Programando AVR na IDE e placa Arduino - Timers

 

Na primeira postagem sobre programação AVR mostrei como os registradores trabalham e como setar portas. Nesta postagem irei mostrar como os Timers trabalham, Timers são o coração dos micro controladores, com eles é possível ter controle de tempo e controlar portas PWM o que gera uma imensa flexibilidade de trabalho

Se você chegou aqui direto, é aconselhável ler o post sobre portas primeiro para entender como fazer o controle de registradores.
O ATMega 328P possui três timers, dois de 8 bits e um de 16 bits, vou mostrar aqui o funcionamento do de 8 bits por ser mais simples e entendendo ele é facilmente entendido os outros.

Não pretendo detalhar muito para não confundir principalmente para quem está começando e não tem conhecimento algum. Mas existe dois modos básicos de operar o timer, o CTC (Clear Timer on Compare Match) e o PWM (Pulse-width modulation). No modo CTC o ciclo de trabalho nos pulsos possuem o mesmo tempo tanto em nível alto quanto em nível baixo, já no PWM é possível ter tempos diferentes entre esses pulsos. Mas como a flexibilidade é grande, é possível fazer muito mais que isso via software e manipulando interrupções.

Para entender um pouco melhor, na figura abaixo é como se o primeiro sinal fosse gerado por um CTC ou PWM e os dois seguinte somente pelo PWM.


O funcionamento do timer é simples entender, existe um contator (8 bits para o Timer0) que logicamente vai de 0 a 255. Para o CTC, de acordo com o valor setado, é alterada a frequência de operação, menor numero maior frequência e maior numero menor frequência, conforme equação abaixo:



Para PWM o valor de frequência fica fixo, a alteração do valor setado fica apenas no ciclo de trabalho, a equação para encontrar a frequência é:

Fclk_I/O é a frequência de operação do micro controlador, no caso do Arduino Uno 16Mhz, N é o prescaler usado para conseguir trabalhar com frequências mais baixas, ele pega a frequência de operação do micro controlador e divide por valores pré estabelecidos conforme tabela abaixo. Esses valores serão setados via registradores.


O OCRnx é o registrador que no CTC será setado para definir a frequência e no PWM será setado para definir o ciclo de trabalho.

Começando pelo CTC, precisamos setar registradores para configurar a porta de saída, para o prescaler, para o modo de trabalho e interrupção que não é obrigatório neste caso, mas será útil para fazer o led piscar.

Primeiro é preciso definir a base de tempo que vamos trabalhar pelo prescaler, para ficar fácil vamos trabalhar com 1ms. Usando a equação acima e invertendo Fclk_I/O com OCRnx, temos o resultado abaixo, lembrando que para 1ms a frequência é 1khz:

16000000 / (2*64) / 1000 = 125

125 será o valor que vamos setar em OCRnx (OCR0A) para conseguirmos a base de tempo em 1 ms. Porém no CTC essa base vai da subida do pulso até a próxima subida do pulso conforme abaixo:


Como será usado uma interrupção para piscar o led e ela é acionada a cada mudança de estado, subida e descida do pulso, fica mais fácil usar o valor de 250 que dará exato 1ms por pulso conforme abaixo:


As porta padrão para CTC e PWM do timer 0 são Arduino 5 e 6 que equivalem ao PD5 (OC0B) e PD6 (OC0A), vamos trabalhar apenas na PD6 (OC0A). Na pagina 12 do datasheet do ATMega 328P é possível entender melhor essas pinagens e na pagina 107 os modos de operação e as portas que são utilizadas. Na pagina 117 item 15.9.4 é detalhado o registrador OCR0A.

Vamos setar uma porta para o led também conforme abaixo:
  DDRB |= (1 << DDB5); // PB5 usado para o led
  DDRD |= (1 << DDD6); // PD6 (OC0A) Arduino 6
No registrador TCCR0A, pagina 113 do datasheet, precisamos setar o CTC, para isso é setado apenas o bit WGM01 conforme tabela 15.8 da pagina 115. Precisamos definir como a porta PD6 (OC0A) vai trabalhar e neste caso para CTC, vamos usar o modo alternando a cada comparação. Na pagina 113 tabela 15.2, podemos ver que é preciso setar o bit COM0A0, então temos os dois bits setados conforme abaixo:
  TCCR0A |= (1 << COM0A0) | // Alterna OC0A na comparação
            (1 << WGM01);   // Habilita forma de onda CTC
No registrador TCCR0B pagina 116 do datasheet, vamos setar o prescaler para 64 e podemos ver na pagina 117 tabela 15.9, que é preciso setar os bits CS00 e CS01 conforme abaixo:
  TCCR0B |= (1 << CS00) |   // Prescaler 64
            (1 << CS01);    // Prescaler 64
Por fim como vamos utilizar uma interrupção, precisamos setar o registrador TIMSK0 conforme pagina 118, setando o bit OCIE0A.
  TIMSK0 |= (1 << OCIE0A);  // Habilita a interrupção setando OCIE0A
A interrupção que será utilizada é a TIMER0_COMPA_vect. A pagina 66 detalha melhor essas interrupções, mas o que essa faz é toda vez que o contador do TCTN0 chegar no valor definido em OCR0A , a interrupção é acionada, por isso o nome comparação.

Basicamente interrupções quando são acionadas param o processo que o micro controlador estava rodando, desviam para interrupção e quando o código da interrupção termina volta ao ponto onde parou o processo.

O código final fica conforme abaixo:
//Interrupção por comparação
ISR(TIMER0_COMPA_vect) {
    static int t = 0;
    t++;
    if (t == 1000) {
      PORTB ^= (1 << PB5);
      t = 0;
    }
}

int main() {

  DDRB |= (1 << DDB5); // PB5 usado para o led
  DDRD |= (1 << DDD6); // PD6 (OC0A) Arduino 6

  cli(); //Desabilita interrupções
  TCCR0B |= (1 << CS00) |   // Prescaler 64
            (1 << CS01);    // Prescaler 64

  TCCR0A |= (1 << COM0A0) | // Alterna OC0A na comparação
            (1 << WGM01);   // Habilita forma de onda CTC
TIMSK0 |= (1 << OCIE0A); // Habilita a interrupção setando OCIE0A sei(); OCR0A = 250; // comparador com TCNT while (1) { } // Loop infinito
return 0; }

Onde além do que já foi explicado, temos cli() e sei() que limpa (clear) e habilita (set) a interrupção, o valor definido em  OCR0A que vai fazer a comparação com o contador TCNT0 e um código dentro da interrupção que vai somar 1 na variável "t" toda vez que a interrupção for chamada.

Detalhes do contador TCNT0 pode ser encontrado na pagina 103 (15.4) do datasheet, o valor OCR0A compara a subida do contador e quando o valor neste caso chega em 250 a porta de saída PD6 (OC0A) é acionada e a interrupção executada.

Na interrupção, após a soma da variável "t" atingir 1000 a porta PB5 (Arduino 13) muda de estado. Onde 1000 * 1ms (cada chamada da interrupção) = 1s, com isso o led irá piscar a cada 1seg. A definição static na varável é para ela não ser destruída a cada saída da função da interrupção.

Na porta Arduino 5 (PD6 (OC0A)) existirá os pulsos a cada 1ms, sendo possível verificar com um osciloscópio ou analisador lógico.

Comentários