Programando AVR na IDE e placa Arduino - ADC

Nas postagens anteriores mostrei como configurar portas, como os Timers trabalham gerando pulsos e PWM. Neste agora vou mostrar como o ADC (Analog to Digital Converter) trabalha e colocar ele para funcionar em conjunto com um PWM. Pegando um sinal analógico em um porta e controlando o brilho de um LED.

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

Para os exemplos será necessário além do Arduino Uno um hardware adicional, composto de um resistor (330 ohms), um LED e um potenciômetro podendo ser de 5k a 100k ohms.


Existe dois modos de se trabalhar com ADC no ATMega 328P, Single Mode e Free Running. Em Single Mode é necessário via código inicializar a conversão manualmente cada vez que for necessário converter o valor analógico. Já no Free Running ele passa a converter interruptamente sempre iniciando uma assim que a anterior terminar.

Para trabalhar em Single Mode, primeiro o registrador a ser setado é o ADMUX, detalhado na pagina 257 do datasheet. Nele precisamos setar como será a tensão de referência do ADC, mais detalhes sobre essa tensão está na página 257 do datasheet. O hardware do Arduino é construído com um capacitor de 100nF no pino AREF conforme figura abaixo:



A página 257 do datasheet , tabela 24.3 mostra que para este caso é necessário setar o bit REFS0 do registrador ADMUX:


Além desse bit, precisamos setar qual porta analógica vamos trabalhar, a pagina 258 do datasheet, tabela 24.4 tem a configuração binária dos bits do registrador, chamados de canais:


Vamos trabalhar com a porta ADC1 que na pinagem do Arduino corresponde ao pino A1, na pinagem AVR ao pino PC1. Os pinos PC0 ao PC5 são todos ADC. Sendo necessário setar o bit MUX0 do registrador ADMUX para trabalhar com a porta PC1.


Assim a configuração do registrador ADMUX fica como abaixo:
ADMUX  |= (1 << REFS0) |   // AVCC com capacitor externo AREF pin
          (1 << MUX0);     // Configura o canal ADC1
No registrador ADCSRA detalhado na pagina 258 do datasheet vamos setar o bit ADEN para habilitar o ADC e os bits ADPSx para o prescaler.  O prescaler é necessário para utilizar o clock do micro controlador como clock do ADC. No caso vamos usar um prescaler 64 que irá trabalhar em 250khz (16mhz / 64) conforme tabela abaixo. Detalhes sobre o prescaler pode ser encontrado na pagina 249 do datasheet.


Assim a configuração do registrador ADCSRA fica como abaixo:
ADCSRA |= (1 << ADEN)  | // Habilita DAC
          (1 << ADPS2) | // Define a taxa de conversão do ADC para prescaler 64 (16MHz / 64 = 250kHz)
          (1 << ADPS1);  // Define a taxa de conversão do ADC para prescaler 64 (16MHz / 64 = 250kHz)
No loop infinito precisamos inicializar a conversão a cada ciclo usando o bit ADSC do registrador ADCSRA
ADCSRA |= (1 << ADSC);   // Inicializa conversão
No registrador ADC teremos o valor da conversão em 10 bits, como cada registrador do AT 328P possui 8 bits, o ADC fica composto pelo ADCL e ADCH. Esses registradores estão detalhados na pagina 259 do datasheet (24.9.3 ADCL and ADCH – The ADC Data Register).

As configurações do PWM para gerar o controle de brilho do led já foram detalhadas na postagem anterior e o valor de OCR0A será alimentado com o valor do registrador ADC dividido por 4, já que ADC trabalha em 10 bits (0-1023) e OCR0A em 8 bits (0-255).

O código para Single Mode fica como abaixo:
int main(void) {

  /// *** Config PWM *** ///
  DDRD |= (1 << DDD6);  // Habilita porta PD6 (Arduino pin6) para PWM

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

  TCCR0A |= (1 << COM0A1) |  // Limpa OC0A na comparação OCR0A
            (1 << WGM00)  |  // Habilita waveform para fast PWM
            (1 << WGM01);    // Habilita waveform para fast PWM

  /// *** Config ADC *** ///
  ADMUX  |= (1 << REFS0) |   // AVCC com capacitor externo AREF pin (padrão Arduino)
            (1 << MUX0);     // Configura o canal ADC1
  ADCSRA |= (1 << ADEN)  |   // Habilita DAC
            (1 << ADPS2) |   // Define a taxa de conversão do ADC para prescaler 64 (16MHz / 64 = 250kHz)
            (1 << ADPS1);    // Define a taxa de conversão do ADC para prescaler 64 (16MHz / 64 = 250kHz)

  while (1) {
    ADCSRA |= (1 << ADSC);   // Inicializa conversão

    OCR0A = (ADC / 4); // Grava o valor para o PWM
  }
  return 0;
}
Para trabalhar em Free Running o código é bem parecido com a diferença que precisamos habilitar o Free Running com o bit ADATE do registrador ADCSRA.
ADCSRA |= (1 << ADATE) | // Habilita auto-trigger (Free Running)
Não é preciso mais deixar a inicialização da conversão (bit ADSC), dentro do loop. Sendo necessário apenas uma inicialização, a partir daí o conversor sempre irá fazer nova conversão após o termino da primeira.

O código para Free Running fica como abaixo:
int main(void) {
  
  /// *** Config PWM *** ///
  DDRD |= (1 << DDD6);  // Habilita porta PD6 (Arduino pin6) para PWM

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

  TCCR0A |= (1 << COM0A1) |  // Limpa OC0A na comparação OCR0A
            (1 << WGM00)  |  // Habilita waveform para fast PWM
            (1 << WGM01);    // Habilita waveform para fast PWM

  /// *** Config ADC *** ///
  ADMUX  |= (1 << REFS0) | // AVCC com capacitor externo AREF pin (padrão Arduino)
            (1 << MUX0);   // Configura o canal ADC1
  ADCSRA |= (1 << ADEN)  | // Habilita DAC
            (1 << ADPS2) | // Define a taxa de conversão do ADC para prescaler 64 (16MHz / 64 = 250kHz)
            (1 << ADPS1) | // Define a taxa de conversão do ADC para prescaler 64 (16MHz / 64 = 250kHz)
            (1 << ADATE) | // Habilita auto-trigger (Free Running)
            (1 << ADSC);   // Inicializa conversão

  while (1) {
    OCR0A = (ADC / 4); // Grava o valor para o PWM
  }
  return 0;
}
Movimentando o potenciômetro o led irá diminuir e aumentar o brilho.

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

Comentários