Часто во многих конструкциях, использующих микроконтроллеры AVR, используется аналогово-цифровой преобразователь (АЦП) данных микроконтроллеров. Он используется везде где необходимо преобразовать какое-нибудь аналоговое значение в цифровое. Обычно это конструкции с датчиками температуры, датчиками наклона, датчиками тока, гибкими датчиками и т.п.
На нашем сайте мы уже рассматривали использование АЦП в микроконтроллерах AVR в следующих проектах:
- измерение температуры с помощью микроконтроллера AVR и сенсора LM35;
- цифровой вольтметр 0-25В на микроконтроллере AVR ATmega32;
- подключение джойстика к микроконтроллеру AVR ATmega8;
- амперметр на 100 мА на микроконтроллере AVR ATmega8;
- измерение интенсивности света с помощью фоторезистора и микроконтроллера AVR ATmega8.
Но в данной статье мы более подробно рассмотрим использование АЦП в микроконтроллере AVR ATmega16. В этом проекте мы будем подсоединять ко входу АЦП микроконтроллера небольшой потенциометр и будем использовать 8 светодиодов чтобы показывать изменение напряжения на выходе АЦП в зависимости от изменения сигнала на его входе.
Что такое АЦП (аналого-цифровой преобразователь)
В электронике под АЦП (в переводе с англ. от ADC - analog-to-digital converter) понимают устройство которое конвертирует аналоговый сигнал (например, ток или напряжение) в цифровой код (двоичную форму). В реальном мире большинство сигналов являются аналоговыми, но все микроконтроллеры и микропроцессоры способны понимать только двоичные (бинарные) сигналы – 0 или 1. То есть чтобы заставить микроконтроллер понимать аналоговые сигналы необходимо конвертировать их в цифровую форму – это и делает АЦП. Существуют различные типы АЦП, каждый тип удобен для конкретных приложений. Наиболее популярные типы АЦП используют такие типы аппроксимаций как приближенная, последовательная и дельта-аппроксимацию.
Самые дешевые АЦП – с последовательной аппроксимацией, их мы и будем рассматривать в данной статье. В данном случае для каждого фиксированного аналогового уровня последовательно формируется серия соответствующих им цифровых кодов. Внутренний счетчик используется для их сравнения с аналоговым сигналом после конверсии. Генерация цифровых кодов останавливается когда соответствующий им аналоговый уровень становится чуть-чуть больше чем аналоговый сигнал на входе АЦП. Этот цифровой код и будет представлять собой конвертированное значение аналогового сигнала.
Мы в данной статье будем использовать встроенный в микроконтроллер AVR ATmega16 аналого-цифровой преобразователь – практически все микроконтроллеры семейства AVR оснащаются встроенным АЦП. Но вместе с тем следует помнить о том, что существуют и другие типы микроконтроллеров, у которых нет собственных АЦП – в этом случае необходимо использовать внешний АЦП. Как правило, внешние АЦП сейчас выпускаются в виде одной микросхемы.
АЦП в микроконтроллере AVR ATmega16
Микроконтроллер ATmega16 имеет встроенный 10-битный 8-канальный АЦП. Разрядность 10 бит означает, что каждый входной аналоговый сигнал (для ATmega16 он должен быть в диапазоне 0-5В) представляется 1024 уровнями дискретного сигнала (2 в степени 10 = 1024), то есть дискретизируется с точностью Uвх/1024. 8-канальный означает что АЦП может быть задействован на 8 контактах микроконтроллера одновременно. Фактически весь PortA (GPIO33-GPIO40) может быть использован для операций АЦП. По умолчанию выводы PORTA являются контактами ввода/вывода общего назначения. Чтобы задействовать на них функции АЦП необходимо сконфигурировать специальные регистры, ответственные за функции аналого-цифрового преобразования в микроконтроллере. Поэтому их и называют регистрами АЦП. В данной статье мы разберем как правильно их конфигурировать.
На следующем рисунке показано расположение контактов АЦП на корпусе микроконтроллера ATmega16
Необходимые компоненты
- Микроконтроллер ATmega16 (купить на AliExpress).
- Источник питания с напряжением 5 Вольт.
- Программатор AVR-ISP (купить на AliExpress), USBASP (купить на AliExpress) или другой подобный.
- Кварцевый генератор 16 МГц (купить на AliExpress).
- Конденсатор 100 нФ (2 шт.) (купить на AliExpress).
- Конденсатор 22 пФ (2 шт.) (купить на AliExpress).
- Кнопка.
- Соединительные провода.
- Макетная плата.
- Светодиоды (любого цвета) (купить на AliExpress).
Работа схемы
Схема устройства приведена на следующем рисунке.
Внешний вид макетной платы с собранной на ней схемой устройства будет выглядеть следующим образом.
Установка регистров АЦП в микроконтроллере ATmega16
Регистр ADMUX (регистр выбора и мультиплексирования канала АЦП) - предназначен для выбора канала АЦП и опорного напряжения (reference voltage). Структура данного регистра представлена на следующем рисунке.
Биты 0-4 используются для выбора канала.
MUX4 | MUX3 | MUX2 | MUX1 | MUX0 |
ADC Channel Selected |
0 | 0 | 0 | 0 | 0 | ADC0 |
0 | 0 | 0 | 0 | 1 | ADC1 |
0 | 0 | 0 | 1 | 0 | ADC2 |
0 | 0 | 0 | 1 | 1 | ADC3 |
0 | 0 | 1 | 0 | 0 | ADC4 |
0 | 0 | 1 | 0 | 1 | ADC5 |
0 | 0 | 1 | 1 | 0 | ADC6 |
0 | 0 | 1 | 1 | 1 | ADC7 |
Бит 5 используется для коррекции результата преобразования вправо или влево.
ADLAR | Description |
0 | Right adjust the result |
1 | Left adjust the result |
Биты 6-7 используются для выбора опорного напряжения АЦП.
REFS1 | REFS0 | Voltage Reference Selection |
0 | 0 | AREF, Internal Vref turned off |
0 | 1 | AREF, Internal Vref turned off |
1 | 0 | Reserved |
1 | 1 | Internal 2.56 Voltage Reference with external capacitor at AREF Pin |
Теперь попробуем правильно сконфигурировать данный регистр в программе.
Исходный код программы на языке С (Си) с пояснениями
Полный текст программы приведен ниже. В этом разделе статьи объяснено значение некоторых элементов программы.
Для начала определим функцию для чтения конвертированного в результате АЦП значения. В качестве аргумента в данной функции будет выступать номер канала, на котором необходимо будет производить АЦП.
1 |
unsigned int ADC_read(unsigned char chnl) |
Поскольку у нас всего 8 каналов АЦП, то номер канала может принимать значение от 0 до 7.
1 |
chnl= chnl & 0b00000111; |
Записав число ‘40’ (в двоичном виде ‘01000000’) в регистр ADMUX мы выберем канал ADC0 для аналого-цифрового преобразования.
1 |
ADMUX = 0x40; |
На этом шаге мы начнем процесс аналого-цифрового преобразования, записав "1" в бит ADSC регистра ADCSRA. После этого необходимо подождать до тех пор пока бит ADIF не сигнализирует нам о том что процесс преобразования завершен. Мы закончим процесс АЦП записав ‘1’ в бит ADIF регистра ADCSRA. Когда преобразование закончено, возвратим значение АЦП.
1 2 3 4 |
ADCSRA|=(1<<ADSC); while(!(ADCSRA & (1<<ADIF))); ADCSRA|=(1<<ADIF); return (ADC); |
Выберем опорное напряжение АЦП установив бит REFS0. После этого разрешим АЦП и установим коэффициент деления предделителя равным 128.
1 2 |
ADMUX=(1<<REFS0); ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); |
Теперь сохраним значение АЦП и передадим его на PORTC. В результате этого 8 светодиодов, подключенных к PORTC, покажут получившееся в результате аналого-цифрового преобразования значение в 8-битном формате. В рассматриваемом примере мы показываем изменение напряжения от 0 до 5В, которое регулируется на входе АЦП микроконтроллера с помощью потенциометра.
1 2 |
i = ADC_read(0); PORTC = i; |
Величину подаваемого напряжения можно контролировать с помощью вольтметра. В представленном примере мы используем для этого цифровой мультиметр.
Полный текст программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <avr/io.h> unsigned int ADC_read(unsigned char chnl) { chnl= chnl & 0b00000111; // выбор канала АЦП от 0 до 7 ADMUX = 0x40; //выбран канал A0 ADCSRA|=(1<<ADSC); // старт преобразования while(!(ADCSRA & (1<<ADIF))); // ждем окончания преобразования ADCSRA|=(1<<ADIF); // очистим ADIF когда преобразование закончится return (ADC); //возвращаем рассчитанное значение АЦП } int main(void) { PORTC = 0xFF; //устанавливаем на всех контактах PORTC логические "1" ADMUX=(1<<REFS0); // выбор внутреннего опорного напряжения ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // разрешаем АЦП и устанавливаем коэффициент деления предделителя = 128 int i = 0; // определяем переменную типа integer чтобы хранить в ней значение АЦП while (1) { i = ADC_read(0); // сохраняем рассчитанное значение АЦП в переменной i PORTC = i; // передаем значение АЦП на контакты portc } } |
Попробовал использовать фрагмент приведённой программы для последовательного опроса двух каналов АЦП. Возникли проблемы - неправильно определяется факт завершения преобразования (строки 8 и 9). Как оказалось, такая конструкция применима для режима постоянного преобразования АЦП (Free Running Mode). В режиме запуска одиночного преобразования, что имеет место в нашем случае, флаг прерывания ADIF не используется, и для определения факта завершения преобразования нужно анализировать состояние бита ADSC регистра ADCSRA. Данный бит остается в высоком состоянии в процессе преобразования и сбрасывается по завершении преобразования. У меня программа нормально заработала, когда я заменил текст строки 8 на:
while (ADCSRA & (1 << ADSC)); //Ожидание завершения преобразования АЦП
а строку 9 удалил.
Ну вы молодец, разобрались в этом уже лучше чем я. Просто после написания более двухсот статей по Ардуино начинаешь уже немного забывать некоторые тонкости работы с AVR
Здравствуйте, while (ADCSRA & (1 << ADSC)); эта конструкция не позволяет пройти программе прежде выполнения условия (завершения конвертации АЦП), то есть, вернуть значение функии return;? Верно?
Здравствуйте, да, верно
Спасибо! Очень полезный материал. Только я так понимаю, что в строке 6 значение chnl нужно добавить к значению ADMUX=0x40, иначе всегда будет опрашиваться только канал 0:
ADMUX = (1 << REFS0) | chnl;
Да, именно так. Только нужно установить то значение chnl, которое вам нужно. Просто в нашей схеме мы считываем значение только с канала 0, поэтому chnl и не используем
Нет, у вас входной параметр chnl функции ADC_read нигде не используется. Поэтому его и нужно использовать, добавив к значению ADMUX.
Это тоже верно, входной параметр chnl функции ADC_read сейчас не используется. Возможно, я что то хотел с этим сделать, но потом забыл. Ваше предложение конструктивное, не спорю. Но почему то я так не сделал в свое время
Разве число ,40,в двоичном виде ‘01000000’ ?
Нет, конечно. Но в строке "ADMUX = 0x40;" число 40 не в десятичном виде, а шестнадцатеричном, при его переводе в двоичный код как раз и получается 01000000
Программу Вы написали, а как ею прошить контроллер, что бы посмотреть эффект.
Валерий, способов много, но один из самых популярных описан в этой статье. Он основан на использовании программатора USBASP и программы Atmel Studio 7.0. Также может пригодиться и программа AVRDUDE_PROG.
Никак не могу понять этот ADIF. При преобразовании ADIF устанавливается в 1 и чтобы сбросить его надо тоже записать 1 ?? Так чтоли ? Как-то странно это, по идеи сбрасывать 0 же нужно если в нём уже 1
Алексей, вот что написано про этот бит в сети:
Bit 4 -ADIF: ADC Interrupt Flag - Флаг прерывания ADC
Данный бит устанавливается в состояние 1 по завершению преобразования и обновления регистров данных. Прерывание по завершению преобразования ADC выполняется если в состояние 1 установлены бит ADIE и I-бит регистра SREG. Бит ADIF сбрасывается аппаратно при выполнении подпрограммы обработки соответствующего вектора прерывания. Кроме того, бит ADIF может быть очищен записью во флаг логической 1.
Насколько я понимаю, ошибки в программе нет.
В коде ошибка:
PORTC = 0xFF; //конфигурируем PORTC на выход поскольку к нему подключены светодиоды
А в чем ошибка то? Можно вместо "выход" написать "вывод данных" - так было бы немного точнее
Ошибка в том, что указанный код меняет состояние выходов порта на 1, а комментарий что меняем направление порта
Хорошо, спасибо за внимательность, я исправил опечатку
Конфигурирование порта С на вывод
DDRC = 0xFF; //
А что не так? С помощью этой команды на все контакты порта C подаются единицы
Это же порт для входа АЦП.
Нет, входы АЦП у Atmega16 находятся на Port A. Распиновку микроконтроллера Atmega16 можно посмотреть, к примеру, на схеме в этой статье.