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.
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.
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).
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).
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.
/*
Hello World, Franzininho!
primeiro programa para a Franzininho
Por: Fábio Souza
*/
const byte LED = 1; //pino do LED
void setup() {
pinMode(LED,OUTPUT);
}
void loop() {
for(int i = 0;i<5;i++) //pisca o LED 5 vezes com instervalos de 100 ms
{
digitalWrite(LED, HIGH); // liga o led
delay(100); // espera 100 ms
digitalWrite(LED, LOW); // apaga led
delay(100); // espera 100 ms
}
for(int i = 0;i<2;i++) //pisca o LED 2 vezes com instervalos de 1000 ms
{
digitalWrite(LED, HIGH); // liga o led
delay(1000); // espera 1000 ms
digitalWrite(LED, LOW); // apaga led
delay(1000); // espera 1000 ms
}
}
As funções pinMode e digitalWrite 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ônica e Conversã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 pinMode e digitalWrite 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] – FranzininhoCrédito da Imagem Destacada.











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