Skip to main content

Introdução à Robótica

Introdução:

A melhor forma de aprender e desenvolver o chamado pensamento computacional em princípio, é estar sempre apoiado no cotidiano. Problemas que existem e mesmo os que já tem solução pronta são boas ferramentas neste objetivo. Um semáforo por exemplo, na sua forma mais simples seria apenas sinais luminosos alternando cores em função de tempos pré-determinados visando organizar por exemplo, o movimento de veículos e pedestres. Este mesmo semáforo pode agregar novas características melhorando a sua função de organizar passando a otimizar ou mesmo melhorar as condições de fluxo de veículos e pedestres, por exemplo. O projeto, portanto, passará por vários estágios. O primeiro, um semáforo simples com tempos fixos. Em um segundo estágio iremos considerar a faixa de pedestre, por exemplo, com acionamento para evitar interromper o trânsito sem necessidade. No último estágio faremos um cruzamento com dois semáforos interligados, algo comum no nosso entorno. Como proposta para continuidade do processo passaremos então pela observação de casos reais de cruzamentos buscando possíveis melhorias. Como exemplos, mas não se limitando a eles poderemos responder a algumas perguntas. É possível melhorar o desempenho do sistema alterando o funcionamento com base no horário e mesmo o dia da semana? Com base em questões de segurança é possível liberar a passagem livre no cruzamento em horários determinados? Como isso poderia ser feito de maneira segura?

1. Primeiro estágio: Semáforo simples

1.1. Material necessário:

    - Arduino Uno
    - 3 resistores de 560Ω*
    - 1 LED verde
    - 1 LED amarelo
    - 1 LED vermelho
    - Jumpers
    - Protoboard

*- A função do resistor aqui é limitar a corrente que passa pelo LED, se formos calcular considerando que LEDs com essas cores apresentam uma tensão de barreira próxima a 2,1V, uma corrente de 20mA e tensão de 5V nas saídas digitais co Arduino, chegariamos a um valor próximo a 145Ω (150Ω - valor comercial mais próximo para resistores de precisão superior a 5%). Adotamos aqui o valor de 560Ω mas qualquer valor entre 150Ω e 560Ω pode ser usado. Valores mais altos podem levar a um brilho muito baixo

1.2. Código:

No nosso primeiro encontro vimos como fazer um LED piscar por um tempo determinado (código exemplo em anexo). Como o objetivo é desenvolver o pensamento computacional não será apresentado aqui o código para um semáforo simples para que vocês implementarem simplesmente copiando. A primeira tarefa será então, com base nos exemplos explicados abaixo, escrever o código. O funcionamento deverá se iniciar com o LED amarelo aceso por 10 segundos, logo depois o vermelho por 20 segundos e finalmente o verde por 15 segundos e assim por diante. Já nesse estágio vocês deveram pensar em uma maneira que facilite a alteração dos tempos de acionamento de cada LED já prevendo por exemplo uma alteração em função de horário e dia de semana, por exemplo. Nos códigos exemplo serão apresentadas mais duas maneiras de fazer o LED piscar utilizando outras instruções/comandos. Vocês escolheram qual caminho utilizar.

1.3. Diagrama e montagem:

Lembrando novamente o nosso primeiro encontro os terminais positivos (anodo) dos LEDs deveram ser conectados em portas digitais de uso geral utilizando os resistores em série e os negatívos (catodo) ligados ao GND da placa (Figura 6).

Alguns resistores utilizam um código de cores definidos pela IEC (International Electrotechnical Conmission) 60062. As cores devem ser lidas partir da faixa mais próxima da extremidade do resistor. Dependendo do valor e precisão podem ter de 3 a 6 faixas de cores. No nosso caso, 4 faixas, seriam digito, digito, multiolicador (número de zeros ou potência de 10) e tolerância.

Cor Valor Multiplicador Tolerância
Preto 0 x 1
Marrom 1 x 10 ±1%
Vermelho 2 x 100 ±2%
Laranja 3 x 1.000
Amarelo 4 x 10.000
Verde 5 x 100.000 ±0,5%
Azul 6 x 1.000.000 ±0,25%
Violeta 7 x 10.000.000 ±0,1%
Cinza 8
Branco 9
Ouro x 0,1 ±5%
Prata x 0,01 ±10%

O resistor de 560Ω teria então as faixas verde, azul, marrom e ouro ou prata.

Fig 1 - Resistor 560Ω

Fig 2 - LEDs

Fig 3 - Arduino Uno

Fig 4 - Protoboard

Fig 5 - Jumpers

Fig 6 - Led - terminais

1.4. Código exemplo I:


//definiçes

int vermelho = 10;


void setup() {
  // put your setup code here, to run once:
  //colocando a porta de uso geral (GPIO - General Purpose Input/Output) na função saída (Output)
  pinMode(vermelho, OUTPUT);

}

void loop() {um
  // put your main code here, to run repeatedly:
  digitalWrite(vermelho, HIGH); //pode ser usado HIGH, true ou 1)
  // intervalo de tempo de 6 segundos
  delay(6000);
  digitalWrite(vermelho, LOW);  //pode ser usado LOW, false ou 0) é importantet ao longo do cédigo manter um padrão 
  // intervalo de tempo de 3 segundosã
  delay(3000);
}

Instruções:

int vermelho = 10;

Esta instrução define uma área de memória contendo um valor. Neste caso uma constante do tipo inteiro. Representa o número da GPIO (General Purpose Input/Output) que será usado para energizar o LED.

pinMode(vermelho, OUTPUT);

A função void setup (){} serve para inicializar os recursos do processador que serão utilizados durante a funcionamento. Como descrito acima a variável vermelho guarda o número da GPIO utilizada para alimentar o LED. Poderíamos utilizar diretamente o valor 10 no lugar de vermelho. Entretanto isso poderia se tornar um problema em um código mais longo onde se mostrasse necessário substituir o GPIO utilizado.

digitalWrite(vermelho, HIGH);

Coloca o GPIO no estado alto (HIGH), 3,3V ou 5V dependendo do tipo da placa, ou baixo (LOW), 0V.

delay(6000);

Promove uma pausa no processamento pelo tempo especificado em milissegundos.

1.5. Código exemplo II:


//definiçes

int vermelho = 10;

int contador = 0;

void setup() {
  // put your setup code here, to run once:
  //colocando a porta de uso geral (GPIO - General Purpose Input/Output) na funço saãda (Output)
  pinMode(vermelho, OUTPUT);

}

void loop() {
  // put your main code here, to run repeatedly:
  contador = contador + 1;

  if (contador == 1) {
    digitalWrite(vermelho, HIGH);      
  } else if (contador == 7) {
    digitalWrite(vermelho, LOW);
  } else if (contador == 9) {
    contador = 0;
  }
  delay(1000); 
  
}
 

O bloco de instrução if … else permite a avaliação de uma ou mais condições e a execução ou não de uma ou mais instruções em função dessas avaliações.

Operadores utilizados nas avaliações:

Operador: Siginificado
x == y x é igual a y
x != y x é diferente de y
x < y x é menor do que y
x <= y x é menor ou igual a y
x > y x é maior que y
x >= y x é maior ou igual a y

Importante: O operador x == y realiza uma comparação entre os valores x e y. Já x = y coloca na variável x o valor de y. Um erro comum em códigos: “if (x = y) {}”

1.6. Código exemplo III:

//definiçes

int vermelho = 10;

int contador = 0;

void setup() {
  // put your setup code here, to run once:
  //colocando a porta de uso geral (GPIO - General Purpose Input/Output) na funço saãda (Output)
  pinMode(vermelho, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  contador = contador + 1;   
  switch (contador) {
    case 1:
      digitalWrite(vermelho, true);
      break;
    case 7:
      digitalWrite(vermelho, false);
      break;
    case 9:
      contador = 0;
      break;      
  }
  delay(1000);
}  

Da mesma forma que o comando if no exemplo anterior, o comando switch .. case permite a avaliação de uma ou mais condições e a execução ou não não de uma ou mais instruções a ´ partir das avaliações. É importante aqui utilizar no o comando break no final de cada case. Se não for utilizado as demais instruções abaixo da primeira condição considerada positiva serão executados. Em projetos futuros veremos como usar essa característica, no momento é importante utilizar esse comando mesmo na última instrução.

1.7. Código desafio:


//definiçes

int vermelho = 10;

int contador = 0;

word semaforo = 63;

void setup() {
  // put your setup code here, to run once:
  //colocando a porta de uso geral (GPIO - General Purpose Input/Output) na funço saãda (Output)
  pinMode(vermelho, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:

  digitalWrite(vermelho, (semaforo & 1));
  semaforo = semaforo >> 1;
  contador = contador + 1;   
  if (contador == 9) {
    contador = 0;
    semaforo = 63;
  }
  delay(1000);   
}

O objetivo do desafio aqui é, não só mostrar que existem inúmeras maneiras de executar uma mesma função, mas demonstrar que é sempre possível buscar novas soluções eventualmente mais eficientes e melhores adaptadas a mudanças e aperfeiçoamentos. A tarefa aqui será explicar o funcionamento do código acima.

1.8. Arduíno IDE:

O Arduino é uma plataforma criada originalmente por Massino Banzi e sua equipe no Interaction Design Institute Ivrea na cidade de Irera, Itália. Criada com o objetivo de proporcionar uma alternativa barata, com código aberto, combinando hardware e software, facilitando a prototipagem eletrônica. O nome é uma homenagem a um bar local e revolucionou juntando em uma única placa um microcontrolador com comunicação serial simplificando não só o processo de gravação dos projetos como a depuração dos mesmos. Tem como principais características o baixo custo, hardware aberto, ou seja, os esquemas das placas são disponibilizados. Utiliza uma linguagem de fácil aprendizado, sendo uma versão bem simplificada da linguagem c/c++. Possui um grande volume de documentação e tutoriais disponíveis graças a uma grande comunidade ativa. O desenvolvimento do código é feito normalmente na IDE (Integrated Development Environment – Ambiente de Desenvolvimento Integrado) denominado Arduíno IDE. Entretanto pode ser feito em outros IDEs como o VS Code da Microsoft e o Eclipse por exemplo. Existem também alguns simuladores que permitem testes de códigos sem o uso do hardware. O download do IDE deve ser feito preferencialmente no site oficial do Arduino

Fig 6 - Arduíno IDE

A fig 6 mostra a IDE com o código do exemplo I já com a placa “Arduino Uno” selecionada.

1.9. Questionário:

Primeiro Estágio

1.10. Atividade Prática:

A nossa primeira atividade será na verdade dividida em duas. A primeira será a elaboração de um diagrama esquemático representando a montagem do simulador do semáforo utilizando os materiais já citados no item 1.1. acima. No esquema deverão ser identificados os leds e os resistores associados a cada led, as conexões entre os leds e os pinos digitais da placa identificados, as ligações ao terminal GND (terra) e o sentido correto de polaridade dos leds. A segunda será a implementação do código do semáforo utilizando a IDE Arduino. Deverão ser utilizados comentários explicando a função de cada linha implementada, a exemplo do código apresentado no item 1.3.1 acima. A IDE Arduíno normalmente salva cada projeto em uma pasta com o nome iniciado por sketch seguido do mês e dia atual seguido de uma letra que é incrementada em se havendo mais de um projeto no mesmo dia. (sketch_jun01b). Utilizando o menu File->Save As, renomear substituindo sketch pelo nome do participante em letras minúsculas sem acentuação. (ex: carlos_jun01c). Compactar a pasta e enviar utilizando o e-mail ou WhatsApp constantes no menu superior direito da página.

1.11 Análise do questionário.

Principal objetivo da aprendizagem de robótica:

Não existe, por assim dizer, uma resposta incorreta. No entanto, o que se busca é o principal objetivo:

“Desenvolver o pensamento computacional”

O pensamento computacional não se restringe apenas a apredizagem de linguagens, desenvolvimento de protótipos, uso de ferramentas e dispositivos. Ele busca desenvolver a capacidade de pensar de forma sistetmática e criativa na identificação e solução de problemas, sempre apoiada na lógica.

Qual a função do resistor conectado em série com o led nas portas digitais de saída das placas de desenvolvimento.

Limitar a corrente que passa pelo led, protegendo tanto ele como as portas de saída do Arduíno.

Código de cores para um resitor de 820Ω 10%:

Da faixa mais próxima da extremidade cinza, vermelho, marrom, prata.

Ou seja nenhuma das apresentadas. (Outro:)

  • Cinza - 8
  • Vermelho - 2
  • Marrom - (82) x 10 = 820
  • Prata - 10%

Valor de um resistor com faixas marrom, laranja, dourado, marrom a partir da mais próxima da extremidade.

  • Marrom - 1
  • Laranja - 3
  • Dourado - (13) x 0,1 = 1,3Ω
  • Marrom - 1%

Qual o objetivo da função void setup() {} do Arduino

Permite configurar o processador conforme o objetivo do código. Define o uso dos pinos e dos recursos existentes como timers, conversores, saídas/entradas analógicas ou digitais.

Qual foi o objetivo original dos criadores da plataforma Arduíno?

“Criada com o objetivo de proporcionar uma alternativa barata, com código aberto, combinando hardware e software, facilitando a prototipagem eletrônica.”

2. Segundo estágio: Processador, registradores e interrupções

Para avançarmos, passa a ser interessante introduzir alguns conceitos um pouco mais complexos, facilitando assim o entendimento desse tipo de sistema. A ideia aqui foi sempre trazer desafios próximos a nossa realidade. O semáforo se encaixa bem nesse objetivo, pois permite partirmos de algo bastante simples e avançarmos em sistemas com níveis extremamente sofisticados. No limite, poderiam utilizar recursos de Inteligência Artificial Adaptativa, visando otimizar o fluxo de pedestres e veículos de maneira continua e evolutiva. Podemos assim buscar melhorias na qualidade de vida e preservação de recursos, reduzindo tempos em deslocamento e economia de combustíveis entre outros. Considerem então os tópicos abaixo como algo a mais, um conhecimento desejável, mas não absolutamente necessário para o avanço do projeto.

2.1 O processador.

O Arduino Uno R3 utiliza o microcontrolador ATmega328P, fabricado originalmente pela Atmel, atualmente parte da Microchip Technology. Ele é o componente responsável por executar o programa carregado na placa, ler sensores, processar dados e controlar dispositivos externos.

2.1.1 Principais características do ATmega328P

Característica Especificação
Arquitetura AVR RISC, de 8 bits
Frequência de operação 16 MHz no Arduino Uno R3
Tensão de operação 5 V, normalmente
Memória Flash 32 KB
SRAM 2 KB
EEPROM 1 KB
Entradas/saídas digitais 14 pinos, dos quais 6 podem gerar PWM
Entradas analógicas 6 canais, ADC de 10 bits
Temporizadores 2 de 8 bits e 1 de 16 bits
Interfaces de comunicação UART, SPI e I²C (TWI)
Encapsulamento na versão clássica DIP de 28 pinos ou SMD, dependendo da placa

2.1.2 Arquitetura interna

O ATmega328P possui uma arquitetura Harvard modificada, que separa os espaços de memória de programa e de dados.

CPU AVR de 8 bits

Unidade de controle, ULA e registradores

Memória Flash — 32 KB

Armazena o programa executável

SRAM — 2 KB

Variáveis e dados temporários

EEPROM — 1 KB

Dados que persistem sem energia


Periféricos integrados

GPIO, ADC, temporizadores, PWM, UART, SPI e I²C

Funções dos principais componentes

  • CPU: executa as instruções do programa e realiza operações aritméticas e lógicas.
  • Flash: mantém o programa mesmo quando a alimentação é desligada.
  • SRAM: armazena variáveis durante a execução; seu conteúdo é perdido ao desligar.
  • EEPROM: permite guardar configurações e dados que devem permanecer após o desligamento.
  • GPIO: possibilita ler sinais digitais e comandar LEDs, relés e outros circuitos compatíveis.
  • ADC: converte sinais analógicos em valores digitais de 10 bits, representando até 1.024 níveis.
  • Temporizadores: permitem medir intervalos, gerar sinais PWM e implementar funções de temporização.

2.1.3 Como ele executa um programa?

No Arduino Uno, o microcontrolador trabalha normalmente com um clock de 16 MHz, equivalente a 16 milhões de ciclos por segundo.

O ciclo básico de funcionamento é:

  1. Busca uma instrução armazenada na memória Flash.
  2. Decodifica a instrução.
  3. Executa a operação na CPU.
  4. Atualiza registradores, memória ou periféricos.
  5. Continua para a próxima instrução.

Muitas instruções AVR simples são executadas em um ciclo de clock, embora outras exijam vários ciclos. Portanto, 16 MHz não significa necessariamente 16 milhões de instruções por segundo.

Por exemplo, em um programa que pisca um LED, a CPU executa instruções para configurar o pino, alterar seu estado e controlar o intervalo entre as mudanças.

Observação: esta descrição refere-se ao Arduino Uno R3 clássico. Outras versões, como o Arduino Uno R4, utilizam microcontroladores diferentes, com arquitetura e capacidade de processamento distintas.

2.1.4 Pinagem do Arduino Uno R3 — ATmega328P

O Arduino Uno R3 utiliza o microcontrolador ATmega328P, que possui 28 pinos no encapsulamento DIP ou 32 pinos na versão TQFP, usada em placas com montagem superficial. A pinagem física depende do encapsulamento.

no próximo item veremos os pinos do modelo ATmega328P DIP-28, utilizado no Arduino Uno R3 clássico, e suas funções principais.

É importante distinguir os pinos físicos do microcontrolador dos pinos digitais e analógicos disponibilizados nos conectores da placa Arduino. Isso ficará claro se analizarmos o diagrama constatnte no próximo item.

2.1.5 Diagrama esquemático do Arduino Uno R3

O diagrama esquemático apresenta as conexões elétricas e os principais componentes da placa Arduino Uno R3, incluindo o microcontrolador ATmega328P, a interface USB, os circuitos de alimentação e os conectores de entrada e saída.

Abrir ou baixar o diagrama esquemático em PDF No esquema apresentado acima podemos ver dois processadores. O mais à direita, ATmega328P tratado aqui e o ATmega16U2 que tem como principal funçaõ a conversão USB Serial, que permite a comunicação entre a IDE e a placa Arduíno. Vale lembrar aqui que, as placas não oficiais utilizam em geral como alternativa, os processadores CH340 ou CP210x para essa função.

2.1.6 Pinos físicos do ATmega328P DIP-28

O encapsulamento DIP possui 14 pinos de cada lado. A numeração começa no pino 1, próximo à marca de referência do componente, e segue no sentido anti-horário, visto de cima.

Pino físico Nome do pino Função principal Pino Arduino
1 PC6 / RESET Reinicialização do microcontrolador RESET
2 PD0 / RXD Recepção serial UART D0 (RX)
3 PD1 / TXD Transmissão serial UART D1 (TX)
4 PD2 / INT0 Entrada digital e interrupção externa D2
5 PD3 / OC2B Entrada/saída digital, PWM e interrupção D3 (PWM)
6 PD4 / T0 Entrada/saída digital e entrada de contador externo D4
7 VCC Alimentação digital 5 V
8 GND Terra (referência elétrica) GND
9 PB6 / XTAL1 Entrada do circuito de clock externo Cristal
10 PB7 / XTAL2 Saída do circuito de clock externo Cristal
11 PD5 / OC0B Entrada/saída digital e PWM D5 (PWM)
12 PD6 / OC0A Entrada/saída digital, PWM e comparador analógico D6 (PWM)
13 PD7 Entrada/saída digital e entrada do comparador analógico D7
14 PB0 / ICP1 Entrada/saída digital e captura de eventos do Timer1 D8
15 PB1 / OC1A Entrada/saída digital e PWM D9 (PWM)
16 PB2 / OC1B / SS Entrada/saída digital, PWM e seleção de escravo SPI D10 (PWM/SS)
17 PB3 / MOSI Entrada/saída digital, PWM e transmissão SPI D11 (PWM/MOSI)
18 PB4 / MISO Entrada/saída digital e recepção SPI D12 (MISO)
19 PB5 / SCK Entrada/saída digital e clock SPI D13 (SCK/LED)
20 AVCC Alimentação do circuito analógico e do conversor ADC Alimentação analógica
21 AREF Referência de tensão do conversor analógico-digital AREF
22 GND Terra GND
23 PC0 / ADC0 Entrada/saída digital e entrada analógica A0
24 PC1 / ADC1 Entrada/saída digital e entrada analógica A1
25 PC2 / ADC2 Entrada/saída digital e entrada analógica A2
26 PC3 / ADC3 Entrada/saída digital e entrada analógica A3
27 PC4 / ADC4 / SDA Entrada/saída digital, entrada analógica e comunicação I²C A4 (SDA)
28 PC5 / ADC5 / SCL Entrada/saída digital, entrada analógica e comunicação I²C A5 (SCL)

Referências: datasheet do ATmega328P e documentação de mapeamento dos pinos do Arduino. Arduino Content

2.1.7 Resumo dos pinos disponíveis no Arduino Uno R3

Na placa Arduino Uno R3, os pinos do microcontrolador são disponibilizados em conectores para facilitar a conexão de sensores, atuadores e outros circuitos.

Categoria Pinos Função
Digitais D0 a D13 Leitura e escrita de sinais digitais
PWM D3, D5, D6, D9, D10 e D11 Geração de sinais com modulação por largura de pulso
Analógicos A0 a A5 Leitura de sinais analógicos pelo ADC de 10 bits
Comunicação UART D0 (RX) e D1 (TX) Comunicação serial
Comunicação SPI D10 a D13 Comunicação serial síncrona SPI
Comunicação I²C A4 (SDA) e A5 (SCL) Comunicação com sensores e periféricos I²C
Interrupções externas D2 e D3 Disparo de interrupções externas
Alimentação 5 V, 3,3 V e GND Alimentação e referência elétrica dos circuitos
Referência analógica AREF Referência externa opcional para o ADC
Reinicialização RESET Reinicia o microcontrolador

A disponibilidade de uma função alternativa não significa que todas as funções de um pino possam ser utilizadas simultaneamente. A configuração depende dos periféricos e dos registradores utilizados pelo programa.

2.1.8 Observações importantes

1. Pinos analógicos também podem funcionar como digitais. Os pinos A0 a A5 correspondem aos pinos PC0 a PC5 do microcontrolador e podem ser utilizados como entradas ou saídas digitais.

2. PWM não é uma saída analógica verdadeira. Os pinos PWM produzem sinais digitais cuja largura dos pulsos pode ser controlada. O efeito médio depende do circuito conectado.

3. Os pinos D0 e D1 são compartilhados com a comunicação serial UART. Utilizá-los para outros fins pode interferir na comunicação com o computador e no carregamento de programas.

4. Os pinos 9 e 10 do encapsulamento DIP são destinados normalmente ao cristal externo de clock. No Arduino Uno R3 clássico, eles estão conectados ao circuito de oscilação da placa e não ficam disponíveis como entradas e saídas digitais comuns.

5. A pinagem depende do encapsulamento. A tabela acima se refere ao ATmega328P DIP-28. Na versão TQFP-32, os números físicos são diferentes e existem os pinos adicionais ADC6 e ADC7, que não estão disponíveis no encapsulamento DIP-28.

2.2 Registradores em processadores: o exemplo do Arduino Uno R3

Os registradores são pequenas unidades de memória localizadas dentro do processador, utilizadas para armazenar temporariamente dados, endereços e informações de controle necessárias à execução das instruções.

No Arduino Uno R3, baseado no microcontrolador ATmega328P, os registradores desempenham um papel fundamental na execução dos programas, no processamento de dados e no controle dos periféricos.

Para entender sua função, é importante distinguir três elementos: os registradores internos da CPU, a memória RAM e os registradores de controle dos periféricos.

2.2.1 Por que os registradores são necessários?

Imagine que o processador precise somar dois números, como 15 e 25.

Uma representação simplificada da operação seria:

  1. Buscar o valor 15 na memória.
  2. Colocar esse valor em um registrador.
  3. Buscar o valor 25.
  4. Colocar esse segundo valor em outro registrador.
  5. Executar a soma na unidade lógica e aritmética (ULA).
  6. Armazenar o resultado, 40, em um registrador.

A CPU utiliza registradores porque eles permitem acessar rapidamente os valores necessários às operações.

Podemos representar o processo assim:

          Memória RAM
          ┌──────────┐
          │ Valor 15 │
          │ Valor 25 │
          └────┬─────┘
               │
               ▼
       ┌─────────────────┐
       │   Registradores │
       │                 │
       │ R1 = 15         │
       │ R2 = 25         │
       └────────┬────────┘
                │
                ▼
       ┌─────────────────┐
       │       ULA       │
       │                 │
       │    15 + 25      │
       └────────┬────────┘
                │
                ▼
       ┌─────────────────┐
       │   Registrador   │
       │                 │
       │    R3 = 40      │
       └─────────────────┘

O desenho é conceitual: a arquitetura AVR possui um conjunto de registradores de trabalho diretamente conectado à CPU, permitindo executar muitas operações sem acessar a SRAM.

A ideia central é que os registradores funcionam como áreas de trabalho imediatas do processador.


2.2.2 Os registradores da CPU do ATmega328P

O ATmega328P possui uma arquitetura AVR RISC de 8 bits e um conjunto de 32 registradores de uso geral, cada um com 8 bits.

Eles são denominados R0 a R31.

Cada registrador pode armazenar um valor entre 0 e 255, quando interpretado como um número inteiro sem sinal.

Registrador Tamanho Função principal
R0 8 bits Registrador de uso geral; também pode servir como temporário em operações específicas
R1 8 bits Uso geral; frequentemente mantido zerado pelo compilador AVR-GCC
R2 a R25 8 bits cada Armazenamento temporário de dados e operandos
R26 a R27 16 bits em conjunto Podem formar o ponteiro X
R28 a R29 16 bits em conjunto Podem formar o ponteiro Y
R30 a R31 16 bits em conjunto Podem formar o ponteiro Z

Os registradores R26 a R31 também podem ser utilizados individualmente como registradores de 8 bits. Quando combinados em pares, formam três ponteiros de 16 bits:

  • X: formado por R27:R26.
  • Y: formado por R29:R28.
  • Z: formado por R31:R30.

Esses ponteiros permitem acessar posições de memória, percorrer vetores e manipular dados armazenados em diferentes regiões de memória.

Por exemplo, o ponteiro X pode representar um endereço de memória de 16 bits. Isso não significa que toda a memória do microcontrolador tenha 64 KB disponíveis para dados: cada espaço de memória possui seus próprios limites e regras de endereçamento.


2.2.3 O registrador de estado: SREG

Além dos registradores de uso geral, o ATmega328P possui registradores especiais utilizados para controlar o funcionamento da CPU.

Um dos mais importantes é o SREG (Status Register), o registrador de estado.

Ele contém oito bits que representam condições produzidas pelas operações aritméticas e lógicas, além de controlar a habilitação global das interrupções.

Bit Nome Função
I Global Interrupt Enable Habilita globalmente as interrupções
T Bit Copy Storage Armazena temporariamente um bit para operações específicas
H Half Carry Flag Indica transporte entre determinados bits em operações aritméticas
S Sign Flag Representa o sinal calculado a partir de N e V
V Two’s Complement Overflow Indica transbordamento em operações com sinal
N Negative Flag Indica resultado negativo na interpretação aritmética com sinal
Z Zero Flag Indica resultado igual a zero
C Carry Flag Indica transporte ou empréstimo, conforme a operação

Exemplo: a comparação de dois números

Imagine uma operação equivalente a:

if (a == b) {
    // Executa alguma ação
}

O compilador pode utilizar uma instrução de comparação que atualiza os bits de estado da CPU.

Se os valores forem iguais, a comparação indicará igualdade, normalmente por meio da ativação da flag Z.

Uma instrução de desvio condicional poderá utilizar esse estado para decidir se o programa deve executar determinado trecho.

Portanto, o SREG ajuda a CPU a tomar decisões com base nos resultados das operações anteriores.


2.2.4 O registrador PC: Program Counter

O PC (Program Counter) é o contador de programa.

Sua função é indicar o endereço da próxima instrução a ser buscada na memória de programa.

Imagine um programa com as seguintes instruções:

Endereço      Instrução
0x0000        Carregar um valor
0x0002        Somar outro valor
0x0004        Comparar o resultado
0x0006        Desviar se igual

Os endereços acima são apenas ilustrativos.

Durante a execução, o PC acompanha a sequência de instruções. Depois de uma instrução, ele normalmente avança para a próxima. Em desvios e chamadas de sub-rotinas, seu valor pode ser alterado para apontar para outro endereço.

No ATmega328P, as instruções são armazenadas na Flash e podem ter 16 ou 32 bits. Por isso, não é correto presumir que o contador sempre avance exatamente uma posição de byte após cada instrução.

O PC é essencial para que o processador saiba onde está o próximo comando do programa.


2.2.5 O registrador SP: Stack Pointer

O SP (Stack Pointer) é o ponteiro da pilha.

A pilha é uma região de memória utilizada para armazenar informações temporárias relacionadas à execução do programa.

Entre suas aplicações estão:

  • Guardar endereços de retorno de chamadas de funções.
  • Preservar registradores durante determinadas operações.
  • Armazenar variáveis locais, quando o compilador utiliza a pilha para esse fim.
  • Apoiar o tratamento de interrupções.

Considere o seguinte programa:

void calcular() {
    int resultado = 10 + 20;
}

void loop() {
    calcular();
}

Quando loop() chama calcular(), o programa precisa preservar informações para retornar ao ponto correto depois da execução da função.

A pilha é utilizada nesse processo, e o SP indica a posição atual do topo da pilha.

No ATmega328P, a pilha fica na SRAM. O ponteiro é inicializado pelo ambiente de execução e atualizado conforme os dados são inseridos ou retirados.

Como a SRAM do ATmega328P possui apenas 2 KB, o uso excessivo da pilha pode consumir a memória disponível e causar falhas de execução.


2.2.6 Registradores de controle dos periféricos

Até agora, consideramos principalmente os registradores que auxiliam a CPU a executar instruções.

Entretanto, o ATmega328P possui outra categoria fundamental: os registradores de E/S e de controle dos periféricos.

Eles permitem que o programa configure e controle recursos internos do microcontrolador, como portas digitais, temporizadores, conversores analógico-digitais e interfaces de comunicação.

Exemplo: controlar um pino digital

No Arduino Uno R3, o pino digital 13 está associado ao bit 5 da porta B, identificado como PB5.

O controle dessa porta envolve três registradores principais:

Registrador Função
DDRB Define se cada pino da porta B será entrada ou saída
PORTB Quando configurado como saída, determina o nível lógico de cada pino
PINB Permite ler o estado lógico presente nos pinos

Por exemplo, o trecho abaixo configura PB5 como saída e coloca esse pino em nível alto:

DDRB |= (1 << DDB5);
PORTB |= (1 << PORTB5);

A primeira instrução configura o bit correspondente em DDRB, definindo PB5 como saída.

A segunda configura o bit correspondente em PORTB, colocando a saída em nível alto.

No Arduino Uno R3 clássico, PB5 corresponde ao pino digital 13, que também está conectado ao LED integrado à placa.

Essas instruções acessam diretamente os registradores do microcontrolador, sem utilizar as funções de alto nível pinMode() e digitalWrite().

Isso ilustra uma característica importante: os registradores permitem controlar o hardware de forma direta.

Como isso funciona internamente?

Quando o programa modifica o bit de um registrador de controle, o circuito eletrônico associado ao periférico responde à configuração.

No exemplo anterior, a alteração de DDRB configura o circuito de entrada e saída do pino. A alteração de PORTB determina o nível lógico de saída.

Assim, os registradores funcionam como uma interface entre o software executado pela CPU e os circuitos físicos do microcontrolador.


2.2.7 Como os registradores participam da execução de um programa?

Podemos resumir o funcionamento da CPU em uma sequência simplificada:

  1. Busca: o PC indica a próxima instrução na memória de programa.
  2. Decodificação: a unidade de controle interpreta a instrução.
  3. Leitura dos operandos: os dados necessários são obtidos dos registradores ou da memória.
  4. Execução: a ULA realiza a operação aritmética ou lógica, quando aplicável.
  5. Atualização: o resultado é colocado em um registrador ou armazenado na memória.
  6. Atualização do estado: flags como Z e C podem ser modificadas.
  7. Continuação: o PC é atualizado para a próxima instrução, salvo quando ocorre um desvio, uma chamada ou outro evento que altere o fluxo de execução.

Os registradores são fundamentais para a execução eficiente dos programas e para o controle do hardware.

Podemos agrupá-los em três categorias principais:

  • Registradores de uso geral: armazenam operandos e resultados das operações da CPU.
  • Registradores especiais da CPU: controlam o fluxo de execução, a pilha e as condições produzidas pelas operações.
  • Registradores de controle dos periféricos: permitem configurar e operar portas digitais, temporizadores, conversores e interfaces de comunicação.

A principal ideia é que os registradores não são apenas pequenas memórias: eles fazem parte da organização funcional do processador e estabelecem a ligação entre a execução das instruções e o comportamento do hardware.

2.2.8 Comparação entre funções Arduino e acesso direto aos registradores

No Arduino Uno R3, o pino digital 13 está associado ao PB5 (Port B, bit 5) do microcontrolador ATmega328P. Esse pino também está conectado ao LED integrado à placa.

Podemos programá-lo para alternar entre os estados HIGH e LOW a cada segundo utilizando duas abordagens:

  1. Funções de alto nível: pinMode() e digitalWrite().
  2. Acesso direto aos registradores: DDRB e PORTB.

As duas abordagens produzem o mesmo resultado físico, mas diferem na forma como o programa interage com o hardware.

Primeira abordagem: funções do Arduino

As funções pinMode() e digitalWrite() fazem parte da API do ambiente Arduino, simplificando o acesso aos pinos digitais.

O exemplo abaixo configura o pino 13 como saída e alterna seu estado a cada segundo.

void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13, HIGH);
  delay(1000);

  digitalWrite(13, LOW);
  delay(1000);
}

Como funciona?

  • pinMode(13, OUTPUT): configura o pino 13 como saída.
  • digitalWrite(13, HIGH): coloca o pino em nível lógico alto.
  • delay(1000): aguarda 1.000 milissegundos, ou seja, um segundo.
  • digitalWrite(13, LOW): coloca o pino em nível lógico baixo.

O LED permanece aceso por um segundo e apagado por outro segundo, repetidamente.

Segunda abordagem: acesso direto aos registradores

O ATmega328P possui registradores que permitem configurar diretamente as portas de entrada e saída.

Para controlar o pino 13, utilizamos dois registradores:

Registrador Função
DDRB Configura a direção dos pinos da porta B: entrada ou saída.
PORTB Define o nível lógico de saída dos pinos da porta B.

O pino 13 corresponde ao bit 5 da porta B, identificado como PB5.

Código equivalente
void setup() {
  DDRB |= (1 << DDB5);
}

void loop() {
  PORTB |= (1 << PORTB5);
  delay(1000);

  PORTB &= ~(1 << PORTB5);
  delay(1000);
}
Como funciona?

1. Configuração do pino como saída

DDRB |= (1 << DDB5);

A expressão 1 << DDB5 desloca o bit 1 cinco posições para a esquerda, produzindo o valor binário:

00000001 << 5 = 00100000

O operador |= realiza uma operação OR bit a bit entre o conteúdo de DDRB e esse valor. Assim, o bit 5 passa a valer 1, configurando PB5 como saída, sem alterar os demais bits do registrador.

2. Colocação do pino em nível alto

PORTB |= (1 << PORTB5);

Define o bit 5 de PORTB como 1, colocando a saída PB5 em nível lógico alto.

3. Colocação do pino em nível baixo

PORTB &= ~(1 << PORTB5);

Essa instrução utiliza uma máscara para zerar apenas o bit 5 de PORTB, preservando os demais bits.

O resultado é funcionalmente equivalente ao uso de digitalWrite(13, HIGH) e digitalWrite(13, LOW), nas condições apresentadas.

Comparação entre as abordagens

Característica pinMode() e digitalWrite() Acesso direto aos registradores
Configuração do pino pinMode(13, OUTPUT) DDRB |= (1 << DDB5)
Nível alto digitalWrite(13, HIGH) PORTB |= (1 << PORTB5)
Nível baixo digitalWrite(13, LOW) PORTB &= ~(1 << PORTB5)
Facilidade de leitura Maior para iniciantes Exige conhecimento da arquitetura
Portabilidade Maior entre placas Arduino compatíveis Dependente do microcontrolador
Controle do hardware Indireto, por meio da API Direto, por meio dos registradores
Desempenho das operações de E/S Geralmente menor Geralmente maior

Qual abordagem é mais rápida?

O acesso direto aos registradores tende a exigir menos instruções do que as funções genéricas digitalWrite() e pinMode(), pois evita parte do processamento necessário para identificar e configurar o pino.

Entretanto, neste exemplo, o intervalo de um segundo é muito maior do que o tempo gasto para alterar o estado do pino. Portanto, a diferença de desempenho praticamente não altera o comportamento visível do LED.

O ganho torna-se mais relevante em aplicações que precisam gerar sinais rapidamente, controlar temporizações precisas ou executar muitas operações de E/S em sequência.

Depois, coloque o código correspondente sob cada subtítulo, utilizando blocos de código C++ delimitados por três crases.

Conclusão: as funções pinMode() e digitalWrite() facilitam o desenvolvimento e tornam o código mais legível. Já o acesso direto a DDRB e PORTB permite compreender melhor a arquitetura do ATmega328P e controlar o hardware com menor sobrecarga de software.