АЦП микроконтроллеров AVR на пальцах

Хоть я и не позиционирую свой сайт как «учебник», но частенько приходится отвечать на вопросы начинающих эмбеддеров. Народ пишет и в личку, и в комменты, некоторые даже звонят. Когда однотипных вопросов накапливается достаточно много, то, мне кажется,  полезнее написать статью по теме, чем отвечать каждому товарищу по-отдельности, тем более, когда они так сильно прОсют 🙂
Тема использования АЦП описана чуть ли не на каждом микроконтроллерном сайте, но, как видим, вопросы у многих остаются. Не знаю, может народ не понимает английского для прочтения и изучения даташитов, но книга А.В. Евстифеева «Микроконтроллеры AVR семейств Tiny и Mega фирмы ATMEL», по-моему, должна быть у каждого человека, занимающегося такими  (и не только такими) микроконтроллерами. Эта книга по своей сути — перевод и обобщение даташитов наиболее популярных контроллеров. И не беда, что номенклатура последних уже существенно обогатилась — основные принципы те же.
Итак, аналого-цифровой преобразователь, как следует из его названия, преобразовывает аналоговую величину (в нашем случае напряжение) в цифровой код. Управляется он в ATmega8 всего двумя регистрами. На рисунке ниже я попытался максимально просто изобразить принцип действия АЦП и его основные характеристики.
ADC_picture

Входной аналоговый сигнал через вход мультиплексора MUX, который мы выбрали разрядами MUX3-MUX0 регистра ADMUX, поступает непосредственно на вход АЦП микроконтроллера и сравнивается с установленным REFS1,REFS0 (ADMUX) опорным напряжением (входной сигнал не может быть выше опорного — для измерения величин выше опорного следует применять делители напряжения). Весь диапазон опорного напряжения разбит на 2 в степени разрешение, т.е для нашего случая 2^10=1024 градации. При запуске АЦП установкой ADSC (ADSCRA) входной аналоговый сигнал сравнивается с опорным в течении времени t преобразования, и, по истечении этого времени в пару регистров ADCH(выделен жёлтым)ADCL(выделен голубым) записывается цифровой выходной 10-ти разрядный код, показывающий относительную величину входного, по отношению к опорному. Например, если опорное напряжение равно 5 В, а на вход АЦП пришёл сигнал 2,5 В, то в регистры ADCH-ADCL запишется значение 512 — половина от 1024. Причём результат в пару ADCH-ADCL будет записан со сдвигом вправо или влево в зависимости от значения разряда ADLAR регистра ADCSRA. Абсолютную величину напряжения легко вычислить , зная опорное.
Все разряды регистра ADMUX на рисунке наглядно представлены. Из ADSCRA не рассмотрены: 7-й разряд — ADEN — разрешение работы АЦП; 5-й — ADFR — включение режима непрерывного преобразования; 4-й — ADIF — флаг прерывания от АЦП; 3-й — ADIE —  разрешение прерывания от АЦП.
Теперь код для простейшего примера, демонстрирующего работу АЦП. Вся логика работы здесь, как и во всех моих примерах, расписана в комментариях. Код написан на классическом Си для WinAVR, но по идее должен работать и под CodeVision (только подключаемые файлы там по другому называются). МК ATmega8 работает от внутреннего генератора на частоте 1 МГц.

/*Учебный пример написаный товарищем s_black www.embed.com.ua для товарища ВМ, который ленится читать даташиты*/
#include <avr/io.h>
#include <avr/interrupt.h>
 
#define PORT_LED PORTD /*назначение порта светодиодов*/
#define DDR_LED  DDRD  /*назначение регистра направления порта светодиодов*/
#define LED_1    PD0   /*первый светодиод - индикатор 1/4 максимального напряжения*/
#define LED_2    PD1   /*второй светодиод - индикатор 2/4 максимального напряжения*/
#define LED_3    PD2   /*третий светодиод - индикатор 3/4 максимального напряжения*/
 
#define ON_LED(LED) (PORT_LED &= ~(1< <LED))  /*макрос включения светодиода*/
#define OFF_LED(LED) (PORT_LED |= (1<<LED))  /*макрос выключения светодиода*/
 
ISR (ADC_vect)//прерывание по завершению преобразования АЦП
{
    unsigned int voltage_ADC ;//переменная результата напряжения
 
	voltage_ADC = ADCW;//считываем значение АЦ преобразования
	//блок операторов ниже реализует "светящийся столбик" в зависимости от измеренной величины напряжения
	     if (voltage_ADC >= 768) {ON_LED (LED_3);ON_LED (LED_2);ON_LED(LED_1);}//если U > или = 3/4 максимального - подсвечиваем светодиоды 3,2,1
	else if (voltage_ADC >= 512) {OFF_LED(LED_3);ON_LED (LED_2);ON_LED(LED_1);}//если U > или = 2/4 и < 3/4 максимального - подсвечиваем светодиоды 2,1
	else if (voltage_ADC >= 256) {OFF_LED(LED_3);OFF_LED(LED_2);ON_LED(LED_1);}//если U > или = 1/4 и < 2/4 максимального - подсвечиваем светодиод 1
	else {OFF_LED(LED_3);OFF_LED(LED_2);OFF_LED(LED_1);}//если U < 1/4 максимального - ничего не подсвечиваем
	ADCSR |= (1<<ADSC);//запускаем очередное преобразование 
}
 
int main (void)                           
{
    DDR_LED |= (1<<LED_3) | (1<<LED_2) | (1<<LED_1);//разряды порта светодиодов - выходы
	PORT_LED |= (1<<LED_3) | (1<<LED_2) | (1<<LED_1);//изначально погашены
 
	//          7     6     5     4     3     2     1     0
	//ADMUX = REFS1 REFS0 ADLAR   –    MUX3  MUX2  MUX1  MUX0
	ADMUX |= (1<<REFS0);//в качестве опорного - напряжение питания, результат выравнивается вправо, 0-й канал АЦП (PC0)
 
	//           7    6     5     4     3     2     1     0
	//ADCSRA = ADEN ADSC  ADFR  ADIF  ADIE  ADPS2 ADPS1 ADPS0
	ADCSRA |= (1<<ADEN) | (1<<ADSC) | (1<<ADIE) | (1<<ADPS1) | (1<<ADPS0);//разрешаем преобразование, запускаем его, разрешаем прерывание от АЦП
	                                                                         //тактовую частоту делим на 8 - получаем частоту преобразования 125 кГц 
	sei();//общее разрешение прерываний
	for(;;);//бесконечный цикл в ожидании прерывания по завершению преобразования АЦП
}
 
 
}

Для демонстрации работы этого кода я собрал макет по такой схеме:
ADC ATmega8Элементы с левой стороны микроконтроллера IC1 ATmega8 — это стандартная даташитовская обвязка для АЦП. В частности L1, C1 — это LC-фильтр для «аналогового» питания.  Резистор R1 подтягивает вывод сброса микроконтроллера к питанию. Конденсатором С2 вывод опорного напряжения AREF рекомендуют коротить на корпус, если не используется внешнее опорное.
Теперь рассмотрим элементы с правой стороны. Источник измеряемого изменяемого напряжения реализован на потенциометре R2. Резистор R3 добавлен, чтобы не «посадить» питание на корпус при самом нижнем положении ползунка. Индикация напряжения по градациям реализована на светодиодах LED1-3 с токоограничительными резисторами R4-6. Питание — Li-ION батарея 3,7 В от мобильного телефона. В качестве отдельных светодиодов используются сегменты «a» трёхразрядного семисегментного индикатора с общим анодом.
На видео ниже демонстрация работы макета. В исходном положении ползунок R2 в верхнем по схеме положении, т.е. подключён к питанию в результате чего светятся все три светодиода. Напряжение на ползунке индицируется мультиметром DT830B.

При «движении» ползунка R2 вниз к «корпусу» (резистор используется многооборотный) мы видим погасание сначала правого (при напряжении 2,82 В), затем среднего ( при напряжении 1,88 В), и, наконец, левого (при напряжении 0,94 В). Если посчитать в столбик, то эмпирическим путём мы подтвердим практические результаты: 1 = 3,77; 3/4 = 2,8275; 2/4 = 1,885; 1/4 = 0,9425.

 

Запись опубликована в рубрике Разное с метками , , , , . Добавьте в закладки постоянную ссылку.

26 комментариев: АЦП микроконтроллеров AVR на пальцах

  1. Konoplj2010 говорит:

    Приветствую, а вот такой вопрос, как Вы считаете что более целесообразно применять из температурных датчиков DS18B20 или Pt100 совместно с АЦП сего МК? Я раньше думал что DS18B20 это вещь вообще безотказная.
    Но по воле случая когда устроился на новую работу (ну Вы знаете) обратил внимание на что что в промышленности никаких DS18B20 или любых других законченных цифровых температурных датчиков не используют, только Pt100 или другие температурные датчики на платиновой основе.

  2. s_black говорит:

    Думаю, что причины следующие:
    1. Инертность по внедрению новых комплектующих.
    2. Проще обработка (не нужно заморачиваться с цифровым обменом — простой замер напряжения).
    3. БОльшая надёжность аналоговых датчиков.
    4. Дешевизна.
    Хотя может быть и другое…

  3. Konoplj2010 говорит:

    Палка о двух концах, в тоже время с аналоговыми датчиками:
    1. Не линейное сопротивление на больших температурах.
    2. Большие требования к кабельной линии.
    3. Большие размеры.
    4. Вот не как не дешевизна (Pt100 стоит начиная от 1500 руб. за шт., в тоже время DS18B20 в гильзе начиная от 250 руб. за шт. это при приблизительно).
    Если Вы говорите о инертности то тоже спорный вопрос, на большинстве температурных датчиков ставят преобразователи 4-20мА, что же не даёт ставить подобный преобразователь и для DS18B20???

  4. aahz говорит:

    ещё забыли про диапазон. платиновые датчики вне конкуренции.

  5. Konoplj2010 говорит:

    Да, да и это тоже. Термостойкие платиновые датчики можно нагревать даже до 450 градусов (в некоторых паяльных станциях стоят). Но вот не задача для замера температуры этого датчика необходимо серьёзное разрешение АЦП, на мой взгляд 1024 маловато.
    Могу пояснить, скажем если датчик работает в диапазоне температур от -50 до +100 градусов то 100+50=150, 150/1024=0.1464 градусов на 1 бит дискретизации, при этом мы даже не уверенные в том что этот бит будет стабилен, у DS18B20 тоже не очень период 0.125 градусов на 1 бит дискретизации.
    Но пожалуй это уже другая история, не в этой теме.

  6. ivan rusev говорит:

    а как выполнить переключение каналов ацп в прерывании

  7. s_black говорит:

    В регистре ADMUX установить необходимые значения разрядов MUX.

  8. ivan rusev говорит:

    в симуляторе протеус симулирует последний mux предыдущий пропускает

  9. ivan rusev говорит:

    может я что то не то делаю

  10. ivan rusev говорит:

    меня интересует алгоритм переключения каналов ацп в прерывании по ацп

  11. s_black говорит:

    Никакой разницы, где Вы переключаете каналы нет. Протеус может показывать что угодно.

  12. Oll говорит:

    Извините что влез, но у Вас там ошибочка небольшая в исходнике.

  13. Oll говорит:

    А так в протеусе все работает. Спасибо, полезная статья.

  14. Oll говорит:

    Извините еще раз, в схеме тоже ошибка.

  15. s_black говорит:

    Извините, а Вы не могли бы указать конкретно на ошибки?

  16. Аноним говорит:

    одна ошибка частота конроллера.То что я увидел

  17. ivan rusev говорит:

    в какой среде компиляции вы работаете?

  18. s_black говорит:

    Про частоту контроллера не понял.
    В WinAVR.

  19. ivan rusev говорит:

    Atmel studio 7 похоже частота контроллера в ней прописывается иначе в железе не будет работать.Я любитель начинающий хотя уже 42.Всё равно интересно.А у вас есть примеры с динамической индикацией и вывода результата с Ацп

  20. s_black говорит:

    Почитайте статьи здесь на сайте «Трёхфазный вольтметр переменного тока» и «Управление водонагревателем».

  21. Oll говорит:

    В первом макросе включения светодиода в знаке «меньше» не должно быть пробела. И посчитайте количество скобок. По моему в конце лишние, один штука. А в схеме светодиоды наоборот поставить. Хотя…

  22. s_black говорит:

    Вставляя сишный код исходника в HTML-код страницы сайта вордпресс иногда автоматом ставит пробелы или некоторые знаки считает за свои — такое бывает. Это насчёт пробела.
    Количество скобок в макросе правильное. Светодиоды поставлены катодами к МК и включаются лог. 0 — смотрите схему и макросы. Где ещё ошибки?

  23. Oll говорит:

    Извините, какие «еще ошибки» Вы думаете что я решил к Вам «доколупаться»? Какие нашел те и назвал. Вопросы еще будут?

  24. Oll говорит:

    Кстати Ваше письмо попало в спам.

  25. Алекс говорит:

    выше вопрос про 4..20мА. Токовая петля надежнее 0-10В, подверженного наводкам и помехам (если о стандартах), обеспечивает и длину сигнальных линий, отсутствие необходимости надежно экранировать/располагать вдали от силовухи и т.д. Про пт-100 и платину — чушь какая-то. Не нужно путать температуру, линейность характеристики и т.д. — каждой задаче — своё. Никель-хром-никель припомните вместо платины., иные никелевые дороже в десятки раз., зависит и от исполнения в т.ч. О преобразователях 4..20мА и 0..10В — нынче (лет 15 как и в России не проблема) у многих производителей есть такие. компактные, на дин рейку., с развязкой, с доп. опциями и т.д. (Избавляет переплачивать за датчик с головой-преобразователем — датчик живет меньше). К слову, это и о фантазиях выше относительно температур и дискретности. Почитайте о стандартах (я указал) и о дискретности., а температуру меряем и 800 гр.ц. и 1600 гр.ц. (а не тривиальные 450, на которые у автора (комментария) почему-то не хватает дискретности).

  26. Артур говорит:

    А вот если питание не 5 вольт, а скажем 4,67. То 4,67 это будет равняться 1024? Ну если опорное напряжение выбрано напряжением источника питания.

Добавить комментарий

Ваш e-mail не будет опубликован.