Técnicas de Mapeamento de Memória em Linguagem C

Olá, caro leitor! Há um tempo escrevi um artigo sobre especificadores de acesso da linguagem C. Nesse artigo, apresentei os modificadores const e volatile, exemplificando algumas operações para acessar registradores de um microcontrolador. Tais operações são conhecidas como mapeamento de memória e podem ser realizadas de maneiras diferentes.

 

De modo geral, existem quatro maneiras de fazer o mapeamento de memória em um firmware: mapeamento direto; por ponteiro; por estruturas; vetor de ponteiros. Assim, este artigo tem como objetivo apresentar tais métodos, destacando as diferenças e comparado-os. Sempre que necessário será reforçado o uso dos modificadores de acesso.

 

 

Técnicas de mapeamento em memória

 

Como dito anteriormente, existem diversas técnicas que podem ser empregadas para fazer o mapeamento de memória. De modo geral, essas técnicas diferem nos seguintes aspectos [1]: densidade do código, tempo de execução, eficiência, portabilidade e grau de configuração.

 

Antes de descrever as técnicas é importante definir os seguintes conceitos:

 

Periférico mapeado em memória (Memory mapped I/O)

 

Periféricos estão mapeados em memória e ocupam o mesmo espaço de endereçamento do programa / dados, isto, é claro, depende da arquitetura. Assim, as operações de escrita e leitura são as mesmas definidas para qualquer movimentação de dados.

 

Porta isolada (Port mapped I/O)

 

Periféricos estão mapeados em um espaço de endereçamento que pode ser dedicado. No entanto, um conjunto específico de instruções é utilizado para acessá-los. Por exemplo, instruções IN e OUT.

 

Mapeamento de memória direto

 

É comum que fabricantes disponibilizem arquivos de cabeçalho com definição de nomes dos registradores. Ao analisar tais definições verifica-se que cada nome está relacionado a um endereço de memória. Geralmente, isso é feito utilizando diretivas de pré-processamento (#define). A seguir é mostrado a biblioteca io2313 com a definição dos registradores do microcontrolador ATtiny2313.

 

 

Um exemplo de acesso aos registradores é mostrado abaixo.

 

 

A princípio, ao utilizar o nome do registrador será efetuado o acesso direto ao endereço especificado. No entanto, é importante consultar a biblioteca. Por exemplo, a macro _SFR_IO8 é definida na biblioteca sfr_defs e determina se o endereço usado como argumento será deslocado em 0x20 bytes. Se deslocado, o endereço corresponde à área de I/O mapeada em memória e possui instruções específicas para acesso. Dito de outra maneira, no acesso direto são usadas as instruções LDS e STS para leitura e escrita, já no outro caso são usadas as instruções IN e OUT.

 

 

O conjunto de instruções gerado é mostrado a seguir. Verifique que a instrução OUT foi utilizada para acessar os registradores. O mesmo pode ser feito com a instrução STS usando o endereço direto (endereço de I/O + 0x20).

 

 

Mapeamento de memória usando ponteiros

 

Nesta técnica um ponteiro é declarado com o endereço do registrador que será acessado. Considere, por exemplo, o acesso ao registrador de dados PORTB do microcontrolador ATtiny2313.

 

 

Essa declaração define um ponteiro para um valor uint8_t. No entanto, é importante consultar a documentação do compilador utilizado, pois podem ocorrer algumas otimizações de código. Para este caso, o acesso direto usando o espaço de I/O continua sendo utilizado. Verifique o conjunto de operações gerado (mesmo modo de acesso do tópico anterior): 

 

 

Continuando este tópico, é importante considerar que um periférico pode ter valores alterados sem influência do software, por exemplo, campos de bits que representam flags de controle ou entradas digitais. Assim, é imprescindível que as operações de leitura sejam sempre realizadas no endereço indicado, obtendo o valor atual do registrador. Para forçar o compilador a realizar essas tarefas o modificador de acesso volatile deve ser utilizado.

 

 

O código mostrado acima garante que a operação de leitura do registrador será realizada. Agora, considerando que o acesso - a princípio - é realizado por um ponteiro, tal variável pode ser manipulada acidentalmente, alterando o endereço apontado. Para evitar esse problema o modificador de acesso const deve ser utilizado.

 

 

Essa declaração define um ponteiro constante para um valor uint8_t. Assim, o modificador volatile determina que o acesso ao registrador sempre será realizado, evitando problemas com otimizações do compilador. Já o modifcador const impede que o ponteiro seja manipulado.

 

Mapeamento de memória usando estruturas

 

Neste tipo de mapeamento é criada uma estrutura contendo membros com nome dos registradores. Tal declaração corresponde diretamente ao mapa de memória do microcontrolador. É importante notar que o tipo de dado deve ser compatível com a quantidade de bits do registrador. Por exemplo, considere o mapa de memória microcontrolador MKL25Z [2] mostrado na Figura 1.

 

Mapeamento de memória dos registradores de GPIO.
Figura 1: Mapa de memória dos registradores de GPIO.

 

No trecho de código abaixo é mostrado a definição de um tipo de dados para representar tais registradores de GPIO. Confira a ordem de declaração conforme a Figura 1.

 

 

A definição da estrutura não representa uma informação armazenada. É utilizada apenas como meio de referenciar endereços de memória. Para acessar os registradores deve ser declarado um ponteiro para o tipo de dado da estrutura.

 

Ao utilizar o nome do ponteiro, o acesso a um determinado membro representa apenas um deslocamento (offset) em relação ao endereço base. Por isso é importante a declaração dos registradores na ordem do mapa de memória, bem como da capacidade de armazenado (diretamente relacionada ao offset).

 

No trecho de código abaixo é mostrada a definição de dois pontos de acesso ao conjunto de registradores dos módulos GPIOA e GPIOB.

 

 

Já o acesso a um registrador do módulo de GPIO é determinado a partir do endereço base:

 

 

Mapeamento de memória usando vetor de ponteiros

 

A ultima técnica apresentada é utilizada para agrupar conjunto de registradores para relacioná-los a partir de um nome comum. Além de referenciá-los da mesma maneira, tais registradores também são agrupados por função. De modo geral, um vetor de ponteiros é criado para armazenar o endereço de tais registradores. Por exemplo, todos os registradores de saída (nos exemplos do artigo, PORTx ou GPIOx_PDOR) podem ser agrupados em um vetor.

A aplicação desta técnica torna-se interessante para criar soluções genéricas. Isto é, uma vez que os registradores estão agrupados por funções, o código pode ser estruturado para criar módulos. Por exemplo, no trecho de código abaixo os registradores foram agrupados em duas categorias, direção e valor de saída, e duas funções foram criadas para configurá-los. A indicação do registrador utilizado é determinada pelo nome do módulo (Port_t).

 

 

A aplicação fica limitada a este exemplo simples. Tabelas de configuração de periféricos podem ser criadas e passadas para uma função de inicialização. Por sua vez, tal função configura todos os elementos definidos na tabela com base no registradores declarados. Este princípio é aplicado na construção de drivers, em que somente a parte de código que acessa o hardware deve ser alterada. Isto é, as funções definem uma interface de acesso para um conjunto de recursos, já os recursos são configurados conforme o dispositivo.

 

 

Conclusão

 

Após apresentar as técnicas de mapeamento é importante fazer algumas comparações. Na ordem em que foram apresentadas, essas técnicas diminuem a densidade do código (aumenta o número de instruções) e acrescem o tempo de execução, isto é, reduzem a eficiência. No entanto, a portabilidade e grau de configuração dos periféricos aumentam substancialmente.

 

De fato, a aplicação destas técnicas dependerá do dispositivo utilizado, pois em alguns casos a quantidade de memória disponível é baixa. Assim, deve-se considerar esses fatores, balanceando portabilidade do código, facilidade de configuração (abstração do hardware) e dos recursos disponíveis.

 

 

Saiba mais

 

Modificadores de Acesso na Linguagem C

Objetos em Linguagem C

Struct - Registros em Linguagem C

Estilo de código - Boas práticas de programação em linguagem C

 

 

Referências

 

[1] BENINGO, J. Reusable Firmware Development: A Practical Approach to APIs, HALs, and Drivers.

[2] KL25 Sub-Family Reference Manual.

Fonte da Imagem destacada

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Fernando Deluno Garcia
Fascinado por computação, especialmente na interface entre hardware e software, me engajei na área de sistemas embarcados. Atuo com desenvolvimento de sistemas embarcados e sou docente da Faculdade de Engenharia de Sorocaba.Para mais informações: https://about.me/fdelunogarcia

Deixe um comentário

avatar
 
  Notificações  
Notificar