12 июня 2022 г.
5411

ATmega328 - АЦП

АЦП позволяет считывать аналоговый сигнал и переводить его в цифровой, благодаря этому можно аналоговым сигналом, например от потенциометра, управлять работой микроконтроллера.

    Алгоритм работы АЦП:
  1. Выбор канала для считывания сигнала;
  2. Запуск преобразования (считывания сигнала);
  3. Ожидание пока преобразование закончится;
  4. Запись считанного значения в переменную.

ATmega328 имеет 6 каналов для считывания аналогового сигнала ADC5 (28) - ADC0 (23). А также еще два вывода: на AREF (21) подается опорное напряжение, а на AVCC (20) питание для аналоговой части. Если нужны точные измерения, то к AVCC можно подвести отдельное питание без помех, если точность не критична, то можно подключить к питанию микроконтроллера.

Регистры АЦП

    Настройка и управление АЦП осуществляется с помощью регистров:
  • ADMUX - регистр настройки мультиплексора;
  • ADCSRA - управляющий и статусный регистр;
  • ADCSRB - управляющий регистр;
  • ADCH и ADCL - регистры в которых записывается значение АЦП.

Регистр ADMUX

Включает в себя следующее
Номер бита 7 6 5 4 3 2 1 0
Название REFS1 REFS0 ADLAR - MUX3 MUX2 MUX1 MUX0
REFS1, REFS0 - выбор опорного напряжения
REFS1 REFS0 Описание
0 0 Внешний источник питания на AREF (21), внутренний отключен
0 1 Внешний источник питания на AVCC (20), с внешним конденсатором на AREF (21)
1 0 -
1 1 Внутренний источник опорного напряжения 1,1 В с внешним конденсатором на AREF (21)

ADLAR - коррекция представления в регистре данных. Так как АЦП имеет разрядность 10 бит, то входящее 10-битное значение разбивается на два регистра ADCH и ADCL и в зависимости от значения ADLAR сдвигается. Если в ADLAR записать 1, 8 бит будет в ADCH и 2 в ADCL, если 0 - то 8 бит будет в ADCL и 2 в ADCH. Если не нужна высока точность (больше 8 бит), то можно сделать ADLAR = 1 и взять старшие 8 бит из регистра ADCH.

ADLAR = 1
ADCH 9 8 7 6 5 4 3 2
ADCL 1 0 - - - - - -
ADLAR = 0
ADCH - - - - - - 9 8
ADCL 7 6 5 4 3 2 1 0
MUX3, MUX2, MUX1, MUX0 - выбор аналогового канала. Значение этих битов определяет, какие аналоговые входы подключены к АЦП
MUX3 MUX2 MUX1 MUX0 Описание
0 0 0 0 ADC0
0 0 0 1 ADC1
0 0 1 0 ADC2
0 0 1 1 ADC3
0 1 0 0 ADC4
0 1 0 1 ADC5
1 0 0 0 Temperature sensor

Регистр ADCSRA

Включает в себя следующее
Номер бита 7 6 5 4 3 2 1 0
Название ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0

ADEN - включение/выключение АЦП.

ADSC - запуск преобразования АЦП. В режиме одиночного преобразования нужно записать единицу в этот бит, чтобы начать преобразование. В режиме свободного запуска нужно записать единицу в этот бит, чтобы начать первое преобразование.

ADATE - включение автоматического запуска АЦП. Когда этот бит записывается в единицу, включается автоматическая работа АЦП, то есть значения будут считываться постоянно.

ADIF - флаг прерывания АЦП. Этот бит устанавливается, когда преобразование АЦП завершается и регистры данных обновляются.

ADIE - активация прерывания АЦП.

ADPS2, ADPS1, ADPS0 - выбор предделителя АЦП
ADPS2 ADPS1 ADPS0 Описание
0 0 0 Предделитель 2
0 0 1 Предделитель 2
0 1 0 Предделитель 4
0 1 1 Предделитель 8
1 0 0 Предделитель 16
1 0 1 Предделитель 32
1 1 0 Предделитель 64
1 1 1 Предделитель 128

Регистр ADCSRB

Включает в себя следующее
Номер бита 7 6 5 4 3 2 1 0
Название - ACME - - - ADTS2 ADTS1 ADTS0
ADTS2, ADTS1, ADTS0 - источник автоматического запуска преобразования. Действует, когда в ADATE записана единица.
ADTS2 ADTS1 ADTS0 Описание
0 0 0 Режим свободного запуска
0 0 1 Совпадение по аналоговому компаратору
0 1 0 Внешнее прерывание 0
0 1 1 При совпадении с регистром сравнения A таймера 0
1 0 0 При переполнении таймера 0
1 0 1 При совпадении с регистром сравнения B таймера 1
1 1 0 При переполнении таймера 1
1 1 1 При событии захвата таймера 1

Примеры

Минимальный код для работы АЦП. Опорное напряжение с AREF, аналоговый сигнал считывается с ADC0.
#define F_CPU 16000000UL //частота работы микроконтроллера
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

void adc_init(){
  ADMUX = 0;
  ADCSRA = 0;
  ADCSRB = 0;
  
  ADMUX|= (1 << ADLAR); //значение в ADCH
  ADCSRA|=(1 << ADEN);  //включение АЦП
}

int main(void){
  adc_init();

  while (1){
    ADCSRA |= (1 << ADSC);        //запуск преобразования
    while(ADCSRA & (1 << ADSC));  //проверка, закончилось ли аналого-цифровое преобразование
    value_adc = ADCH;                   //считывание значения в переменную
  }
}
Считывание аналогового сигнала с нескольких каналов и с усреднением значений
#define F_CPU 16000000UL //частота работы микроконтроллера
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t adc_in_0 = 0;
uint8_t adc_average_0 = 0;
uint8_t adc_out_0 = 0;

uint8_t adc_in_1 = 0;
uint8_t adc_average_1 = 0;
uint8_t adc_out_1 = 0;

uint8_t adc_in_2 = 0;
uint8_t adc_average_2 = 0;
uint8_t adc_out_2 = 0;
  
float coef =0.5;
  
  
void adc_init(){
  ADMUX = 0;
  ADCSRA = 0;
  ADCSRB = 0;
  
  ADMUX|= (1 << ADLAR); //значение в ADCH
  ADCSRA|=(1 << ADEN);  //включение АЦП
}

int main(void){
  adc_init();

  while (1){
    //выбор канала ADC0
    ADMUX &= ~(1 << MUX0);
    ADMUX &= ~(1 << MUX1);
    ADMUX &= ~(1 << MUX2);
    ADMUX &= ~(1 << MUX3);
    
    ADCSRA |= (1 << ADSC);        //запуск преобразования
    while(ADCSRA & (1 << ADSC));  //проверка, закончилось ли аналого-цифровое преобразование
    adc_in_0 = ADCH;
    adc_average_0 += (adc_in_0 - adc_average_0) * coef;
    adc_out_0 = adc_average_0;

    //выбор канала ADC1
    ADMUX |= (1 << MUX0);
    ADMUX &= ~(1 << MUX1);
    ADMUX &= ~(1 << MUX2);
    ADMUX &= ~(1 << MUX3);
    
    ADCSRA |= (1 << ADSC);          //запуск преобразования
    while((ADCSRA & (1 << ADSC)));  //проверка, закончилось ли аналого-цифровое преобразование
    adc_in_1 = ADCH;
    adc_average_1 += (adc_in_1 - adc_average_1) * coef;
    adc_out_1 = adc_average_1;

    //выбор канала ADC2
    ADMUX &= ~(1 << MUX0);
    ADMUX |= (1 << MUX1);
    ADMUX &= ~(1 << MUX2);
    ADMUX &= ~(1 << MUX3);
    
    ADCSRA |= (1 << ADSC);        //запуск преобразования
    while(ADCSRA & (1 << ADSC));  //проверка, закончилось ли аналого-цифровое преобразование
    adc_in_2 = ADCH;
    adc_average_2 += (adc_in_2 - adc_average_2) * coef;
    adc_out_2 = adc_average_2;
  }
}
Управление скважностью ШИМ сигнала с помощью потенциометра
#define F_CPU 16000000UL //частота работы микроконтроллера
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

int ctrl = 0;

uint8_t adc_in_0 = 0;
uint8_t adc_average_0 = 0;
uint8_t adc_out_0 = 0;

uint8_t adc_in_1 = 0;
uint8_t adc_average_1 = 0;
uint8_t adc_out_1 = 0;

uint8_t adc_in_2 = 0;
uint8_t adc_average_2 = 0;
uint8_t adc_out_2 = 0;
  
float coef =0.5;
  
  
void adc_init(){
  ADMUX = 0;
  ADCSRA = 0;
  ADCSRB = 0;
  
  ADMUX|= (1 << ADLAR); //значение в ADCH
  ADCSRA|=(1 << ADEN);  //включение АЦП
}

void timer0_init() {
  TCCR0A = 0;
  TCCR0B = 0;
  TCNT0 = 0;
  
  // Не инверсный режим работы OC0A и OC0B 
  TCCR0A |= (1 << COM0A1);  
  TCCR0A |= (1 << COM0B1);

  TCCR0A |= (1 << WGM00) | (1 << WGM01);  //Режим 3: Быстрый ШИМ, скважность регулируется OCR0A и OCR0B
  TCCR0B |= (1 << CS01) | (1 << CS00);    //64 - 976 ГЦ
  
  OCR0A = 0;
  OCR0B = 0;
}

void timer2_init() {
  
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2 = 0;
  
  // Не инверсный режим работы OC0B
  TCCR2A |= (1 << COM2B1);
  
  TCCR2A |= (1 << WGM20) | (1 << WGM21);  //Режим 3: Быстрый ШИМ, скважность регулируется OCR2A и OCR2B
  TCCR2B |= (1 << CS22);                  //64 - 976 ГЦ
  
  OCR2B = 0;
}

int main(void)
{
  DDRD |= (1 << PORTD3);
  DDRD |= (1 << PORTD5);
  DDRD |= (1 << PORTD6);
  
  cli();
  adc_init();
  timer0_init();
  timer2_init();
  sei();
  
  while (1){
  ctrl++;
    if (ctrl > 10){
      ctrl = 0;
      
      ADMUX &= ~(1 << MUX0);
      ADMUX &= ~(1 << MUX1);
      ADMUX &= ~(1 << MUX2);
      ADMUX &= ~(1 << MUX3);
      
      ADCSRA |= (1 << ADSC);        //запуск преобразования
      while(ADCSRA & (1 << ADSC));  //проверка, закончилось ли аналого-цифровое преобразование
      potenciometr_in_0 = ADCH;
      potenciometr_average_0 += (potenciometr_in_0 - potenciometr_average_0) * coef;
      if (potenciometr_average_0 < 10 ){
        potenciometr_out_0 = 0;
        DDRD &= ~(1 << PORTD6);
      }
      else{
        DDRD |= (1 << PORTD6);
        potenciometr_out_0 = potenciometr_average_0;
        
      }
      OCR0A = potenciometr_average_0;
      
      
      ADMUX |= (1 << MUX0);
      ADMUX &= ~(1 << MUX1);
      ADMUX &= ~(1 << MUX2);
      ADMUX &= ~(1 << MUX3);
      
      ADCSRA |= (1 << ADSC);          //запуск преобразования
      while((ADCSRA & (1 << ADSC)));  //проверка, закончилось ли аналого-цифровое преобразование
      potenciometr_in_1 = ADCH;
      potenciometr_average_1 += (potenciometr_in_1 - potenciometr_average_1) * coef;
      if (potenciometr_average_1 < 10 ){
        potenciometr_out_1 = 0;
        DDRD &= ~(1 << PORTD5);
      } 
      else{
        DDRD |= (1 << PORTD5);
        potenciometr_out_1 = potenciometr_average_1;
        
      }
      OCR0B = potenciometr_out_1;
      

      ADMUX &= ~(1 << MUX0);
      ADMUX |= (1 << MUX1);
      ADMUX &= ~(1 << MUX2);
      ADMUX &= ~(1 << MUX3);
      
      ADCSRA |= (1 << ADSC);        //запуск преобразования
      while(ADCSRA & (1 << ADSC));  //проверка, закончилось ли аналого-цифровое преобразование
      potenciometr_in_2 = ADCH;
      potenciometr_average_2 += (potenciometr_in_2 - potenciometr_average_2) * coef;
      if (potenciometr_average_2 < 10 ){
        potenciometr_out_2 = 0;
        DDRD &= ~(1 << PORTD3);
      }
      else{
        DDRD |= (1 << PORTD3);
        potenciometr_out_2 = potenciometr_average_2;
              
      }
      OCR2B = potenciometr_average_2;
    }
  }
}