Memória Flash do AVR ATmega328P
Valmor

Memória Flash do AVR ATmega328P

avrmicrocontroladormemóriaprogramação

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:

  1. O contador de programa (PC) aponta para a próxima instrução a ser executada na memória Flash.
  2. Essa instrução é carregada no registrador de instruções.
  3. 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:

  1. 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.
  2. 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:

  1. Selecione o modo PWM com correção de fase (Phase Correct) ajustando o Waveform Generation Mode (WGM) nos bits do TCCR1A e TCCR1B.
  2. Escolha uma frequência adequada definindo o valor de ICR1 e o prescaler no TCCR1B.
  3. Ajuste o duty cycle alterando os valores dos registradores OCR1A e/ou OCR1B.
  4. 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.

PINFunçãoObs
D9OC1APWM Canal A
D10OC1BPWM Canal B

Instruções gerais:

  1. Ajuste o fuse do microcontrolador para habilitar o bootloader, caso vá utilizar programação via serial.
  2. 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;
}