Programação de Periféricos Mapeados em Memória: Introdução

Este post faz parte da série Programação de Periféricos Mapeados em Memória. Leia também os outros posts da série:

Conhecer as funções e particularidades de cada periférico envolve um processo minucioso de leitura e interpretação de um datasheet. Embora cada periférico tenha uma função, o mesmo procedimento é utilizado para configurá-los. Isso é possível, pois periféricos são controlados por registradores – elementos de memória – que são visíveis ao programador.

 

 

Organização e mapeamento de periféricos

 

Um periférico é composto de um ou mais registradores. Cada registrador possuí nome (endereço) único e a capacidade de armazenamento dependerá das características do dispositivo (8, 16 ou 32 bits).

 

 

Como os registradores são organizados?

 

O registrador pode ser visto como um conjunto de bits que são referenciados por um endereço. De fato, todo registrador é composto por um conjunto de elementos de memória. A menor unidade de armazenamento de informação é o bit, que pode assumir somente um de dois estados: zero ou um. A seguir são ilustrados registradores de 8, 16 e 32 bits.

 

Modelo de um registrador.
Figura 1: Modelo de um registrador.

 

 

Como os registradores são acessados?

 

Para responder essa questão, basta analisar um datasheet. Nas seções que descrevem o dispositivo você encontrará o mapa de memória que apresenta as regiões de memória do espaço de endereçamento. Uma região – ao menos uma – será destinada aos periféricos.

 

Considere, por exemplo, o microcontrolador ATtiny85 que é utilizado na Franzininho. O microcontrolador tem arquitetura de 8 bits e os registradores de periféricos são mostrados na Figura abaixo.

 

Mapa de memória do microcontrolador ATtiny85 com seus registradores.
Figura 2: Mapa de memória do microcontrolador ATtiny85 [1].

 

Resumindo: Um periférico tem um conjunto de registradores. Cada registrador está mapeado em memória em uma posição específica. O registrador também tem uma capacidade de armazenamento que depende de características da arquitetura.

 

 

Acionando terminais do microcontrolador

 

Para exemplificar o uso de registrador, considere uma aplicação típica de sistemas embarcados: acender um LED. Para tal, será necessário controlar um terminal do microcontrolador que está conectado ao LED. A Franzininho tem um LED conectado no pino P1 (PB1).

Franzininho.
Figura 3: Franzininho [2].

 

Em geral, os terminais de um microcontrolador são agrupados em portas (registradores). Assim, cada bit corresponde a um terminal específico. Como destacado na imagem abaixo, o pino 6 corresponde ao bit 1 da porta B (PB1).

 

Figura 4: Encapsulamento e Pinos do microcontrolador ATtiny85 [1].

 

No mapa de memória, são definidos três registradores para controlar o PORTB:

  • DDRB - Controla direção do pino: bit em nível zero, entrada; bit em nível 1, saída;
  • PINB - Valores de entrada: bit indica o nível presente no pino;
  • PORTB - Controla valor de saída do pino: bit determina o nível lógico do pino.

 

Esse exemplo é importante pois evidencia o uso dos registradores e isso vale para qualquer periférico. Para realizar a configuração de uma função no periférico basta alterar um bit ou conjunto de bits do registrador.

 

Neste artigo escrito pelo Fábio Souza tem o seguinte exemplo. 

 

 

As funções pinModedigitalWrite abstraem o acesso ao hardware. Nesse caso, as funções estão alterando o PORTB, especificamente o pino 1:

  • Configurando o pino como saída: necessário estabelecer o bit 1 de DDRB em zero;
  • Configurando o pino como saída alta: necessário estabelecer o bit 1 de PORTB em um;
  • Configurando o pino como saída baixa: necessário estabelecer o bit 1 de PORTB em zero.

 

Nesse ponto, é importante compreender que qualquer valor escrito em um registrador altera todo o conteúdo, isto é, todos os bits. Para aqueles que não conhecem o sistema binário, sugiro a leitura dos artigos Sistemas de numeração mais usados em eletrônicaConversão entre sistemas de numeração, escritos pelo Fábio Souza.

 

 

Configuração de Registradores

 

De modo geral, uma configuração pode envolver a manipulação de apenas um bit ou de um conjunto de bits. A seguir serão demonstradas algumas situações envolvendo registradores de 8 bits.

 

Deseja-se configurar o registrador de modo que o bit 5 tenha o valor 1.

 

Sem considerar os efeitos que um valor atribuído ao registrador pode causar, tal procedimento poderia ser executado da seguinte maneira:

 

REGISTRADOR = 32;

 

Valor 32? Nesse caso, 32 (2^5) é o mesmo que 100000 em binário. Isto é, somente o bit 5 está ativado. Outra maneira de realizar a mesma configuração é a seguinte.

 

REGISTRADOR = (1 << 5);

 

Pois, o valor 1 (decimal) representado no formato binário é 00000001. Agora, considere o valor 1 deslocado (<<) 5 vezes para a esquerda. Tal procedimento é chamado de máscara.

  • 00000001: valor atual;
  • 00000010: valor deslocado uma vez para esquerda (o bit mais a esquerda é descartado);
  • 00000100: zeros são adicionados no primeiro bit;
  • 00001000;
  • 00010000;
  • 00100000.

 

Problemas com o método anterior

 

De fato, o bit 5 teve o valor configurado no exemplo anterior. No entanto, a atribuição do valor 3 ou (1 << 5) estabeleceu todos os bits com valor zero, com exceção do bit 5. Nesse ponto, é importante lembrar que um registrador mantém uma determinada configuração e qualquer modificação implica em uma nova configuração.

 

Assim, tais operações devem manipular somente os bits necessários. Isto é, deve-se manter a configuração anterior e modificar somente o necessário. O cenário agora é o seguinte: o valor que será atribuído corresponde ao valor do próprio registrador mais o da modificação desejada.

 

REGISTRADOR = REGISTRADOR OPERADOR MÁSCARA;

 

Qual operador deve ser utilizado? O operador lógico OR (símbolo | na linguagem C).

 

REGISTRADOR = REGISTRADOR | (1 << 5);

 

Tal operação é realizada bit a bit entre os dois valores. O resultado da operação OR será zero somente se os dois bits foram iguais a zero. Considere que o registrador está com o valor 3. O resultado dessa operação é o seguinte:

 

REGISTRADOR

00000011

MÁSCARA

00100000

Resultado:

00100011

 

Observe que o resultado consiste dos bits que estavam em 1 (valor anterior) mais os bits ativados da máscara. Em outras palavras, são modificados apenas os bits que estão em um na máscara.

 

Estabelecendo zeros

 

Da mesma maneira, o procedimento de manipulação de bits pode ser utilizado para colocar em zero um conjunto de bits. Basicamente, esse procedimento é o oposto do demonstrado anteriormente e utiliza os operadores NOT e AND.

 

Considere que o registrador está com o valor 35 (00100011, resultado do exemplo anterior). Utilizaremos o complemento (NOT) da máscara corresponde ao bit que será posto em zero. Nesse caso, deseja-se colocar em zero o bit 5. O resultado é dado pela operação AND que resulta em zero sempre que um dos bits é zero.

 

REGISTRADOR

00100011

NOT(MÁSCARA)

11011111

Resultado:

00000011

 

O complemento da máscara estabelece todos os bits em 1, exceto aqueles que serão manipulados. Com a operação AND, somente esses bits em zero alterarão o valor do registrador.

 

Na linguagem C, os operadores NOT e AND são representados pelos símbolos ~ e &.

 

REGISTRADOR = REGISTRADOR & ~(1 << 5);

 

Comutação de nível lógico

 

Para inverter o estado de um bit utiliza-se o operador XOR. Na linguagem C, o operador XOR é representado pelo símbolo ^. Tal operação resulta em zero sempre que os bits envolvidos têm o mesmo valor. Assim, são modificados apenas os bits que estão em "um" na máscara.

 

REGISTRADOR = REGISTRADOR | (1 << 5)

00100011

REGISTRADOR = REGISTRADOR ^ (1 << 5)

00000011

REGISTRADOR = REGISTRADOR ^ (1 << 5)

00100011

REGISTRADOR = REGISTRADOR ^ (1 << 5)

00000011

 

 

Retornando ao exemplo

 

Considerando que o LED da Franzininho acende em nível 1. As funções pinModedigitalWrite realizam as seguintes operações:

  • Configurando o pino PB1 como saída: DDRB = DDRB | (1 << 1);
  • Configurando o pino PB1 com saída alta: PORTB = PORTB | (1 << 1);
  • Configurando o pino PB1 com saída baixa: PORTB = PORTB & ~(1 << 1).

 

 

Saiba mais

 

Existem várias formas de estruturar uma aplicação para acessar os periféricos mapeados em memória. No artigo sobre mapeamento de memória são apresentadas técnicas para acessar registradores utilizando recursos da linguagem C.

 

Introdução aos sistemas embarcados e microcontroladores

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

Configuração e inicialização de microcontroladores: Um estudo utilizando ARM Cortex-M0+

 

 

Referências

 

[1] - ATtiny85

[2] - Franzininho

Crédito da Imagem Destacada.

Outros artigos da série

Programação de Periféricos Mapeados em Memória: Módulos >>
Este post faz da série Programação de Periféricos Mapeados em Memória. Leia também os outros posts da série:
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

1
Deixe um comentário

avatar
 
1 Comment threads
0 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
Otnael Rocha Silva Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Otnael Rocha Silva
Visitante
Otnael Rocha Silva

Ótima explicação!
Parabéns Fernando Deluno Garcia.