Programando AVR na IDE e placa Arduino - PWM


Nas duas primeiras postagens sobre AVR mostrei configurações de portas e timer trabalhando no modo CTC, nesta postagem mostrarei o timer trabalhando como PWM (Pulse-width modulation). O PWM é amplamente usado em micro controladores para controle de motores, lampadas, temperatura e inúmeros dispositivos.

Se você chegou aqui direto, é aconselhável ler o post sobre portas primeiro e depois o post sobre timers, para entender como fazer o controle de registradores e como timers trabalham.

O trabalho do PWM como o próprio nome diz é controlar a largura de pulso, com isso a porta passará a ter um controle de potência. Na figura abaixo por exemplo, para a saída da porta em 5V a primeira linha entregaria 2.5V a segunda 3.75V e a terceira 1.25V

Lembrando que no PWM do ATMega 328P já existe as portas padrões para se trabalhar, para o Timer 0 vamos trabalhar na porta Arduino 6 que equivale ao PD6 (OC0A). Existe dois modos de trabalho em PWM para o Timer 0, que são Fast PWM e Phase Correct PWM Mode. As diferenças são que no Fast PWM quando o contator TCNT0 inicia a contagem, a porta é colocada em nível alto até chegar ao valor setado em OCR0A, neste momento a porta vai para nível baixo e permanece até o contador TCNT0 chegar em 255 e voltar a 0, onde inicia novamente o processo. No modo Phase Correct PWM Mode o contador TCNT0 não volta a 0 quando chega em 255 e sim decrementa esse valor até 0. Nas páginas 108 e 110 do datasheet tem detalhado esses modos com gráficos para melhor entendimento.

Para o exemplo vamos precisar de um hardware adicional além do Arduino Uno, um led e resistor conforme abaixo:

O código de exemplo será este:

int main(void) {
  DDRD |= (1 << DDD6); // PD6 (Arduino Pin 6)

  TCCR0A |= (1 << COM0A1) |              // Seta na comparação
            (1 << WGM01) | (1 << WGM00); // Modo Fast PWM

  TCCR0B |= (1 << CS01) | (1 << CS00);  // Prescaler 64

  OCR0A = 125; // Valor baixo largura de pulso pequena
               // Valor alto largura de pulso grande

  while (1) {} //Loop infinito

  return 0;
}
Para o modo Fast PWM a frequência é definida apenas pelo prescaler onde setado como 64 teremos:

16000000 / (64*256) = 976.56hz

Valor próximo de 1Khz (1ms) mas não exato. Detalhes da equação estão nas paginas 108 e 109 do datasheet.
 
No código acima temos TCCR0A setado com o bit COM0A1 conforme pagina 113, tabela 15.3 (Clear OC0A on Compare Match) do datasheet, se for setado também o bit COM0A1 o sinal PWM ficará invertido. Os bits WGM00 e WGM01 definem o modo Fast PWM. 

Com o valor de OCR0A em 125 o sinal fica em 50% positivo e 50% negativo conforme abaixo:


Com o valor de OCR0A em 50 teremos um pulso positivo de largura pequena conforme abaixo:


Com o valor de OCR0A em 200 teremos um pulso positivo de largura grande conforme abaixo:


Com o led ligado na porta 6 será possível ver a variação de brilho de acordo com o valor setado em OCR0A.

Abaixo segue um código extra porém do Timer 1, que permite em um modo de operação definir o valor da frequência que será utilizada com precisão (1khz/1ms neste caso), usando o registrador ICR1. O modo de operação é o Fast PWM modo 14 de acordo com a tabela 16.4 da página 141. A alteração do ciclo de trabalho continua no registrador OCRnx que neste caso é OCR1A e a porta utilizada é a Arduino 9 que corresponde ao PB1 (OC1A).
int main(void) {
  DDRB |= (1 << DDB1); // PB1 (Arduino Pin 9)

  TCCR1A = 0; // Inicia o registrador com valores em 0
  TCCR1B = 0; // Inicia o registrador com valores em 0
TCCR1A |= (1 << COM1A1) | // Seta na comparação (1 << WGM11); // Fast PWM (Modo 14) TCCR1B |= (1 << CS10) | (1 << CS11) | // Prescaler 64 (1 << WGM13) | (1 << WGM12); // Fast PWM (Modo 14) ICR1 = 250 ; // 16000000 / (64*250) = 1000hz OCR1A = 125; while (1) {} // Loop infinito return 0; }
O registrador ICR1 para o micro controlador ATMega 328P só está disponível no Timer 1 e tem outra função que é capturar um sinal de entrada na porta PB0 através de interrupção, assunto talvez para outra postagem.

Referência:
Códigos de exemplo do livro AVR Programming:

Comentários