Аналоговые измерения с Arduino. Создание зарядного устройства, управляемого Arduino

Вайбер на компьютер 14.07.2019
Вайбер на компьютер

С некоторыми дополнениями.

Мало известная фишка Ардуино и многих других AVR чипов это возможность измерить внутренний источник опорного напряжения 1.1 В. Эта функция может быть использована для повышения точности функции Arduino — analogRead при использовании стандартного опорного напряжения 5 В (на платформах с напряжением питания 5 В) или 3.3 В (на платформах с напряжением питания 3.3 В). Она также может быть использована для измерения Vcc , поданного на чип , обеспечивая средство контроля напряжения батареи без использования драгоценных аналоговый выводов .

Мотивация

Есть, по крайней мере, две причины для измерения напряжения, питающего наш Arduino (Vcc). Одним из них является наш проект, питающийся от батареи, если мы хотим следить за уровнем напряжения батареи. Кроме того, когда питание от батареи (Vcc) не может быть 5,0 вольт(например питание от 3-х элементов 1.5 В), а мы хотим сделать аналоговые измерения более точными - мы должны использовать либо внутренний источник опорного напряжения 1,1 В либо внешний источник опорного напряжения. Почему?

Обычно предполагают при использовании analogRead () то, что аналоговое напряжение питания контроллера составляет 5.0 вольт, когда в действительности это может быть совсем не так(например питание от 3-х элементов 1.5 В). Официальная документация Arduino даже может привести нас к этому неправильному предположению. Дело в том, что питание не обязательно 5,0 вольт, независимо от текущего уровня это питание подано на Vcc чипа. Если наше питание не стабилизировано или если мы работаем от аккумулятора, это напряжение может меняться совсем немного. Вот пример кода, иллюстрирующий эту проблему:

Double Vcc = 5.0; // не обязательно правда int value = analogRead(0); / читаем показания с А0 double volt = (value / 1023.0) * Vcc; // верно только если Vcc = 5.0 вольт Для того чтобы измерить напряжение точно, необходимо точное опорное напряжение. Большинство чипов AVR обеспечивает три источника опорного напряжения:

  • 1,1 в от внутреннего источника, в документации он проходит как bandgap reference (некоторые из них 2,56 В, например ATMega 2560). Выбор осуществляется функцией analogReference() с параметром INTERNAL : analogReference(INTERNAL) ;
  • внешний источник опорного наптяжения, на ардуинке подписан AREF. Выбор: analogReference(EXTERNAL);
  • Vcc - источник питания самого контроллера. Выбор: analogReference(DEFAULT).

В Arduino нельзя просто взять и подключить Vcc к аналоговому пину напрямую - по умолчанию AREF связан с Vcc и вы всегда будете получать максимальное значение 1023, от какого бы напряжения вы не питались. Спасает подключение к AREF источника напряжения с заранее известным, стабильным напряжением, но это - лишний элемент в схеме.

Еще можно соединить Vcc с AREF через диод : падение напряжение на диоде заранее известно, поэтому вычислить Vcc не составит труда. Однако, при такой схеме через диод постоянно протекает ток , сокращая жизнь батареи, что тоже не очень удачно.

Источник внешнего опорного напряжения является наиболее точным, но требует дополнительных аппаратных средств. Внутренний ИОН стабильным, но не точен + / - 10% отклонение. Vcc является абсолютно ненадежен в большинстве случаев. Выбор внутреннего источника опорного напряжения является недорогим и стабильным, но большую часть времени, мы хотели бы измеряет большее напряжение чем 1.1 В, так что использование Vcc является наиболее практичным, но потенциально наименее точным. В некоторых случаях оно может быть очень ненадежным!

Как это сделать

Многие чипы AVR включая серию ATmega и ATtiny обеспечивают средства для измерения внутреннего опорного напряжения. Зачем это нужно? Причина проста - путем измерения внутреннего напряжения, мы можем определить значение Vcc. Вот как:

  1. Установить источник опорного напряжения по умолчанию: analogReference(DEFAULT); . Используем как источник - Vcc.
  2. Снять показания АЦП для внутреннего источника 1.1 В.
  3. Расчитать значение Vcc основываясь на измерении 1.1 В по формуле:

Vcc * (Показания АЦП) / 1023 = 1.1 В

Из чего следует:

Vcc = 1,1 В * 1023 / (Показания АЦП)

Собираем все вместе и получаем код:

long readVcc() { // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(75); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts }

Использование

Проверка напряжения Vcc или батареи

Вы можете назвать эту функцию – readVcc(), если Вы хотите мониторить Vcc. Примером может служить для проверка уровня заряда батареи. Вы также можете использовать её для определения подключены ли Вы к источнику питания или работаете от батареи.

Измерение Vcc для опорного напряжения

Вы также можете использовать её, чтобы получить правильное значение Vcc для использования с analogRead (), когда вы используете опорное напряжение (Vcc). Пока Вы не используете стабилизированный источник питания, Вы не можете быть уверенны, что Vcc = 5.0 вольт. Эта функция позволяет получить правильное значение. Хотя есть один нюанс….

В одной из статей я сделал заявление, что эта функция может использоваться, чтобы улучшить точность аналоговых измерений в тех случаях, когда Vcc было не совсем 5.0 вольт. К сожалению, эта процедура не будет давать точный результат. Почему? Это зависит от точности внутреннего источника опорного напряжения. Спецификация дает номинальное напряжение 1.1 вольт, но говорится, что оно может отличаться до 10%. Такие измерения могут быть менее точными, чем наш источник питания Arduino!

Повышаем точность

Пока большие допуски внутреннего источника питания 1.1 В. значительно ограничивают точность измерений при использовании в серийном производстве, для индивидуальных проэктов мы можем добиться большей точности. Сделать это просто, просто измерив Vcc с помощью вольтметра и нашей функции readVcc(). Далее заменяем константу 1125300L новой переменной:

scale_constant = internal1.1Ref * 1023 * 1000

internal1.1Ref = 1.1 * Vcc1 (показания_вольтметра) / Vcc2 (показания_функции_readVcc())

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

Вывод

С этой маленькой функцией можно сделать многее. Вы можете использовать стабильное опорное напряжение близкое к 5.0 В не имея на самом деле 5.0 В на Vcc. Вы можете измерять напряжение вашей батареи или даже увидеть на каком вы питание от батареи или от стационарного источника питания.

И наконец, код будет поддерживать все Arduino, включая новый Leonardo, а также чипы ATtinyX4 и ATtinyX5 серий.

В этой статье приводится интересная схема для любителей экспериментов и Arduino . В ней представлен простой цифровой вольтметр, который может безопасно измерять постоянное напряжение в диапазоне от 0 до 30 В. Сама плата Arduino может питаться от стандартного источника 9 В.



Как известно, с помощью аналогового входа Arduino можно измерить напряжение от 0 до 5 В (при стандартном опорном напряжении 5 В). Но этот диапазон можно расширить, воспользовавшись делителем напряжения.


Делитель понижает измеряемое напряжение до приемлемого для аналогового входа уровня. Затем специально написанный код высчитывает фактическое напряжение.



Аналоговый датчик в составе Arduino определяет напряжение на аналоговом входе и преобразует его в цифровой формат, воспринимаемый микроконтроллером. К аналоговому входу A0 мы подключаем делитель напряжения, образованный сопротивлениями R1 (100K) и R2 (10K). С такими значениями сопротивлений на Arduino можно подавать до 55 В, поскольку коэффициент деления в данном случае получается 11, поэтому 55В/11=5В. Для того, чтобы быть уверенным в безопасности измерений для платы, лучше проводить измерение напряжения в диапазоне от 0 до 30 В.



Если показания дисплея не соответствуют показанием поверенного вольтметра, следует использовать прецизионный цифровой мультиметр для нахождения точных значений R1 и R2. При этом в коде нужно будет заменить R1=100000.0 и R2=10000.0 своими значениями. Затем стоит проверить питание, измерив на плате напряжение между 5V и GND. Напряжение может быть 4.95 В. Тогда в коде vout = (value * 5.0) / 1024.0 нужно заменить 5.0 на 4.95. Желательно использовать прецизионные резисторы с погрешностью не более 1%. Помните, что напряжение выше 55 В может вывести плату Arduino из строя!



#include LiquidCrystal lcd(7, 8, 9, 10, 11, 12); int analogInput = 0; float vout = 0.0; float vin = 0.0; float R1 = 100000.0; // сопротивление R1 (100K) float R2 = 10000.0; // сопротивление R2 (10K) int value = 0; void setup(){ pinMode(analogInput, INPUT); lcd.begin(16, 2); lcd.print("DC VOLTMETER"); } void loop(){ // считывание аналогового значения value = analogRead(analogInput); vout = (value * 5.0) / 1024.0; vin = vout / (R2/(R1+R2)); if (vin<0.09) { vin=0.0;// обнуляем нежелательное значение } lcd.setCursor(0, 1); lcd.print("INPUT V= "); lcd.print(vin); delay(500); }


Используемые элементы:


Плата Arduino Uno
Резистор 100 КОм
Резистор 10 КОм
Резистор 100 Ом
Потенциометр 10 КОм
LCD-дисплей 16×2

Если вы очень переживаете за расход электро энергии и жуть как хочется вычислить виновника - это ваш день. Мы соберем датчик тока напишем простую логику для обработки входных значений для пересчета значений в киловаты/ч.

Для сборки я использовал плату Arduino nano (никто не мешаем вам использовать тот же код для ESP или STM плат), LCD экранный шилд, резистор на 56 Ом, резисторы 100 кОм, конденсатор 10 мКф, датчик тока CT - Talema AC103 (с номинальным измерением 30A и максимальным 75A).

Что такое датчик тока?


Датчик тока - это магнитопровод с зазором и обмоткой компенсации, а так же встроенный датчик Холла и плата управления. Датчик Холла размещается в зазоре магнитопровода и реагирует на создаваемое катушкой магнитное поле. Чем сильнее напряженность магнитного поля, тем сильнее датчик Холла подает сигнал, который усиливается платой управления.
Датчики тока бывают для измерения переменного тока и постоянного тока. Наш - CT-Talema AC103 - для переменного.

Соберем наше устройство согласно схеме:


LCD шилд уже имеет выводы для подключения наших аналоговых портов для измерения сигнала - и это удобно.
Через датчик тока нужно пропустить единственный вводной кабель фазы т.к. на нулевой провод зачастую приходит не все напряжение - часть может уходить через заземление.


Не забываем, что нам нужно выполнить калибровку нагрузочного резистора R3. Формула расчета R = V / I - R = 2,5 / 0,042 = 59,5 Ом где 2,5 - опорное напряжение на плате, а 42mA - потребление платы. По тому принимаем самый близкий резистор по номиналу - 56 Ом.
Для деление основного напряжения питания до опорного 5/2 вам потребуется поставить два одинаковых резистора R1 и R2.

Остается только загрузить пример кода в Arduino:

//Michael Klements //The DIY Life //27 October 2014 #include int currentPin = 1; //Assign CT input to pin 1 double kilos = 0; int peakPower = 0; LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //Assign LCD screen pins, as per LCD shield requirements void setup() { lcd.begin(16,2); // columns, rows. use 16,2 for a 16x2 LCD, etc. lcd.clear(); lcd.setCursor(0,0); // set cursor to column 0, row 0 (the first row) lcd.print("Running"); } void loop() { int current = 0; int maxCurrent = 0; int minCurrent = 1000; for (int i=0 ; i<=200 ; i++) //Monitors and logs the current input for 200 cycles to determine max and min current { current = analogRead(currentPin); //Reads current input and records maximum and minimum current if(current >= maxCurrent) maxCurrent = current; else if(current <= minCurrent) minCurrent = current; } if (maxCurrent <= 517) { maxCurrent = 516; } double RMSCurrent = ((maxCurrent - 516)*0.707)/11.8337; //Calculates RMS current based on maximum value int RMSPower = 220*RMSCurrent; //Calculates RMS Power Assuming Voltage 220VAC, change to 110VAC accordingly if (RMSPower > peakPower) { peakPower = RMSPower; } kilos = kilos + (RMSPower * (2.05/60/60/1000)); //Calculate kilowatt hours used delay (2000); lcd.clear(); lcd.setCursor(0,0); // Displays all current data lcd.print(RMSCurrent); lcd.print("A"); lcd.setCursor(10,0); lcd.print(RMSPower); lcd.print("W"); lcd.setCursor(0,1); lcd.print(kilos); lcd.print("kWh"); lcd.setCursor(10,1); lcd.print(peakPower); lcd.print("W"); }

Завершающим шрихом нашей установки станет калибровка. Ее лучше выполнять при включенной эталонной нагрузке известной мощности. Для этого хорошо подходят мощные лампы накаливания. Возьмем лампу на 100 Ват. Включаем плату и высчитываем поправочный коэффициент:
Двойной RMSCurrent = ((maxCurrent - 516) * 0,707) /11,8337 где 11.8337 - подобранный коэффициент для компенсации расхождений в измерениях.

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

Как известно, ATmega может питаться от широкого диапазона напряжений, поэтому может оставаться "в строю" даже в случае постепенного разряда батареи, выражающегося в уменьшении напряжения. Ситуация эта в робототехнике более чем стандартная, за примером далеко ходить не надо: возьмем проект Voyager . Это робот-космический аппарат, запущенный за пределы солнечной системы и потому лишенный способности питаться от солнечных батарей . На него установлены радиоизотопные термические генераторы (ядерные батарейки), которые на момент старта выдавали 30В / 470Вт, но каждый год они теряют 0.78% своей мощности. Соответственно, в настоящий момент осталось около 60% от первоначальной, и приходится включать исследовательские подсистемы поочередно, чтобы не перегрузить генераторы.

В Arduino нельзя просто взять и подключить Vcc к аналоговому пину напрямую - по умолчанию AREF связан с Vcc и вы всегда будете получать максимальное значение 1023, от какого бы напряжения вы не питались. Спасает подключение к AREF источника напряжения с заранее известным, стабильным напряжением, но это - лишний элемент в схеме.

Еще можно соединить Vcc с AREF через диод : падение напряжение на диоде заранее известно, поэтому вычислить Vcc не составит труда. Однако, при такой схеме через диод постоянно протекает ток , сокращая жизнь батареи, что тоже не очень удачно.

Как же выкрутиться из положения, не добавляя новых элементов в схему и не сокращая время работы батареи? Оказывается, выход - есть, и поможет нам внутренний источник опорного напряжения 1.1В в ATmega (в документации он проходит как bandgap reference), которое не зависит от Vcc. Получается такая формула:

V_BAT=(1.1*1024)/analogRead(14);

где V_BAT - это напряжение Vcc в вольтах, а analogRead(14) - результат прямого чтения АЦП из канала 14.

В Arduino разрешены чтения только из каналов 0-7 (не удивляйтесь, если нашли на своем Diecimila или Duemilanove только 0-5 , возьмите, к примеру, Seeeduino ;)

Чтобы появилась возможность отправлять данные в другие каналы, в том числе в 14, надо изменить маску в библиотеке-ядре Arduino. Для этого откройте файл hardware\cores\arduino\wiring_analog.c и найдите там строку:

ADMUX = (analog_reference

замените ее на:

ADMUX = (analog_reference

После этого можно написать вот такой скетч:

uint16_t raw_bandgap = 0; // значение внутреннего bandgap
float volt_battery = 0.0;

void setup (){
Serial .begin (57600);
}

void loop (){
// Чтение напряжения батареи
analogReference (DEFAULT ); // использовать Vcc как AREF
// холостое чтение после смены AREF (см. 23.5.2 в руководстве)
raw_bandgap = analogRead (14); // измерить значение внутреннего bandgap
volt_battery = (1.1 * 1024) / raw_bandgap; // вычислить Vcc
Serial .print (volt_battery);
Serial .println (" v_bat" );
delay (1000);
}

Тем, кто хочет знать напряжение на батарее, стоит иметь ввиду, что при наличии регулятора (типа L7805CV), мы будем измерять напряжение Vout регулятора, а в случае наличия входного диода - надо учесть напряжение, которое на нем падает.

Вот так выглядят вычисляемые значения в Seeeduino , в момент переключения с питания 5В на 3,3В:

При этом мой вольтметр показывает меньшие значения. Но, тем не менее - скетч работает;)

UPD: Нашел примеры, в которых вместо константы 1.1 используется 1.05. Результат получается гораздо ближе к показаниям вольтметра, ищу теоретическую базу, способную объяснить этот факт...



Рекомендуем почитать

Наверх