Аналого-цифровые преобразования — АЦП. Аналоговые входы

Описание портов, работающих как аналоговые входы, платформы Arduino (Atmega8, Atmega168, Atmega328, или Atmega1280)

Аналого-цифровой преобразователь

Микроконтроллеры Atmega, используемые в Arduino, содержат шестиканальный аналого-цифровой преобразователь (АЦП). Разрешение преобразователя составляет 10 бит, что позволяет на выходе получать значения от 0 до 1023. Основным применением аналоговых входов большинства платформ Arduino является чтение аналоговых датчиком, но в тоже время они имеют функциональность вводов/выводов широкого применения (GPIO) (то же, что и цифровые порты ввода/вывода 0 - 13).

Таким образом, при необходимости применения дополнительных портов ввода/вывода имеется возможность сконфигурировать неиспользуемые аналоговые входы.

Цоколевка

Выводы Arduino, соответствующие аналоговым входам, имеют номера от 14 до 19. Это относится только к выводам Arduino, а не к физическим номерам выводов микроконтроллера Atmega. Аналоговые входы могут использоваться как цифровые выводы портов ввода/вывода. Например, код программы для установки вывода 0 аналогового входа на порт вывода со значением HIGH:

pinMode(14, OUTPUT);
digitalWrite(14, HIGH);

Подтягивающие резисторы

Выводы аналоговые входов имеют подтягивающие резисторы работающие как на цифровых выводах. Включение резисторов производится командой

digitalWrite(14, HIGH); // включить резистор на выводе аналогового входа 0

пока вывод работает как порт ввода.

Подключение резистора повлияет на величину сообщаемую функцией analogRead() при использовании некоторых датчиков. Большинство пользователей использует подтягивающий резистор при применении вывода аналогового входа в его цифровом режиме.

Подробности и предостережения

Для вывода, работавшего ранее как цифровой порт вывода, команда analogRead будет работать некорректно. В этом случае рекомендуется сконфигурировать его как аналоговый вход. Аналогично, если вывод работал как цифровой порт вывода со значением HIGH, то обратная установка на ввод подключит подтягивающий резистор.

Руководство на микроконтроллер Atmega не рекомендует производить быстрое переключение между аналоговыми входами для их чтения. Это может вызвать наложение сигналов и внести искажения в аналоговую систему. Однако после работы аналогового входа в цифровом режиме может потребоваться настроить паузу между чтением функцией analogRead() других входов.

Arduino имеет несколько аналоговых входов, используя которые можно измерять параметры аналоговых величин. Это может быть напряжение, ток, сопротивление, температура, свет и так далее. В некоторых случаях для преобразования физических величин в электрические сигналы могут потребоваться специальные датчики. Сегодня я расскажу об использовании и проведу тест производительности аналого-цифрового преобразователя (АЦП) Arduino . Тест я буду производить, используя оригинальную плату Arduino Mega 2560, в основе которой лежит микроконтроллер ATMega 2560, работающий на частоте 16 Мгц. Микроконтроллер ATMega328 , на котором основаны Arduino Uno и Arduino Nano , также работает на частоте 16 МГц, так что все вышеизложенное, скорее всего, справедливо и для этих и аналогичных плат.

analogRead

Давайте посмотрим сколько же времени занимает аналого-цифровое преобразование с использованием стандартной функции analogRead .

Для определения моментов начала и конца преобразования я буду использовать 12 вывод в качестве маркера. Для начала повторим эксперимент, который я описывал в статье . Будем изменять уровень напряжения на 12 цифровом пине между состояниями LOW и HIGH . Для чистоты эксперимента я помещу внутрь loop бесконечный цикл.

Скетч, реализующий простые переключения на 12 цифровом выводе выглядит следующим образом:

void setup() { DDRB = B01000000; //устанавливаем 12 пин в режим выхода } void loop() { while(1) { PORTB = B01000000; // устанавливаем пин 12 в состояние HIGH PORTB = B00000000; // устанавливаем пин 12 в состояние LOW } }

Воспользуемся осциллографом и посмотрим на временные параметры работы этой программы:

Отсюда видно, что время переключения состояния пина занимает у нас 62 нс (длительность положительного импульса).

Теперь немного изменим скетч и добавим между переключениями функцию чтения аналогового сигнала analogRead на 3 аналоговом пине:

int analogPin = 3; // входной аналоговый пин int analogValue = 0; void setup() { DDRB = B01000000; // устанавливаем 12 пин в режим выхода } void loop() { while(1) { PORTB = B01000000; // устанавливаем пин 12 в состояние HIGH analogValue = analogRead(analogPin); // читаем аналоговый сигнал PORTB = B00000000; // устанавливаем пин 12 в состояние LOW analogValue = analogRead(analogPin); // читаем аналоговый сигнал } }

int analogPin = 3 ; // входной аналоговый пин

int analogValue = 0 ; // значение аналогового сигнала

void setup ()

DDRB = B01000000 ; // устанавливаем 12 пин в режим выхода

void loop ()

while (1 )

PORTB = B01000000 ; // устанавливаем пин 12 в состояние HIGH

// читаем аналоговый сигнал

PORTB = B00000000 ; // устанавливаем пин 12 в состояние LOW

analogValue = analogRead (analogPin ) ; // читаем аналоговый сигнал

Осцилограмма сигнала на 12 цифровом пине теперь будет выглядеть следующим образом:

Длительность переключения в 62 нс и время циклического возврата к началу работы программы в 124 нс не превышают погрешность измерения на этом временном масштабе и мы можем пренебречь этими временными промежутками. Отсюда видно, что время, которое затрачивается на аналого-цифровое преобразование примерно равно 112 мкс, поэтому максимальная частота выборки при использовании функции analogRead не превышает 8.9 кГц.

Недостатком использования analogRead является еще и то, что Arduino не может выполнять другой код во время ожидания результата преобразования.

Используем прерывания АЦП

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

Разовая выборка

Разовая выборка — это на самом деле то, что Arduino делает при вызове функции analogRead . Мы не сможем получить значительных преимуществ, реализовав разовую выборку с помощью других средств. Поскольку перед запуском АЦП, в первую очередь проверяется флаг готовности АЦП, то это означает, что проверка флага в цикле ничем не отличается от того, что делает Arduino.

Непрерывная выборка

Хорошей идеей при непрерывной выборке сигнала является использование прерываний. Микроконтроллеры ATMega328 и ATMega2560 могут быть переведены в режим непрерывной выборки (free running mode ). В этом режиме АЦП запускается автоматически после завершения предыдущей обработки. Каждый раз преобразование заканчивается генерированием прерывания, которое вызывает функцию обработки прерывания ISR (ADC_vect) , в которой результат аналого-цифрового преобразования может быть считан и обработан.

Для включения режима непрерывной выборки необходимо установить три регистра: ADMUX , ADCSRA и ADCSRB . Детальное описание этих регистров можно найти в технических руководствах к микроконтроллерам.

Внутреннее опорное напряжение 1.1 В и входной аналоговый канал ADC3 выбираются при помощи ADMUX . Тактовая частота задается при помощи ADCSRA и в нашем примере установлена в виде делителя ÷16. Одно аналоговое преобразование занимает 13 тактовых периодов. Частота дискретизации может быть вычислена, исходя из тактовой частоты микроконтроллера: 16 Мгц/(16*13) ≈ 77 кГц. Установкой 6 бита регистра ADCSRA в состояние HIGH , запускается непрерывная выборка.

Результат аналого-цифрового преобразования считывается в функцию обработки прерывания ISR (ADC_vect) . Поскольку, результат имеет длину 10 бит, то он делится на два регистра ADCL и ADCH , размером в один байт каждый. Для корректного чтения значения сначала нужно считать значение регистра ADCL , а затем — регистра ADCH .

Пример скетча, в котором результат, полученный из АЦП копируется в целочисленную переменную analogValue:

int analogValue = 0; // значение аналогового сигнала void setup() { DDRB = B01000000; // pin 12 в режиме OUTPUT DIDR0 = 0x3F; // отключаем цифровые входы ADMUX = 0x43; // измеряем на ADC3, используем внутреннее опорное напр.= 1.1В ADCSRA = 0xAC; // включаем АЦП, разрешаем прерывания, делитель = 16 ADCSRB = 0x40; // включаем АЦ коналы MUX, режим скользящей выборки bitWrite(ADCSRA, 6, 1); sei(); // устанавливаем флаг прерывания } void loop() { } /*** Процедура обработки прерывания АЦП ***/ ISR(ADC_vect) { PORTB = B00000000; // пин 12 переводим в состояние LOW analogValue = ADCL; // сохраняем младший байт результата АЦП analogValue += ADCH << 8; // сохраняем старший байт АЦП PORTB = B01000000; // пин 12 переводим в состояние HIGH }

int analogValue = 0 ; // значение аналогового сигнала

void setup ()

DDRB = B01000000 ; // pin 12 в режиме OUTPUT

DIDR0 = 0x3F ; // отключаем цифровые входы

ADMUX = 0x43 ; // измеряем на ADC3, используем внутреннее опорное напр.= 1.1В

ADCSRA = 0xAC ; // включаем АЦП, разрешаем прерывания, делитель = 16

ADCSRB = 0x40 ; // включаем АЦ коналы MUX, режим скользящей выборки

bitWrite (ADCSRA , 6 , 1 ) ; // Запускаем преобразование установкой бита 6 (=ADSC) в ADCSRA

sei () ; // устанавливаем флаг прерывания

void loop ()

/*** Процедура обработки прерывания АЦП ***/

ISR (ADC_vect )

PORTB = B00000000 ; // пин 12 переводим в состояние LOW

analogValue = ADCL ; // сохраняем младший байт результата АЦП

analogValue + = ADCH << 8 ; // сохраняем старший байт АЦП

PORTB = B01000000 ; // пин 12 переводим в состояние HIGH

Результат работы программы на экране осциллографа:

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

Цикл loop теперь полностью свободен и может использоваться для обработки какого-либо кода.

Опорное напряжение

Для измерения аналогового сигнала у нас должен быть некоторый уровень напряжения, с которым мы будем производить сравнение. В микроконтроллерах ATMega328 и ATMega2560 , которые используются в Arduino опорное напряжение также является максимальным напряжением, которое может быть измерено. Напряжения всегда измеряются относительно земли. В Arduino есть три возможных источника опорного напряжения: AV cc — которое соединяется с цифровой линией питания 5 В, внутреннее напряжение 1.1 В (для Arduino Mega возможен еще вариант 2.56 В) и внешний источник опорного напряжения. Из-за того, что измерение входных напряжений производятся относительно опорного напряжения, флуктуации опорного напряжение оказывают влияние на результат.

Опорное напряжение можно установить, используя функцию или при помощи битов REFT в регистре ADMUX .

Опорное напряжение AV cc

AV cc является опорным напряжением по умолчанию и оно используется когда измеряемые напряжения напрямую зависят от напряжения источника питания. Например, в случае, где нужно измерить напряжение в резисторном полумосте, как показано на рисунке ниже.

Использование опорного напряжения 5В при измерении сопротивления в полумосте

Если по каким-то причинам напряжение источника питания упадет, то и напряжение в точке соединения двух резисторов упадет пропорционально. Из-за того, что теперь опорное и входное напряжение изменяются пропорционально, то и результат АЦП останется таким же.

Внутренне опорное напряжение 1.1 В

Используйте внутреннее опорное напряжение 1.1 В для точных измерений внешних напряжений. Опорное напряжение 1.1 В более стабильно и не зависит от изменения напряжения питания или температуры. Таким образом, можно производить измерения абсолютных значений. В Arduino Mega также возможен вариант опорного напряжения 2.56 В. примере на рисунке ниже используется опорное напряжение 1.1 В и делитель напряжения 10:1 для измерения внешнего напряжения в диапазоне от 0 до 11 В.

Использование внешнего опорного напряжения или внутреннего напряжения 1.1 В при измерении внешних напряжений

Погрешность

В соответствии с техническим руководством для микроконтроллеров ATMega328 и ATMega2560 опорное напряжение составляет 1.1 ± 0.1 В. Это достаточно большой допуск. Измеренное опорное напряжение тестируемой Arduino Mega 2560 было 1.089 В при температуре окружающего воздуха 21 °С и температура корпуса микроконтроллера была 29 ºC.

Я охладил корпус микроконтроллера, не проводящим ток охлаждающим спреем Kontakt Chemie FREEZE 75/200 до температуры -18 °С, при этом измеренное опорное напряжение снизилось до 1.084 В. Таким образом, температурный дрейф составил примерно 100 ppm (миллионных долей) / °C.

Тестовый скетч:

int analogPin = 3; // входной аналоговый пин void setup() { analogReference(INTERNAL1V1); // выбираем внутреннее опорное напряжение 1.1В Serial.begin(9600); } void loop() { int analogValue = analogRead(analogPin); // читаем значение на аналоговом входе Serial.println(analogValue); // выводим его в последовательный порт delay(300); }

int analogPin = 3 ; // входной аналоговый пин

void setup ()

// выбираем внутреннее опорное напряжение 1.1В

Serial . begin (9600 ) ;

void loop ()

// читаем значение на аналоговом входе

Serial . println (analogValue ) ; // выводим его в последовательный порт

delay (300 ) ;

Аналоговый пин 3 был подключен к источнику напряжения 0.545 В. При температуре 29 °C результат должен быть: (0.545/1.089)*1024 = 512 (реально полученное значение — 511). При температуре -18 °C должно быть (0.545/1.084) * 1024 = 515 (реально полученное значение тоже 515).

Как показал эксперимент, температурный дрейф небольшой и для точных измерений при использовании Arduino его нужно откалибровать из-за его большой общей неопределенности опорного напряжения, составляющей около 10%.

Шум

Одним из способов измерить уровень шума является определение разброса значений, получаемых с АЦП. Для этого подадим стабилизированное постоянное напряжение на один из аналоговых входов и преобразованные при помощи АЦП значения используем для построения гистограммы.

Тестовая цепь

Схема на рисунке ниже обеспечивает тестовое напряжение для Arduino .

Схема, подающая регулируемое постоянное напряжение на аналоговый вход Arduino

Стабилизированный регулируемый источник питания выдает напряжение 0.55 В, что составляет половину от опорного напряжения в 1.1 В. На фотографии ниже видно, что встроенный в мой регулируемый источник питания вольтметр явно привирает, показывая напряжение на выходе 0.4 В.

Сигнал дополнительно фильтруется при помощи цепочки R1 , C1 , C2 и подключается через резистор R2 , имеющий сопротивление 100 Ом к аналоговому входу A3 Arduino . Земля подключается к пину GND Arduino .

Шумовая составляющая на входе Arduino выглядит следующим образом:

Отсюда видно, что среднеквадратическое значение амплитуды переменной составляющей измеряемого напряжения на входе АЦП Arduino составляет лишь единицы милливольт.

Биннинг

АЦП микроконтроллеров ATMega328 и ATMega2560 имеет разрешение 2 10 = 1024 бита. Идея биннинга состоит в подсчете частоты наблюдения определенного значения. Создается массив со 1024 значениями, называемых бинами, которые представляют каждое из возможных значений АЦП. Так как доступная память ограничена, могут быть созданы бины только размером в байт. Число отсчетов, следовательно, ограничивается 255.

Программы

Протестируем шум, используя функцию analogRead , а затем используем прерывания. Две программы, по сути, делают одно и то же: определяют массив, состоящий из 1024 бин. В функции setup все бины инициализируются нулем и выбирается опорное напряжение 1.1 В.

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

Код примера биннинга измеренных значений, используя функцию analogRead :

Показать/скрыть код

int analogPin = 3; // входной аналоговый пин int sendStatus = 0; // статус передачи int startDelay = 0; byte valueBin; // значения бинов void setup() { analogReference(INTERNAL1V1); // выбираем опорное напряжение 1.1В for (int i=0; i<=1023; i++) valueBin[i] = 0; // очищаем бины Serial.begin(9600); Serial.println("Start"); } void loop() { int analogValue = analogRead(analogPin); // выборка аналогового входа if (sendStatus == 0) { // ничего не делаем первые 10000 выборок if (startDelay < 10000) startDelay++; else { valueBin += 1; // увеличиваем значение бина if (valueBin == 255) sendStatus = 1; } } if (sendStatus == 1) { for (int i=0; i<=1023; i++) { // выводим значение бина Serial.print(i); Serial.print("\t"); Serial.println(valueBin[i]); } Serial.println("Done"); sendStatus = 2; } }

int analogPin = 3 ; // входной аналоговый пин

int sendStatus = 0 ; // статус передачи

int startDelay = 0 ;

byte valueBin [ 1024 ] ; // значения бинов

void setup ()

analogReference (INTERNAL1V1 ) ; // выбираем опорное напряжение 1.1В

for (int i = 0 ; i <= 1023 ; i ++ ) valueBin [ i ] = 0 ; // очищаем бины

Serial . begin (9600 ) ;

void loop ()

int analogValue = analogRead (analogPin ) ; // выборка аналогового входа

if (sendStatus == 0 )

// ничего не делаем первые 10000 выборок

if (startDelay < 10000 ) startDelay ++ ;

else

valueBin [ analogValue ] + = 1 ; // увеличиваем значение бина

// останавливаемся, если бин полон

if (valueBin [ analogValue ] == 255 ) sendStatus = 1 ;

if (sendStatus == 1 )

for (int i = 0 ; i <= 1023 ; i ++ )

// выводим значение бина

Serial . print (i ) ;

Serial . print ("\t" ) ;

Serial . println (valueBin [ i ] ) ;

Serial . println ("Done" ) ;

sendStatus = 2 ;

Код примера биннинга измеренных значений, используя прерывания:

Показать/скрыть код

int sendStatus = 0; // статус передачи int startDelay = 0; byte valueBin; // значения бинов void setup() { TIMSK0 = 0x00; // отключаем таймер (из-за прерываний) DIDR0 = 0x3F; // отключаем цифровые входы ADMUX = 0xC3; // измеряем на ADC3, без корректировки, внутр.опорное напр. 1.1В ADCSRA = 0xAC; // включаем АЦП, разрешаем прерывания, делитель = 128 ADCSRB = 0x40; // Включаем каналы MUX АЦП, режим постоянной выборки bitWrite(ADCSRA, 6, 1); // Запускаем преобразование установкой бита 6 (=ADSC) в ADCSRA sei(); // устанавливаем глобальный флаг прерываний for (int i=0; i<=1023; i++) valueBin[i] = 0; // очищаем бины Serial.begin(9600); Serial.println("Start"); } void loop() { if (sendStatus == 1) { for (int i=0; i<=1023; i++) { // выводим значения бинов Serial.print(i); Serial.print("\t"); Serial.println(valueBin[i]); } Serial.println("Done"); sendStatus = 2; } } /*** Процедура обработки прерывания АЦП ***/ ISR(ADC_vect) { int analogValue = ADCL; // сохраняем младший байт АЦП analogValue += ADCH << 8; // сохраняем старший байт АЦП if (sendStatus == 0) { // ничего не делаем первые 10000 выборок if (startDelay < 10000) startDelay++; else { valueBin += 1; // увеличиваем значение бина if (valueBin == 255) sendStatus = 1; { // останавливаемся, если бин полон } } }

int sendStatus = 0 ; // статус передачи

int startDelay = 0 ;

byte valueBin [ 1024 ] ; // значения бинов

void setup ()

TIMSK0 = 0x00 ; // отключаем таймер (из-за прерываний)

DIDR0 = 0x3F ; // отключаем цифровые входы

ADMUX = 0xC3 ; // измеряем на ADC3, без корректировки, внутр.опорное напр. 1.1В

ADCSRA = 0xAC ; // включаем АЦП, разрешаем прерывания, делитель = 128

ADCSRB = 0x40 ; // Включаем каналы MUX АЦП, режим постоянной выборки

bitWrite (ADCSRA , 6 , 1 ) ; // Запускаем преобразование установкой бита 6 (=ADSC) в ADCSRA

sei () ; // устанавливаем глобальный флаг прерываний

for (int i = 0 ; i <= 1023 ; i ++ ) valueBin [ i ] = 0 ; // очищаем бины

Serial . begin (9600 ) ;

Serial . println ("Start" ) ;

void loop ()

if (sendStatus == 1 )

for (int i = 0 ; i <= 1023 ; i ++ )

{ // выводим значения бинов

Serial . print (i ) ;

Serial . print ("\t" ) ;

Serial . println (valueBin [ i ] ) ;

}

Serial . println ("Done" ) ;

sendStatus = 2 ;

// останавливаемся, если бин полон

}

}

}

Тестируемые частоты

Тест проводился с использованием функции analogRead и используя режим непрерывной выборки. Так как в последнем случае частоту выборки можно изменять, то тестировались четыре различные частоты выборки, задаваемые путем изменения значения в строке ADCSRA = 0xAC . Тестируемые частоты: 9.6 кГц (тактовая частота clk ÷128), 19.2 кГц (clk ÷64), 38.4 кГц (clk ÷32) и 76.9 кГц (clk ÷16). Частота выборки при использовании функции analogRead , как мы выяснили выше примерно равна 8.9 кГц.

Результаты

Результаты для обоих способов и различных частот выборки оказались похожими. Выборки разделились на 2 бина и лишь некоторые выборочные значения попали в третий бин. Это значит, что уровень шума во всех случаях достаточно низкий.

Переключение между входами

Выбор аналогового входа осуществляется в строке analogPin =n где n является номером аналогового пина или изменением битов выбора аналогового канала MUX в регистре ADMUX . Особое внимание должно быть уделено при использовании режима непрерывной выборки: аналоговый канал нужно выбрать перед стартом нового аналогового преобразования. В процедуре обработки прерывания выбирается аналоговый вход, который будет считываться в момент следующего прерывания.

Чтобы проверить уровень шума и погрешность при переключении входов, нужно немного изменить представленные выше программы. Второе напряжение подается на аналоговый вход 5. Кроме того, производится биннинг измеренных значений как и при тестировании шума.

= 0xC3 ;

int analogValue = ADCL ;

. . .

Результаты

Оба измеренных напряжения видны как два выступа на гистограммах. На рисунке ниже представлены гистограммы пяти тестов: с использованием функции analogRead , непрерывная выборка с clk ÷128, clk ÷64, clk ÷32 и clk ÷16. Измеренные значения первого напряжения (результат обработки АЦП = 511) не отклоняются от предыдущего теста шума. Измерение по-прежнему точное. Окружающих бинов очень мало, это означает, что уровень шума не увеличился.

На каждой из пяти гистограмм показаны две области с выступами, представляющие два измеренных напряжения

Частота выборки и разрешение

Коды, реализующие биннинг использованы и для анализа разрешения и частоты выборки. Для этого теста к аналоговому входу Arduino был подключен генератор функций, как показано на рисунке ниже.

Генератор функций подает с напряжением размаха в 25 мВ и напряжением смещения (= среднему значению) в 0.55 В. На каждом измерении частота сигнала выбирается таким образом, чтобы частота выборки была в 163 раза выше.

Треугольный сигнал выбран из-за того, что каждое значение при квантовании встречается одинаково часто. При биннинге такого сигнала каждое значение бина с минимальным и максимальным значениями напряжения может иметь то же самое число повторений.

Результаты

Результаты тестирования показали, что функция analogRead , работающая с низкой частотой дискретизации и непрерывная выборка с частотой clk ÷128 имеют достаточную плоскую вершину: все значения в диапазоне встречаются с одним и тем же числом повторений. Но на более высоких частотах дискретизации (clk ÷64, clk ÷32 и clk ÷16) возникают провалы в области биннинга и с ростом частоты ситуация ухудшается.

Большая частота выборки приводит к провалам

В технических описаниях на микроконтроллеры ATmega

Не секрет, что все величины в физическом мире носят аналоговый характер. Для измерения этих величин, люди придумали множество различных приборов. Так, например, термометр позволяет узнать температуру вещества, барометр — давление газа, гигрометр — влажность воздуха. А с помощью весов можно измерить вес тела.

Все эти устройства имеют шкалу, которую мы используем для фиксации их показаний. Рассмотрим простой пример — определение температуры с помощью обычного градусника. Человек решает эту задачу очень просто: мы смотрим, к какому из делений ближе всего приблизился уровень жидкости в градуснике. Полученное таким образом значение и будет измеренной температурой. Иными словами, мы осуществляем преобразование аналоговой непрерывной величины в дискретную, которую можно записать на бумаге с помощью цифр.

Чтобы автоматизировать процесс измерения аналоговых величин, и возложить эту задачу на электронные приборы, инженеры создали особое устройство, называемое аналого-цифровым преобразователем (АЦП). Это устройство позволяет превращать аналоговый сигнал в цифровой код, пригодный для использования в ЭВМ.

В робототехнике АЦП являются важной составляющей системы датчиков машины. Акселерометр, гироскоп (гиротахометр), барометр, магнитометр, и даже видеокамера — все эти приборы соединяются с центральным процессором с помощью АЦП.

Конструктивно, АЦП может находиться в одном корпусе с микропроцессором или микроконтроллером, как в случае Arduino Uno. В противном случае, как и все современные электронные устройства, АЦП может быть оформлен в виде отдельной микросхемы, например MCP3008:

Следует отметить, что существует и устройство с обратной функцией, называемое цифро-аналоговым преобразователем (ЦАП, DAC). Оно позволяет переводить цифровой сигнал в аналоговый. Например, во время проигрывания мелодии на мобильном телефоне происходит преобразование цифрового кода из MP3 файла в звук, который вы слышите у себя в наушниках.

Для лучшего понимания работы АЦП нам потребуется интересная задачка. В качестве оной, попробуем сделать устройство для измерения оставшегося заряда обычных пальчиковых батареек — самый настоящий цифровой вольтметр.

1. Функции работы с АЦП

На этом уроке изучать работу АЦП мы будем с помощью платформы Arduino. В используемой нами модели Arduino Uno, наряду с обычными выводами общего назначения (к которым мы уже подключали и ) есть целых шесть аналоговых входов . В других версиях Arduino таких входов может быть и больше, например, у Arduino Mega их 16.

На карте Arduino Uno аналоговые входы имеют буквенно-цифровые обозначения A0, A1, …, A5 (снизу слева).

Во время работы всё с теми же , мы познакомились с функцией digitalRead , которая умеет считывать цифровой сигнал с определенного входа контроллера. У этой функции существует аналоговая версия analogRead , которая может делать то же самое, но только для аналогового сигнала.

результат = analogRead(номер_контакта);

после вызова этой функции, микроконтроллер измерит уровень аналогового сигнала на заданном контакте, и сохранит результат работы АЦП в переменную «результат». При этом результатом функции analogRead будет число от 0 до 1023.

2. Разрядность АЦП

Надо заметить, что число 1023 здесь появилось неспроста. Дело в том, что у каждого устройства АЦП есть такой важный параметр как разрядность . Чем больше значение этого параметра, тем точнее работает прибор. Предположим, что у нас есть АЦП с разрядностью 1. Подавая на вход любое напряжения от 0 до 2,5 Вольт, на выходе мы получим 0. Любое же напряжение от 2,5 до 5 вольт даст нам единицу. То есть 1-битный АЦП сможет распознать только два уровня напряжения. Графически это можно изобразить следующим образом:

АЦП с разрядностью 2 распознает уже четыре уровня напряжения:

  • от 0 до 1,25 — это 0;
  • от 1,25 до 2,5 — это 1;
  • от 2,5 до 3,75 — это 2;
  • наконец, от 3,75 до 5 — это 3.

На следующих двух картинках изображена работа АЦП с разрядностью 2 и 3 бит:

В Arduino Uno установлен 10-битный АЦП, и это значит, что любое напряжение на аналоговом входе в диапазоне от 0 до 5 вольт будет преобразовано в число с точностью 1/1024 вольта. На графике будет сложно изобразить столько ступенек. Имея такую точность, 10-битный АЦП может «почувствовать» изменение напряжение на входе величиной всего 5 милливольт.

3. Опорное напряжение

Есть нюанс, который может стать причиной ошибки измерения с помощью АПЦ. Помните тот диапазон от 0 до 5 вольт в котором работает устройство? В общем случае этот диапазон выглядит иначе:

от 0 до опорного напряжения

Это изменение повлечет за собой изменение формулы расчет точности АЦП:

точность = опорное напряжение/1024

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

В нашем примере опорное напряжение будет равно напряжению питания Arduino Uno, которое дал USB порт компьютера. У моем конкретном случае это напряжение было 5.02 Вольта, и я могу смело заявить, что измерил заряд батарейки с высокой точностью.

Что если вы питаете микроконтроллер от другого источника? Допустим у вас есть четыре NiMh аккумулятора на 1.2 Вольта. В сумме они дадут 4.8 Вольта (пусть они немного разряжены, ведь в действительности их заряжают до 1.4 Вольта). Точность измерения будет равна 4.8/1024. Это следует учесть в нашей программе.

Наконец рассмотрим случай, когда мы питаем Arduino Uno одним напряжением, а в качестве опорного хотим установить совсем другое, например, 3.3 Вольта. Что делать? Для такого варианта на Arduino Uno есть специальный вывод Vref. Чтобы решить проблему, нам нужно подать на этот контакт напряжение 3.3 Вольта, и разрешить использование внешнего источника опорного напряжения функцией:

AnalogReference(EXTERNAL);

которую следует вызвать внутри функции setup нашей программы.

Также следует учитывать, что результат измерения значения напряжения не может превышать границы диапазона. Если мы выбираем в качестве опорного напряжения 3.3 Вольта, а поступающий сигнал будет с большим напряжением, то мы получим неправильное значение напряжения, поскольку АЦП «не знает» о наличии более высокого напряжения.

4. Программа

Наша первая программа с использованием АЦП будет крайне простой: каждую секунду мы будем измерять аналоговое значение на входе A0, и передавать его в последовательный порт.

Int val = 0; void setup() { Serial.begin(9600); pinMode(A0, INPUT); } void loop() { val = analogRead(A0); Serial.println(val); delay(1000); }

Теперь загружаем программу на Arduino, и переходим к измерениям.

5. Подключение

Чтобы измерить напряжение на батарейке, мы должны подключить её к нашей Arduino всего двумя контактами. Для примера используем щелочную батарейку на 1.5 Вольта.

Теперь откроем окно COM-монитора в Arduino IDE, и посмотрим какие значение выдает нам АЦП:

Что означает число 314? Вспомним, что 10-битный АЦП разбивает диапазон от 0 до 5 вольт на 1024 части. Значит точность 10-битного АЦП — 5/1024. Зная точность, мы можем записать формулу для преобразования показаний АЦП к вольтам:

V = (5/1024)*ADC

где V — измеренное напряжение на батарейке;
ADC — результат работы функции analogRead.

Подставим эту формулу в программу и снова попробуем измерить заряд батарейки!

Int val = 0; void setup() { Serial.begin(9600); pinMode(A0, INPUT); } void loop() { val = analogRead(A0); Serial.println((5/1024.0)*val); delay(1000); }

Результат измерений:

Уже больше похоже на правду.

6. Итог

Итак, мы разобрались с весьма сложной и важной темой в мире электроники. АЦП используется повсеместно, и в робототехнике без этого устройства уж точно не обойтись. Для понимания окружающего мира роботам как-то нужно переводить аналоговые ощущения в числа.

На нашем портале можно найти несколько уроков, выполнение которых зависит от понимания темы АЦП: , ёмкостный датчик, потенциометр и аналоговый джойстик. А в совокупности с еще одной важной темой — ШИМ, применение АЦП позволит создать диммер светодиодной лампы и регулятор хода двигателя. Успехов!

Система Arduino поддерживает обработку аналоговых сигналов. Для входных сигналов мы имеем АЦП (аналогово-цифровой преобразователь), в случае выходного сигнал — возможна модуляция ШИМ (широтно-импульсная модуляция).

В Arduino, сердцем которой является микроконтроллер Atmega, имеется один 10-битный АЦП. Это означает, что считанное значение напряжения может находиться в диапазоне от 0 — 1023. В зависимости от опорного напряжения 1024 значений будут распределены на соответствующий диапазон. В результате мы можем получить различную точность и различный диапазон напряжений, считываемых аналого-цифровым преобразователем.

Если выбрать опорное напряжение равное 1,024В, то каждое последующее значение, считанное с аналогового входа будет соответствовать 1мВ. Если опорное напряжение задать равным 5В, то каждое последующее значение будет соответствовать приблизительно 5 мВ.

Следует отметить, что аналого-цифровые преобразователи не имеют идеальной линейной характеристики. Это означает, что в определенных интервалах может быть разница между фактическим и считанным значением напряжения. Из вышеизложенного вытекает, что увеличивая диапазон измерения, мы теряем на качестве измерения, уменьшая диапазон, мы увеличиваем точность измерения.

Примечание: Arduino имеет несколько (в зависимости от версии) аналоговых входов, однако АЦП в ней только один. Это означает, что одновременно может быть считано значение только с одного из датчиков, подключенных к аналоговым входам A0… A5 (A0… A15 для Arduino MEGA).

Функция analogReference()

Для правильной работы АЦП требуется опорное напряжение (эталон). Для Arduino опорное напряжение может быть в диапазоне 0…5В (или 0… 3,3В для Arduino с напряжением питания 3,3В). В зависимости от типа используемого микроконтроллера у нас могут быть разные виды опорного напряжения.

Мы можем использовать внутренний или внешний источник опорного напряжения. Функция AnalogReference() предназначена для того, чтобы установить соответствующий источник опорного напряжения. Доступны следующие параметры этой функции:

  • DEFAULT: опорное напряжение составляет 5В или 3,3В (в зависимости от питания) — то есть, оно равно напряжению питания микроконтроллера;
  • INTERNAL: опорное напряжения составляет 1,1В для ATmega168, ATmega328 и 2,56В для ATmega8;
  • INTERNAL1V1: опорное напряжение составляет 1,1В — только для Arduino MEGA;
  • INTERNAL2V56: опорное напряжение составляет 2,56В — только для Arduino MEGA;
  • EXTERNAL: внешнее опорное напряжение, приложенное к выводу AREF — от 0 до 5В.

Параметр DEFAULT выбираем, когда хотим воспользоваться опорным напряжением 5В (питание системы). Это самый простой и одновременно наименее точный способ. Здесь требуется хорошая стабильность питания.

Использование INTERNAL является хорошим вариантом, в ситуации, когда мы создаем проект, предназначенный для конкретной версии Arduino. Внутренние опорное напряжение является относительно стабильным и достаточным в большинстве случаев.

Наиболее точным вариантом является использование внешнего источника опорного напряжения. Существуют специальные источники опорного напряжения (ИОН). Плюсом является возможность получения необходимого точного опорного напряжения, например, 1,024В или 2,048В, что облегчает интерпретацию данных, считываемых АЦП. К недостаткам применения внешнего источника опорного напряжения можно отнести возможное увеличение стоимости проекта.

Синтаксис функции analogReference() показан в следующем примере:

AnalogReference(DEFAULT); //опорное напряжение = напряжение питания analogReference(INTERNAL); //опорное напряжение = 1,1В или 2,56В analogReference(EXTERNAL); //опорное напряжение = напряжение на AREF выводе

Функция analogRead()

Функция analogRead() обеспечивает считывание значения с одного из аналоговых входов. Считанное значение находится в диапазоне 0 — 1023 (10-битный АЦП). Необходимо указать номер аналогового входа, с которого будет происходить чтение данных.

Следующий пример иллюстрирует использование аналоговых входов:

#define analogPin 0 // потенциометр подключен к A0 int val = 0; // val — переменная, хранящая считанное значение void setup() { Serial.begin(9600); // инициализация последовательного порта } void loop() { val = analogRead(analogPin); // чтение значения напряжения с порта A0 Serial.println(val); // отправка измеренной величины на терминал }

Как видно, на приведенном выше примере, считанное значение напряжения передается через последовательный порт на компьютер.

В примере не использована функция analogReference(), так как по умолчанию система использует опорное напряжение от источника питания. Однако, лучше указывать в функции setup() явный выбор опорного напряжения (в нашем случае это analogReference(DEFAULT)), так как это облегчает понимание кода и его модификацию в будущем.

Функция analogWrite()

Функция analogWrite() позволяет управлять выходом с помощью сигнала ШИМ. ШИМ часто используется в качестве замены обычного аналогового сигнала. Количество доступных выводов ШИМ зависит от типа используемого микроконтроллера в Arduino.

Так у Arduino на микроконтроллере:

  • Atmega8 — выводы 9, 10, 11;
  • Atmega128, Atmega168 и Atmega328 — выводы 3, 5, 6, 9, 10, 11;
  • Atmega1280 — выводы 2…13 и 44…46.

Частота переключения ШИМ большинства контактов составляет 490 Гц. Степень заполнения формируется числом от 0 до 255 (0 — без заполнения, 255 – полное заполнение).

Если мы подключим светодиод к контакту PWM и будем менять заполнение ШИМ, мы увидим изменение интенсивности свечения светодиода. Ниже приведен пример программы изменения свечения светодиода при помощи потенциометра:

#define ledPin 11 // светодиод подключен к контакту 9 #define analogPin 0 // потенциометр на А0 int val = 0; // val — переменная, хранящая значение A0 void setup() { pinMode(ledPin, OUTPUT); // устанавливаем контакт 9 как выход } void loop() { val = analogRead(analogPin); // чтение с потенциометра analogWrite(ledPin, val / 4); // пишем в ШИМ }

Как вы можете видеть, значение, считанное с аналогового входа, преобразуется в соответствующее значение ШИМ.

Чтобы ШИМ работал пропорционально вращению потенциометра, значение, полученное с A0, следует разделить на четыре. Это связано с тем, что данные с потенциометра лежат в диапазоне от 0 до 1024, а ШИМ принимает диапазон данных от 0 до 255.

В этом примере используется простое деление. В Arduino IDE имеется специальная функция map(), которая предназначена для пропорционального преобразования данных в новый диапазон значений.

ADS1115 это 16-разрядный Аналого-Цифровой Преобразователь, который может прекрасно расширить разрешающие и измерительные возможности Вашей Arduino. Он имеет внутренний ИОН (Источник Опорного Напряжения), 4 аналоговых входа, которые могут быть настроены в единичный, дифференциальный и сравнительный режимы измерения.

Важно: АЦП выдает 16-разрядное знаковое значение напряжения на входе. Т. е. максимальная величина шкалы напряжения не 65535 а 32768. Соответственно если необходимо использовать шкалу на все 16-бит можно только при условии дифференциального измерения, где на один из входов будет подключен внешний источник опорного напряжения а второй будет являться измерительным.
ОЧЕНЬ ВАЖНО!!!: Этот модуль очень боится перенапряжения как по входам, так и по питанию. Превышение напряжения более чем на 5% от напряжения питания его моментально сожжет.

Система установки адреса I2C ADS1115

Данное устройство предполагает установку адреса для I2C шины при помощи замыкания вывода ADDR на цифровые сигнальные выводы или выводы шины питания. Установка различных адресов I2C предназначена для того, чтоб при подключении двух и более различных устройств не происходило конфликта между ними, т. к. в лучшем случае модули у которых адреса совпадут просто не будут работать, а в худшем - работа всех модулей на шине может стать непредсказуемой. Как выглядит выбор адреса показано на рисунке ниже.

Схема подключения АЦП ADS1115 к Arduino

Данная схема показывает каким образом можно измерить напряжение с внутреннего стабилизатора на 3.3В



Подключив ADS1115 I2C АЦП к ардуино по вышеприведенной схеме, Вы можете попробовать в действии его уже прямо сейчас!

Для этого необходимо всего лишь выбрать в выпадающем списке ниже, Вашу плату, указать порт и нажать Run on Arduino .

Самую свежую библиотеку с примерами кода можно скачать из репозитория GITHUB

Следующий код представляет собой образец программы выводящей в последовательный порт значение на входе 0 АЦП в виде целого (относительной шкалы), и пересчитанное в напряжение.

Если Вы испытываете затруднения с определением адреса устройства или не можете определить причину неработоспособности Вашего модуля i2c, запустите следующий код на Вашей Arduino Uno/Mega. Данная программа является сканером I2C порта управляемым через отладочный порт в режиме диалога. Она отображает на каких адресах есть устройства, а на каких их нет, а также поддерживаемые скорости общения (если вешаете несколько устройств, нужно выбирать скорость порта по самому медленному устройству, иначе при общении с более быстрым медленное может воспринять команды на себя и работа станет непредвиденной). Если подключение по I2C шине верное - данная программа это покажет.

Предлагаю полный спектр услуг по разработке систем автоматики и автоматизации бытового и промышленного направления. Имею в наличии готовые модульные решения для системы «умный дом»: вентиляция, отопление, освещение, дистанционное управление. Поверьте, «Умный дом», сегодня, уже не роскошь, а вполне ДОСТУПНАЯ для каждого из нас РЕАЛЬНОСТЬ!)))