Registradores

Aviso

Este tópico precisa ser testado e reeditado.

Seguindo os procedimentos das seções anteriores somos capazes de iniciar o sistema e gerar programas a serem executados pelo sistema operacional. O próximo passo é, portanto, controlar os sinais que podem ser enviados a outros dispositivos pelo computador embarcado para estabelecer a comunicação entre os dispositivos.

A comunicação entre dispositivos é feita pela alteração dos níveis de tensão dos pinos do computador embarcado. Esses pinos estão, de uma maneira resumida, conectados a espaços de memória do sistema e, quando alteramos o bit armazenado neste espaço de memória, alteramos também o nível de tensão do pino, permitindo a codificação de uma mensagem e sua transmissão a outro dispositivo.

Posteriormente, a comunicação entre dispositivos será mais discutida, mas neste momento o que mais nos importa são os espaços de memória, citados no parágrafo anterior. Esses espaços de memória são na verdade circuitos digitais voláteis que são capazes de armazenar níveis de tensão, o acesso ao conteúdo desses espaços de memória é extremamente rápido e a estes espaços de memória é dado o nome de registrador. Os registradores estão no topo da hierarquia de memória, sendo assim o tipo de memória mais rápida de uma unidade central de processamento.

Dessa forma, para que possamos implementar a comunicação entre dois dispositivos, um modem e o computador embarcado por exemplo, precisamos, primeiro, executar uma tarefa mais simples de alterar os níveis de tensão de um pino. Esse processo de alterar os níveis de tensão de um pino possui diversas aplicações que vão desde o simples controle de ON/OFF de um LED até comunicação serial entre dispositivos. Aos pinos com esse propósito é dado o nome de General Purpose Input/Output (GPIO).

General Purpose Input/Output (GPIO) são, basicamente, pinos de comunicação de entrada e saída de sinais digitais, de um circuito integrado ou placa de circuito eletrônico, sem finalidade pré-definida, podendo assim ter funções definidas pelo projetista ou usuário para prover uma interface entre outros dispositivos (periféricos, modens, microcontroladores, microprocessadores etc.).

Como comentado anteriormente, estamos utilizando o computador embarcado Overo junto a uma placa de expansão Tobi. Uma das funções desta placa é fornecer acesso ao usuário aos pinos do computador embarcado, portanto os pinos do computador embarcado que podemos acessar fisicamente são os pinos da placa de expansão Tobi. Na figura abaixo podemos visualizar um diagrama que contém, de maneira resumida, quais funções ou pinos do computador embarcado estão conectadas a cada pino da placa de expansão Tobi. Observe que alguns desses pinos possuem mais de uma função.

../../../_images/Pinos_Tobi.png

Diagrama dos pinos da placa de expansão Tobi.

Controle do GPIO via terminal

A maneira mais simples, porém menos eficiente de se controlar o GPIO está descrita no próprio site da fabricante, disponível em Control Overo GPIO . Lá eles indicam controlar o GPIO pelo próprio terminal do sistema Linux através de um sistema sysfs. O sistema sysfs é um sistema de “arquivos“ oferecidos pelo núcleo do Linux para o controle e comunicação com dispositivos e drivers através do terminal do Linux.

Se, por exemplo, desejarmos controlar a saída do GPIO10 através deste método para piscar um LED precisaremos exportar o GPIO10 para o espaço do usuário escrevendo 10 no arquivo /sys/class/gpio/export, o que irá gerar um diretório com outros arquivos para a manipulação do GPIO10. Em seguida, devemos definir sua direção como de saída escrevendo out em /sys/class/gpio/gpio10/direction e definir seu valor como alto ou baixo escrevendo 1 ou 0 em /sys/class/gpio/gpio10/value.

Dica

A função de configuração de interrupção também é acessível pelo terminal.

Este processo pode ser feito tanto pelo terminal do usuário com o comando echo, quanto por um programa que abra esse arquivo e escreve nela por nós. Por exemplo, para controlar o GPIO146 através do terminal podemos executar os seguintes comandos (exemplo utilizado no site da Gumstix):

Nota

Lembrando que o comando echo teste > pasta/arquivo irá sobrescrever todo o arquivo pela palavra «teste» e o comando cat pasta/arquivo irá exibir o conteúdo do arquivo.

root@overo# echo 146 > /sys/class/gpio/export
root@overo:/sys/class/gpio# cat gpio146/direction
in
root@overo# echo out > /sys/class/gpio/gpio146/direction
root@overo:/sys/class/gpio# cat gpio146/direction
out
root@overo# cat /sys/class/gpio/gpio146/value
0
root@overo# echo 1 > /sys/class/gpio/gpio146/value
root@overo# cat /sys/class/gpio/gpio146/value
1

Esse comando controlará o pino 27 da placa Tobi.

Dica

Se você não possuir um medidor, um LED de 1,8V pode ser utilizado. Use o pino 1 como aterramento.

Porém, como já comentado, esse método é bem lento e não pode ser utilizado para comunicação entre dispositivos. Entretanto para atividades com períodos superiores a 100 milissegundos este método pode ser utilizado tranquilamente.

Outra abordagem, utilizando o mesmo método, é utilizar um código semelhante ao código apresentado abaixo, que escreve diretamente nos arquivos do GPIO. Essa abordagem foi testada e melhorou consideravelmente, através de um simples código, o tempo de resposta do GPIO.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

int main()
{
    int arq = open("/sys/class/gpio/export", O_WRONLY);
    write(arq, "10", 2);
    close(arq);

    arq = open("/sys/class/gpio/gpio10/direction", O_WRONLY);
    write(arq, "out", 3);
    close(arq);

    arq = open("/sys/class/gpio/gpio10/value", O_RDWR);

    for (int i = 0; i < 10000; i++)
    {
        write(arq, "1", 1);
        //usleep (500000);
        write(arq, "0", 1);
        //usleep (500000) ;
    }
    close(arq);

    return 0;
}

Download do código 1 comentado

Para testar o código, o pino 18 (pino do GPIO 10) foi conectado a um osciloscópio com o objetivo de medir o período da forma de onda. O resultado dessa medida pode ser visto na figura abaixo, nela podemos ver a amplitude da forma de onda de 1,96 V, frequência de 33,76 kHz e período de 29,62 microssegundos. Para a maioria das aplicações podemos utilizar esse método.

../../../_images/teste1-gpio.png

Controle do GPIO via registradores

Outra maneira de se controlar o GPIO é escrevendo diretamente nos registradores do sistema. Apesar de o procedimento ser um pouco mais complexo essa, na verdade, é a maneira mais comum e mais recomendada de se realizar esse procedimento oferecendo resultados muito mais rápidos.

Para utilizar este método precisamos, primeiro, definir em quais registradores devemos escrever e o que devemos escrever neles. Essa informação só pode ser encontrada no Technical Reference Manual (TRM) do processador DM3730, disponivel no site da Texas Instruments.

Como é explicado na seção 25 do TRM do processador DM3730, a partir da página 3477, a interface de controle combina seis bancos de GPIO. Cada modulo de GPIO providencia 32 pinos, totalizando 192 pinos que podem ser utilizados como input e/ou output. Em nosso caso apenas alguns desses 192 pinos estão fisicamente acessíveis, como pode ser visto na figura apresentada abaixo. Cada banco de GPIO possui 26 registradores distribuídos a partir de um endereço de base, sendo que cada um desses registradores possui um comprimento de 32 bits ou 4 bytes.

../../../_images/interface-gpio.png

Diagrama da interface de GPIO.

Nota

A figura foi retirada do Technical Reference Manual do processador DM3730 e mostra um pouco mais detalhadamente como esses pinos estão distribuídos entre os módulos dos GPIO. A explicação detalhada de cada um desses registradores pode ser encontrada no manual do processador DM3730.

Neste trabalho apenas dois dos registradores serão comentados de forma a ilustrar o funcionamento desses registradores.

O registrador GPIO_OE é o registrador que define a direção do pino que está sendo configurado. A abreviação «OE» vem de output enable. Esse registrador possui um offset de endereço igual a «0x034», ou seja, seu endereço será o endereço de base do modulo do GPIO mais 34 em hexadecimal. Esse registrador possui 32 bits do tipo «Read/White», ou assim, se o pino correspondente à porta GPIO estiver armazenando o valor 0, essa porta GPIO estará configurada para operar como output, caso neste pino esteja o valor 1 a porta estará configurada como input.

O registrador GPIO_SETDATAOUT é o registrador que tem a função de colocar o bit correspondente ao registrador GPIO_DATAOUT em 1. Ou seja, se tudo estiver configurado corretamente, surgirá no pino físico o valor de tensão equivalente ao bit 1. Esse registrador possui endereço de offset igual a «0x094». Assim como o registrador comentado anteriormente este registrador é constituído por 32 bits do tipo «RW». A leitura de qualquer um dos bits deste registrador retorna o valor do bit correspondente em GPIO_DATAOUT.

Além dos registradores apresentados na seção 25 do Technical Reference Manual, também é necessário configurar um registrador do System Control Module (SCM). O SCM é um módulo que permite o controle através de software de várias funções do dispositivo. Para nossa aplicação, o SCM é o ponto primário de controle da função de GPIO e é nele onde vamos realizar a multiplexação, que determina se o pino irá operar na função de GPIO ou em sua função específica, e definiremos se o GPIO será do tipo pullup ou pulldown, por exemplo.

Os registradores do SCM são divididos em cinco classes. Entretanto, para nossa aplicação iremos utilizar apenas uma, o bloco de registradores de configuração e multiplexação. Esse bloco é um conjunto de registradores de 32 bits, que configura 2 pinos e define, além dos dois parâmetros mencionados anteriormente, a função de wakeup. Aos registradores pertencentes a esse bloco é dado o nome de Configuration Register Functionality.

Nota

Mais informações sobre o SCM podem ser encontradas na seção 13 do Technical Reference Manual.

Para encontrarmos qual o endereço de cada registrador deste tipo podemos procurar na tabela 13-4 do TRM. Nessa tabela será dado o endereço físico exato de cada registrador (base+offset). No caso o endereço base é o próprio endereço dos registradores «PADCONFS» da interface do SCM, encontrado na seção 13.6.1 do TRM e o endereço offset de cada registrador deste bloco pode ser encontrado na tabela 13-73 do mesmo documento.

Após a identificação dos registradores podemos iniciar a elaboração de um código para modifica-los. Assim nos deparamos com mais um desafio, sistemas operacionais trabalham com dois conceitos de memória, memória física e memória virtual. Memória física é a memória do hardware, aquela qual sabemos o endereço e pois verificamos no TRM. Entretanto se criarmos um ponteiro que aponta para a memória «0x4800000», por exemplo, ele não irá apontar para a memória física que possui este endereço pois o sistema operacional mapeia um espaço da memória física diferente para cada programa com os principais objetivos de aumentar a segurança e evitar conflitos de dados entre programas.

Entretanto para ter acesso à memória física do sistema precisamos solicitar ao sistema operacional que mapeie esse espaço de memória para a aplicação. Uma maneira de realizar esse procedimento é através da função «mmap()».

Nota

Detalhes do funcionamento dessa função e seus parâmetro podem ser encontrados em mmap(2) — Linux manual page.

Vamos supor que queremos mapear o espaço de memória físico de «0x45000000» até «0x45001000» e para isso decidimos usar a função mmap(). Portanto, chamamos a função da seguinte maneira, por exemplo, mmap(NULL,0x1000,PROT_WRITE || PROT_READ,MAP_SHARED,fd,0x45000000), executando isso a função irá retornar um ponteiro que aponta para um endereço de memória virtual endereçado no endereço de memória física «0x45000000». Em que, para ter acesso à memória física do dispositivo, «fd» é o file descriptor direcionado para «/dev/mem».

Com essas informações, temos tudo o que é necessário para implementar testes acerca deste modo de operação. A seguir temos um código que aplica o método descrito nesta seção para alternar o nível de tensão do pino «186». Esse código foi implementado para se realizar o mesmo teste da seção «Controle do GPIO via terminal».

Nota

O código abaixo foi obtido no Fórum de Discussões da Gumstix e foram realizadas pequenas alterações para evitar o excesso de informação e facilitar sua compreensão.

// Local includes definition
#include <stdio.h>    // for lprint instruction
#include <stdlib.h>
#include <fcntl.h>    // ok for mmap
#include <sys/mman.h> // ok for mmap
#include <unistd.h>

// Defines local parameters (from TRM)
#define SCM_INTERFACE_BASE 0x48002000
#define SCM_PADCONFS_BASE 0x48002030
#define CONTROL_PADCONF_SYS_NIRQ (*(volatile unsigned long *)0x480021E0)
#define CONTROL_PADCONF_SYS_NIRQ_OFFSET 0x1B0

#define GPIO6_BASE 0x49058000
#define GPIO6_SYSCONFIG_OFFSET 0x10
#define GPIO6_CLEARDATAOUT_OFFSET 0x90
#define GPIO6_SETDATAOUT_OFFSET 0x94
#define GPIO6_OE_OFFSET 0x34
#define GPIO6_CTRL_OFFSET 0x30

#define MAP_SIZE (volatile unsigned long)4 * 1024
#define MAP_MASK (volatile unsigned long)(MAP_SIZE - 1)

// Defines "volatile unsigned long" how "u32"
#define u32 volatile unsigned long

// Defines commom variables
u32 *A;
u32 *B;

int main() // Local functions definition
{
    // Defines local variables
    unsigned long i;
    int fd;
    int j;

    fd = open("/dev/mem", O_RDWR | O_SYNC); // "O_RDWR" opens the file for reading and writing & "O_SYNC" guarantees that the call will not return before all data has been transferred to the disk

    A = (u32 *)mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, SCM_INTERFACE_BASE & ~MAP_MASK); // creates a new mapping in the virtual address space

    *(u32 *)((u32)A + 0x30 + CONTROL_PADCONF_SYS_NIRQ_OFFSET) |= (0x00040000); //set mode 4 on the pad 186 configuration register; enables digital pin use

    close(fd);
    /********/

    fd = open("/dev/mem", O_RDWR | O_SYNC);

    B = (volatile unsigned long *)mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO6_BASE & ~MAP_MASK); // COM1 0x4806A000

    //gpio_186 handling
    *(u32 *)((u32)B + GPIO6_SYSCONFIG_OFFSET) |= 0x00000004; // bit2=1 enable/wake up, free running clock

    //*(u32 *)((u32)B + GPIO6_CTRL_OFFSET) &= 0xfffffffe; // bit0=0 module enabled, clock not gated , clock=interface clock divided by 8

    *(u32 *)((u32)B+GPIO6_CTRL_OFFSET)&= 0xfffffff8;  // bit0=0,bit1=0,bit2=0 module enabled, clock not gated , clock=interface clock not divided

    *(u32 *)((u32)B + GPIO6_OE_OFFSET) &= 0xfbffffff; // bit26=0, gpio_186 output

    // generate a pulse stream on gpio_186 pin output

    for (j = 0; j < 1000000; j++)
    {
        *(u32 *)((u32)B + (GPIO6_CLEARDATAOUT_OFFSET)) |= 0x04000000;
        //printf("Saida = 0\n");
        //usleep(1000000);


        *(u32 *)((u32)B + (GPIO6_SETDATAOUT_OFFSET)) |= 0x04000000;
        //printf("Saida = 1\n");
        //usleep(1000000);
    }
    close(fd);
    return (0);
}

Download do código 2 comentado

O código acima foi testado da mesma maneira que o código apresentado na seção anterior. Já na figura a seguir é possível ver o resultado deste teste. Observe que dessa vez o tempo obtido foi 720,3 nano segundos, ou seja, aproximadamente 42 vezes mais rápido que o resultado do outro método. Além disso, podemos observar que a forma de onda não é mais um sinal retangular exato, a presença de um efeito capacitivo retardando o processo é evidente, portanto, é possível que essa seja a velocidade máxima em que o sinal de um pino pode ser alterado.

Muito dificilmente alguma aplicação envolvendo GPIO não será satisfeita por algum dos métodos aqui apresentados.

Problemas de escrita em registradores

Para finalizar este último tópico é necessário destacar alguns problemas recentemente encontrados envolvendo escrita em registradores.

O primeiro problema encontrado ocorre sempre que tentamos alterar o valor dos registradores «0x49050030», «0x49056030» e «0x49058030», responsáveis por controlar o clock de todo o bloco do «GPIO_2», «GPIO_5» e «GPIO_6», respectivamente.

Nota

devmem2 é um comando que executa um programa simples para ler ou escrever em qualquer espaço de memoria. Mais informações podem ser encontradas em devmem2 - Ubuntu Manual.

O que ocorre é que instantes após a alteração do valor do registrador, seu valor retorna ao que possuía antes de ser alterado. Como o teste desta seção apresentou frequência muito alta ele não foi interrompido por este efeito, porém o fenômeno ocorre inclusive quando alteramos valores dos registradores por comandos do terminal, como o devmem2. Esse problema está exemplificado na figura abaixo, onde executamos o comando devmem2 0x49058030 w 0x2 para modificar o registrador 0x49058030 que é o registrador que controla o clock de todo o bloco do GPIO6.

../../../_images/register-erro.png

Tal modificação deveria realizar uma redução na velocidade do clock dividindo-o por 2, como indicado no Technical Reference Manual (TRM) do processador DM3730, na tabela 25-29, página 3528, onde é explicado que o GPIO_CTRL pode ter seu clock dividido por certos valores pré-cadastrados, como apresentado na figura a seguir.

../../../_images/GPIO_CTRL.png

Porém, logo após a execução do comando é realizado um procedimento de leitura que garante que tudo foi escrito no registrador como o esperado. No entanto, o mesmo comando, executado instantes depois no modo de leitura, sempre retorna ao valor anteriormente armazenado, o valor existente no registrador antes da modificação. Vale ressaltar que este problema não ocorre para o método de controle do GPIO via terminal, este método opera até que receba uma ordem de parada do usuário.

O segundo problema encontrado ocorre quando tentamos alterar o valor dos registradores 0x49052030 e 0x49054030, responsáveis por controlar o clock de todo o bloco do GPIO_3 e do GPIO_4, respectivamente. Nesses registradores em específico, ao tentar executar o comando devmem2 para alterar o clock de um determinado bloco de GPIO ou apenas realizar uma leitura, o sistema retorna o erro «bus error» como apresentado na figura abaixo, onde executamos o mesmo comando no registrador 0x49054030.

../../../_images/register-bus_erro.png

Dessa forma, foi possível apenas alterar o clock do bloco do GPIO_1, como pode ser visto na imagem abaixo.

../../../_images/register-clock.png

Não sabemos por quais motivos esses fenômenos estão ocorrendo com os blocos de 2 a 6, porém suspeitasse que alguns processos do sistema operacional estejam impedindo que o clock de tais blocos seja alterados, provavelmente por algum circuito interno ou operação depende de tais valores pré-definidos ou até por alguma restrição no consumo de energia.

Referências

  • PITA, H. C. Desenvolvimento de sistema de comunicação multiplataforma para veículos aéreos de asa fixa. Faculdade de Tecnologia, Universidade de Brasília, 2018.
  • TEXAS INSTRUMENTS. AM/DM37x Multimedia Device Technical Reference Manual. 12500 TI Blvd, Dallas, TX 75243, EUA, 2012. Version R. Disponível em: ti.com.
  • Direct register access control of GPIO ARM interface on Overo Water +TOBI - Gumstix Discussion Forum