Arduino работа с аналоговыми входами. Простое повышение точности АЦП Arduino

21.04.2019 Фото и видео

На этом занятии рассмотрим, аналоговые порты Arduino A0-A5. Разберем принцип работы аналоговых портов, что к ним можно подключать. Соберем с помощью макетной платы схему светильника с управляемой яркостью, чтобы с помощью потенциометра (переменного резистора) можно было изменять яркость свечения светодиода. Рассмотрим директиву #define и analogRead в языке Ардуино IDE.

Устройство и принцип работы потенциометра

Переменный резистор (потенциометр) поворотом ручки изменяет сопротивление в электрической цепи от нуля до номинального сопротивления в 10 кОм. Потенциометр сделан состоит из токопроводящей поверхности — плоского постоянного резистора с двумя контактами и скользящего по поверхности токосъемника. Потенциометр предназначен для регулировки напряжения в электрической цепи.

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

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

Аналоговые входы на Ардуино

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


Небольшая цена деления шкалы позволяет с большой точностью получать значения практически любой физической величины. Чтобы считать показания на аналоговом входе следует использовать функцию analogRead . Аналоговые порты, как цифровые Ардуино можно сделать с помощью команды digitalRead — используется для считывания данных с кнопки и digitalWrite — можно подключить светодиод.

Светильник с управляемой яркостью

На этом занятии мы соберем электрическую схему светильника с управляемой яркостью. С помощью потенциометра мы сможем изменять яркость светодиода, подключенного к пину 9. Потенциометр подключается крайними ножками к портам 5V и GND, со средней ножки снимается значение напряжения на аналоговый вход A0.

Для занятия нам понадобятся следующие детали:

  • плата Arduino Uno / Arduino Nano / Arduino Mega;
  • макетная плата;
  • потенциометр;
  • 1 светодиод и резистор 220 Ом;
  • провода «папа-папа».

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

Скетч для Ардуино и потенциометра

// Присваиваем имя для пина со светодиодом (англ. «led») #define LED_PIN 9 // Присваиваем имя для пина с потенциометром (англ. «potentiometer») #define POT_PIN A0 void setup () { // пин со светодиодом будет выходом (англ. «output») pinMode (LED_PIN, OUTPUT ); // пин с потенциометром будет входом (англ. «input») pinMode (POT_PIN, INPUT ); // Запускаем монитор последовательного порта // снимите комментарий // Serial.begin(9600); } void loop () { // заявляем, что будем использовать 2 переменные - rotation и brightness // хранить в переменных будем только целые числа (англ. «integer») int rotation, brightness; // rotation равна значениям с потенциометра в интервале от 0 до 1023 rotation = analogRead (POT_PIN); // переменная brightness будет равна rotation делённое на 4 // brightness может быть только целым числом, дробная часть будет отброшена // в итоге переменная brightness будет находится в пределах от 0 до 255 brightness = rotation / 4; // выдаём напряжение, рассчитанное по формуле brightness = rotation / 4 analogWrite (LED_PIN, brightness); // Выдаем значение rotation на монитор последовательного порта // снимите комментарий // Serial.println(rotation); // снимите комментарий // delay(1000); }

Пояснения к коду:

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

В 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(), которая предназначена для пропорционального преобразования данных в новый диапазон значений.

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

Выбор этого источника происходит при вызове функции analogReference(type) , где type может принимать одно из трех значений:

  • DEFAULT : напряжение питания, около 5 Вольт (по умолчанию, после старта скетча);
  • INTERNAL : встроенный ИОН - 1.1 Вольт для ATmega168 и 2.56 Вольт для ATmega8;
  • EXTERNAL : напряжение на пине AREF.
Я не случайно написал "около 5 Вольт", потому что в действительности питание шины USB составляет 4,40 .. 5,25 В, а стабилизатора L7805 - 4,65 .. 5,35 В. Простой математический подсчет показывает, что диапазон 0,7 В "выливается" в 14% от ожидаемых 5.00 Вольт. А теперь обратите внимание, что один шаг нашего 10-битного АЦП составляет 5.00/1024 = 0,0048828, и 0,7 Вольт в пересчете analogRead составит 143 единицы.

Это подводит нас к грустному, но неотвратимому выводу - точность лучше одного вольта при использовании DEFAULT нам заказана . Может быть, нам поможет INTERNAL?

Тут нас будет ждать второе разочарование - дело в том, что внутренний источник опорного напряжения ATmega требует предварительной калибровки . Надо взять эталонный источник напряжения, подать его на аналоговый пин и сравнить с внутренним, получив таким образом поправку, компенсирующую т.н. систематическую погрешность. Полученное значение надо сохранить в EEPROM микроконтроллера, поскольку оно индивидуально для каждого конкретного чипа, а в скетче считывать после старта. Немного занудно, но ничего сложного. Но, быть может, есть более простой путь?

Надо каким-то образом подать внешнее опорное напряжение на наш АЦП, да поточнее (это вариант EXTERNAL). В этом нам могут помочь:

Этот прием я использовал в схеме SMPReaderUSB , для измерения напряжения внутренней литиевой батареи модуля МПО-10. Резистор R9 22К выбран с таким расчетом, чтобы через LM385 протекал небольшой ток, как и положено по его документации. Измеряемый вход BATT притянут через R10 22K к земле на тот случай, если модуль не подключен и вход ADC0 повис в воздухе.

Далее, происходит считывание с двух аналоговых пинов - к одному подключен измеряемый источник напряжения BATT (ADCm), к другому - LM385 (ADC385). Оба значения передаются в хост-программу на PC, которая вычисляет пропорцию:

Um = ADCm * 1,235 / ADC385

Значение опорного напряжения в этой формуле не участвует, и хотя зависимость по-прежнему есть, выведя это значение из формулы мы понизили его влияние на пару порядков (речь про ошибку квантования). Такой способ позволяет улучшить точность до 0,03 В, что - согласитесь - для Arduino весьма неплохо!

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 шине верное - данная программа это покажет.

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