Memória Flash do AVR ATmega328P
https://www.weigu.lu/tutorials/microcontroller/04_memory/index.html https://mundoprojetado.com.br/arquitetura-de-um-microprocessador/ https://mundoprojetado.com.br/arquitetura-de-um-microcontrolador/
Bootloader Na aula passada, vimos que a memória de programa do AVR possui um espaço chamado “Boot” conforme a imagem abaixo.
Alguns PICs também possuem um espaço de Boot.
Mapa da memória flash do AVR O boot nada mais é do que um espaço de memória que é executado logo quando o microcontrolador é energizado. Ou seja, é possível criar um firmware “adicional” para ser armazenado neste espaço de memória. E um código criado neste espaço é chamado de bootloader.
Um exemplo de bootloader é o do Arduino, que basicamente é um algoritmo feito para gravar a memória de programa a partir de comandos na entrada UART do microcontrolador. Acontece que, por padrão, é necessário utilizar os pinos do periférico SPI do AVR para programá-lo. E o bootloader permite que esta gravação seja feita de uma forma mais prática (pela UART).
Este é o motivo de ser interessante existir o boot, já que, neste caso, não precisamos nos preocupar com ele toda vez que formos programar o Arduino. Isto porque, uma vez gravado, o bootloader permanece no espaço de memória dele sem que o programa principal o apague.
1. Introdução
Qual é o problema?
O tema abordado envolve a forma como o ATmega328P, um microcontrolador da família AVR, armazena e executa programas na memória Flash. É importante entender como se dá o processo de programação e armazenamento do código para que seja possível configurar corretamente o dispositivo, especialmente em tarefas como a de gerar PWM (Pulse Width Modulation).
Por que é importante?
A compreensão da arquitetura de memória Flash permite um uso otimizado do dispositivo, evitando sobrecarga de memória e garantindo eficiência na execução de instruções. Além disso, a configuração de PWM em modo de correção de fase é amplamente utilizada em aplicações de controle de motores, LEDs e diversos outros componentes que necessitam de ajustes finos de frequência e duty cycle.
Resumo do que será discutido
- Organização e funcionamento da memória Flash no ATmega328P.
- Papel do bootloader no processo de programação.
- Sequência de leitura e execução das instruções armazenadas na Flash.
- Configuração do Timer/Counter para geração de PWM em modo de correção de fase.
- Exemplo de código didático e conclusões sobre boas práticas de configuração.
2. Teoria Relevante
2.1 Memória Flash no ATmega328P
A memória Flash do ATmega328P é não-volátil, mantendo o conteúdo mesmo na ausência de alimentação elétrica. Ela é responsável por armazenar todo o programa que será executado pelo microcontrolador. As principais características incluem:
- Capacidade total de 32 KB (onde 512 bytes podem ser reservados ao bootloader).
- Possibilidade de gravação por meio de protocolos seriais, como SPI ou via interface UART quando se utiliza o bootloader pré-instalado.
- Ciclo de vida típico de milhares de escritas e leituras.
2.2 O Bootloader
O bootloader ocupa uma pequena parcela dessa memória Flash (por padrão, 512 bytes) e é responsável por gerenciar a comunicação entre um dispositivo externo (por exemplo, um computador) e o microcontrolador. Ele possibilita a gravação do programa na Flash sem a necessidade de um programador dedicado (ISP, por exemplo).
2.3 Execução das Instruções
Quando o microcontrolador está em operação:
- O contador de programa (PC) aponta para a próxima instrução a ser executada na memória Flash.
- Essa instrução é carregada no registrador de instruções.
- A Unidade de Controle decodifica e executa a instrução, movendo dados entre registradores ou entre registradores e memória, conforme o caso.
2.4 PWM com Correção de Fase
O PWM em modo de correção de fase (Phase Correct PWM) baseia-se no incremento e decremento do timer, resultando em uma frequência de saída com ruído menor e maior estabilidade na geração de sinais de controle. Para configurá-lo no ATmega328P, devemos:
-
Configurar os registradores de controle Timer/Counter:
- TCCR1A: Seleciona o modo PWM com correção de fase.
- TCCR1B: Define o prescaler e complementa a configuração do modo.
- ICR1: Define o período do PWM (valores de contagem para o timer).
- OCR1A/B: Controla o duty cycle para os canais A e B, respectivamente.
-
Habilitar interrupções (caso necessário), ajustando os bits apropriados nos registradores de interrupção.
3. Estratégia de Solução
Para que o PWM funcione corretamente:
- Selecione o modo PWM com correção de fase (Phase Correct) ajustando o Waveform Generation Mode (WGM) nos bits do TCCR1A e TCCR1B.
- Escolha uma frequência adequada definindo o valor de
ICR1e o prescaler no TCCR1B. - Ajuste o duty cycle alterando os valores dos registradores
OCR1Ae/ouOCR1B. - Verifique se há necessidade de habilitar interrupções de overflow ou captura para fins de controle mais elaborado.
4. Implementação: Exemplo de Configuração
O código abaixo, escrito como comentário de bloco em Python, ilustra de forma didática como configurar o modo PWM com correção de fase no ATmega328P. Trata-se de um exemplo em C comentado, pois esta é a linguagem mais comum para microcontroladores AVR.
Exemplo de configuração de PWM em modo de Correção de Fase no ATmega328P, utilizando o Timer/Counter1.
Para compilar, usualmente usa-se a ferramenta AVR-GCC junto com uma placa Arduino Uno ou similar.
| PIN | Função | Obs |
|---|---|---|
| D9 | OC1A | PWM Canal A |
| D10 | OC1B | PWM Canal B |
Instruções gerais:
- Ajuste o fuse do microcontrolador para habilitar o bootloader, caso vá utilizar programação via serial.
- Carregue o arquivo .hex gerado após a compilação utilizando o avrdude ou a IDE Arduino.
Código C para configuração do PWM:
#include <avr/io.h>
int main(void) {
// 1. Configura os pinos OC1A e OC1B como saída
DDRB |= (1 << PB1) | (1 << PB2);
// PB1 (Arduino D9) -> OC1A
// PB2 (Arduino D10) -> OC1B
// 2. Configura o modo PWM Phase Correct usando ICR1 como TOP
// WGM13:0 = 1010 (WGM13=1, WGM12=0, WGM11=1, WGM10=0)
// TCCR1A = (1 << WGM11)
// TCCR1B = (1 << WGM13) | (1 << CS11)
// Neste exemplo, usamos prescaler = 8 (CS11=1).
TCCR1A = (1 << WGM11); // WGM11=1, WGM10=0
TCCR1B = (1 << WGM13) | (1 << CS11); // WGM13=1, WGM12=0, prescaler=8
// 3. Define ICR1 como TOP -> define o período
// Se o clock do MCU é 16 MHz e prescaler=8,
// timer clock = 16 MHz / 8 = 2 MHz.
// Para um período de 50µs (20 kHz), ICR1 deve ser 100 (pois 2 MHz * 50e-6 = 100).
ICR1 = 100;
// 4. Define o duty cycle em OCR1A e OCR1B
// Exemplo: 50% de duty cycle -> OCR1A = 50
OCR1A = 50;
// 30% de duty cycle -> OCR1B = 30
OCR1B = 30;
// 5. Define o modo de saída no compare match
// Para saída em modo não invertido -> COM1A1=1 e COM1A0=0
// COM1B1=1 e COM1B0=0
// TCCR1A |= (1 << COM1A1) | (1 << COM1B1);
TCCR1A |= (1 << COM1A1) | (1 << COM1B1);
// 6. Loop principal (opcional)
while(1) {
// Nesse exemplo, apenas geramos o sinal PWM;
// Ajustes dinamicamente podem ser feitos alterando OCR1A e OCR1B.
}
return 0;
}