Arduino и добавленная к ней схема заряда могут быть использованы для мониторинга и управления зарядкой никель-металл-гидридных аккумуляторов, например, так:

Законченное устройство

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

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

Комплектующие

Список комплектующих слева направо:

  • мощный резистор 10 Ом (минимум 5 ватт);
  • резистор 1 МОм;
  • конденсатор 1 мкФ;
  • MOSFET транзистор IRF510;
  • датчик температуры TMP36;
  • источник питания 5 вольт;

Как заряжать NiMH AA аккумуляторы

Увеличение скорости заряда увеличивает риск повреждения аккумулятора.

Существует много способов зарядки NiMH аккумуляторов. Выбор используемого вами метода главным образом зависит от того, как быстро вы хотите зарядить аккумулятор. Скорость заряда измеряется по отношению к емкости батареи. Если ваша батарея обладает емкостью 2500 мАч, и вы заряжаете ее током 2500 мА, то вы заряжаете ее со скоростью 1C. Если вы заряжаете этот же аккумулятор током 250 мА, то вы заряжаете его со скоростью C/10.

Во время быстрой зарядки аккумулятора (со скоростью выше C/10), вам необходимо тщательно контролировать напряжение на батарее и ее температуру, чтобы не перезарядить ее. Это может серьезно повредить аккумулятор. Тем не менее, когда вы заряжаете аккумулятор медленно (со скоростью ниже C/10), у вас гораздо меньше шансов повредить батарею, если случайно перезарядите ее. Поэтому медленные методы зарядки, как правило, считаются более безопасными и помогут вам увеличить срок службы батареи. Поэтому в нашем самодельном зарядном устройстве мы будем использовать скорость заряда C/10.

Цепь заряда

Для данного зарядного устройства основой является схема для управления источником питания с помощью Arduino. Схема питается от источника напряжения 5 вольт, например, от адаптера переменного тока или компьютерного блока питания. Большинство USB портов не подходит для данного проекта из-за ограничений по току. Источник 5В заряжает батарею через мощный резистор 10 Ом и мощный MOSFET транзистор. MOSFET транзистор устанавливает величину тока, протекающего через батарею. Резистор добавлен как простой способ контроля тока. Контроль величины тока выполняется подключением каждого вывода резистора к аналоговым входным выводам Arduino и измерением напряжения с каждой стороны. MOSFET транзистор управляется выходным ШИМ выводом Arduino. Импульсы сигнала широтно-импульсной модуляции сглаживаются до постоянного напряжения фильтром на резисторе 1 МОм и конденсаторе 1 мкФ. Данная схема позволяет Arduino отслеживать и управлять током, протекающим через батарею.


Датчик температуры

Датчик температуры служит для предотвращения перезаряда батареи и обеспечения безопасности.

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

Держатель AA батареи перед и после установки на макетную плату

Код


Код для данного проекта довольно прост. Переменные в начале исходного кода позволяют настроить зарядное устройство путем ввода значений емкости батареи и точного сопротивления мощного резистора. Также добавлены и переменные безопасных порогов. Максимально допустимое напряжение на батарее устанавливается в значение 1,6 вольта. Максимальная температура батареи установлена на 35 градусов по Цельсию. Максимальное время заряда установлено на 13 часов. Если какой-либо из этих порогов безопасности будет превышен, зарядное устройство выключается.

В теле программы вы можете увидеть, что система постоянно измеряет напряжения на выводах мощного резистора. Это используется для расчета значений напряжения на батарее и протекающего через нее тока. Ток сравнивается с целевым значением, которое составляет C/10. Если рассчитанный ток отличается от целевого значения более, чем на 10 мА, система автоматически подстраивает выходное значение, чтобы подкорректировать его.

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

Int batteryCapacity = 2500; // значение емкости батареи в мАч float resistance = 10.0; // измеренное сопротивление мощного резистора int cutoffVoltage = 1600; // максимальное напряжение на батарее (в мВ), которое не должно быть превышено float cutoffTemperatureC = 35; // максимальная температура батареи, которая не должна быть превышена (в градусах C) //float cutoffTemperatureF = 95; // максимальная температура батареи, которая не должна быть превышена (в градусах F) long cutoffTime = 46800000; // максимальное время заряда в 13 часов, которое не должно быть превышено int outputPin = 9; // провод выходного сигнала подключен к цифровому выводу 9 int outputValue = 150; // значение выходного ШИМ сигнала int analogPinOne = 0; // первый датчик напряжения подключен к аналоговому выводу 0 float valueProbeOne = 0; // переменная для хранения значения на analogPinOne float voltageProbeOne = 0; // рассчитанное напряжение на analogPinOne int analogPinTwo = 1; // второй датчик напряжения подключен к аналоговому выводу 1 float valueProbeTwo = 0; // переменная для хранения значения на analogPinTwo float voltageProbeTwo = 0; // рассчитанное напряжение на analogPinTwo int analogPinThree = 2; // третий датчик напряжения подключен к аналоговому выводу 2 float valueProbeThree = 0; // переменная для хранения значения на analogPinThree float tmp36Voltage = 0; // рассчитанное напряжение на analogPinThree float temperatureC = 0; // рассчитанная температура датчика в градусах C //float temperatureF = 0; // рассчитанная температура датчика в градусах F float voltageDifference = 0; // разница между напряжениями на analogPinOne и analogPinTwo float batteryVoltage = 0; // рассчитанное напряжение на батарее float current = 0; // рассчитанный ток, протекающий через нагрузку в (мА) float targetCurrent = batteryCapacity / 10; // целевой выходной ток (в мА) устанавливается в значение // C/10 или 1/10 от емкости батареи float currentError = 0; // разница между целевым и фактическим токами (в мА) void setup() { Serial.begin(9600); // настройка последовательного интерфейса pinMode(outputPin, OUTPUT); // установить вывод, как выход } void loop() { analogWrite(outputPin, outputValue); // записать выходное значение в выходной вывод Serial.print("Output: "); // показать выходные значения для контроля на компьютере Serial.println(outputValue); valueProbeOne = analogRead(analogPinOne); // считать входное значение на первом пробнике voltageProbeOne = (valueProbeOne*5000)/1023; // рассчитать напряжение на первом пробнике в милливольтах Serial.print("Voltage Probe One (mV): "); // показать напряжение на первом пробнике Serial.println(voltageProbeOne); valueProbeTwo = analogRead(analogPinTwo); // считать входное значение на втором пробнике voltageProbeTwo = (valueProbeTwo*5000)/1023; // рассчитать напряжение на втором пробнике в милливольтах Serial.print("Voltage Probe Two (mV): "); // показать напряжение на втором пробнике Serial.println(voltageProbeTwo); batteryVoltage = 5000 - voltageProbeTwo; // рассчитать напряжение на батарее Serial.print("Battery Voltage (mV): "); // показать напряжение на батарее Serial.println(batteryVoltage); current = (voltageProbeTwo - voltageProbeOne) / resistance; // рассчитать ток заряда Serial.print("Target Current (mA): "); // показать целевой ток Serial.println(targetCurrent); Serial.print("Battery Current (mA): "); // показать фактический ток Serial.println(current); currentError = targetCurrent - current; // разница между целевым и измеренным токами Serial.print("Current Error (mA): "); // показать ошибку установки тока Serial.println(currentError); valueProbeThree = analogRead(analogPinThree); // считать входное значение третьего пробника, tmp36Voltage = valueProbeThree * 5.0; // преобразуя его в напряжение tmp36Voltage /= 1024.0; temperatureC = (tmp36Voltage - 0.5) * 100 ; // преобразование, исходя из зависимости в 10 мВ на градус со сдвиком в 500 мВ // ((напряжение - 500 мВ) умножить на 100) Serial.print("Temperature (degrees C) "); // показать температуру в градусах Цельсия Serial.println(temperatureC); /* temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; //преобразовать в градусы Фаренгейта Serial.print("Temperature (degrees F) "); Serial.println(temperatureF); */ Serial.println(); // дополнительные пустые строки, чтобы облегчить чтение данных при отладке Serial.println(); if(abs(currentError) > 10) // если ошибка установки тока достаточно велика, то подстроить выходное напряжение { outputValue = outputValue + currentError / 10; if(outputValue < 1) // выходное значение никогда не может быть ниже 0 { outputValue = 0; } if(outputValue > 254) // выходное значение никогда не может быть выше 255 { outputValue = 255; } analogWrite(outputPin, outputValue); // записать новое выходное значение } if(temperatureC > cutoffTemperatureC) // остановить зарядку, если температура батареи превысила безопасный порог { outputValue = 0; Serial.print("Max Temperature Exceeded"); } /* if(temperatureF > cutoffTemperatureF) // остановить зарядку, если температура батареи превысила безопасный порог { outputValue = 0; } */ if(batteryVoltage > cutoffVoltage) // остановить зарядку, если напряжение на батарее превысило безопасный порог { outputValue = 0; Serial.print("Max Voltage Exceeded"); } if(millis() > cutoffTime) // остановить зарядку, если время заряда превысило порог { outputValue = 0; Serial.print("Max Charge Time Exceeded"); } delay(10000); // задержка в 10 секунд перед следующей итерацией цикла }

Скачиваемую версию исходного кода вы можете найти по ссылке, приведенной ниже.

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, который предназначен для измерения частоты и напряжения в розетке с отображением результатов на дисплее 1602A . Прибор очень прост в изготовлении, благодаря применению готового модуля ARDUINO UNO.

Принципиальная схема

Индикатором служит ЖК-дисплей типа 1602А, он стандартный, на основе контроллера HD44780. Обозначение 1602А фактически значит, что он на две строки по 16 символов в строке.

Основой прибора служит ARDUINO UNO, это относительно недорогой готовый модуль, - небольшая печатная плата, на которой расположен микроконтроллер ATMEGA328, а так же вся его «обвязка», необходимая для его работы, включая USB-программатор и источник питания.

Прибор питается от электросети и измеряет вышеуказанные параметры. Источник питания одновременно служит и датчиком для получения данных о частоте и напряжении в электросети. Источник питания сделан на основе маломощного готового трансформатора Т1, у которого есть две одинаковые обмотки по 9V переменного напряжения каждая.

Рис. 1. Принципиальная схема измерителя частоты и значения напряжения в электросети, использованы Arduino Uno и 1602A.

Одна обмотка служит для получения напряжения питания. Переменное напряжение с неё поступает на выпрямительный мост на диодах VD1-VD4. Конденсатор С3 сглаживает пульсации выпрямленного напояжения.

На С3 полVчается постоянное напряжение около 12V, которое поступает на разъем для подачи питания на плату ARDUINO UNO, на вход имеющегося на этой плате стабилизатора напряжения 5V, которым и питается вся плата и индикатор Н1.

Другая вторичная обмотка трансформатора служит датчиком измерения параметров электросети. Переменное напряжение с неё через R1 поступает на формирователь импульсов с частотой электросети, выполненный на транзисторе VТ1 по схеме ключа. Эти импульсы поступают на цифровой порт D10 платы ARDUINO UNO.

Переменное напряжение сети измеряется при помощи выпрямителя на диоде VD5 и конденсаторе С2. Величина переменного напряжения в сети определяется по величине постоянного напряжения на этом конденсаторе и через настраиваемый делитель на резисторах R4 и R5 поступает на аналоговый вход А1 платы ARDUINO UNO.

Программа

Программа на C++ с подробными комментариями приведена в таблице 1. Для измерения частоты используется функция pulseln , которая измеряет в микросекундах длительность положительного либо отрицательного перепада входного импульса.

Таблица 1.

Рис. 2. Исходный код программы для измерения частоты и значения напряжения в электросети.

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

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

Htime=pulseln(10,HIGH);

Ltime=pulseln(10,LOW);

Затем, происходит вычисление полного периода в строке:

Ttime=Htime+Ltime ;

И потом, вычисление частоты в строке:

frequency=1000000/Ttime;

Действие программы по измерению напряжения основано на чтении данных с аналогового входа и расчета результата измерения. Выход аналогового порта преобразуется АЦП микроконтроллера в цифровую форму. Для получения результата в единицах вольт, нужно его умножить на 5 (на опорное напряжение, то есть, на напряжение питания микроконтроллера) и разделить на 1024.

Для того чтобы можно было измерять напряжение более 5V, вернее, более напряжения питания микроконтроллера, потому что реальное напряжение на выходе 5-вольтового стабилизатора на плате ARDUINO UNO может отличаться от 5V, и обычно немного ниже, нужно на входе применить обычные резистивные делители. Здесь это делитель напряжения на резисторах R5 и R4.

К тому же играет роль и применение трансформатора, а так же отличие значение постоянного напряжения от переменного. Поэтому добавляется исходный коэффициент деления величиной 0,016. Чтение данных с аналогового порта происходит в строке:

vout=analogRead(analogInput);

Затем, производится вычисление фактического напряжения с учетом коэффициента деления делителя входного напряжения:

volt=vout*5.0/1024.0/0.016;

В этой строке число 5.0 - это напряжение на выходе стабилизатора платы ARDUINO UNO. В идеале должно быть 5V, но для точной работы вольтметра это напряжение нужно предварительно измерить.

Подключите источник питания напряжением 12V и измерьте достаточно точным вольтметром напряжение +5V на разъеме POWER платы. Что будет, то и вводите в эту строку вместо 5.0, например, если будет 4.85V, строка будет выглядеть так:

volt=vout*4.85/1024.0/0.016;

Затем, результаты измерений выводятся на ЖК-дисплей. Напряжение вносится в первую строку дисплея, а частота во вторую. Единицы измерения указаны как «V» и «Hz». Что касается измерения частоты, - налаживание не требуется вообще. А вот для измерения напряжения нужно откалибровать прибор по образцовому подстройкой резистора R5.

Каравкин В. РК-10-17.

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

Плата Arduino UNO содержит 6 аналоговых входов предназначенных для измерения напряжения сигналов. Правильнее сказать, что 6 выводов платы могут работать в режиме, как дискретных выводов, так и аналоговых входов.

Эти выводы имеют номера от 14 до 19. Изначально они настроены как аналоговые входы, и обращение к ним можно производить через имена A0-A5. В любой момент их можно настроить на режим дискретных выходов.

pinMode(A3, OUTPUT); // установка режима дискретного вывода для A3
digitalWrite(A3, LOW); // установка низкого состояния на выходе A3

Чтобы вернуть в режим аналогового входа:

pinMode(A3, INPUT); // установка режима аналогового входа для A3

Аналоговые входы и подтягивающие резисторы.

К выводам аналоговых входов, так же как и к дискретным выводам, подключены подтягивающие резисторы. Включение этих резисторов производится командой

digitalWrite(A3, HIGH); // включить подтягивающий резистор к входу A3

Команду необходимо применять к выводу настроенному в режиме входа.

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

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

Собственно измерение напряжение на входах производится аналого-цифровым преобразователем (АЦП) с коммутатором на 6 каналов. АЦП имеет разрешение 10 бит, что соответствует коду на выходе преобразователя 0…1023. Погрешность измерения не более 2 единиц младшего разряда.

Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм. Это требование особенно важно при использовании резисторных делителей, подключенных к аналоговым входам платы. Сопротивление резисторов делителей не может быть слишком большим.

Программные функции аналогового ввода.

int analogRead(port)

Считывает значение напряжения на указанном аналоговом входе. Входное напряжение диапазона от 0 до уровня источника опорного напряжения (часто 5 В) преобразовывает в код от 0 до 1023.

При опорном напряжении равном 5 В разрешающая способность составляет 5 В / 1024 = 4,88 мВ.

Занимает на преобразование время примерно 100 мкс.

int inputCod; // код входного напряжения
float inputVoltage; // входное напряжение в В

inputCod= analogRead(A3); // чтение напряжения на входе A3
inputVoltage= ((float)inputCod * 5. / 1024.); // пересчет кода в напряжение (В)

void analogReference(type)

Задает опорное напряжение для АЦП. Оно определяет максимальное значение напряжения на аналоговом входе, которое АЦП может корректно преобразовать. Величина опорного напряжения также определяет коэффициент пересчета кода в напряжение:

Напряжение на входе = код АЦП * опорное напряжение / 1024.

Аргумент type может принимать следующие значения:

  • DEFAULT – опорное напряжение равно напряжению питания контроллера (5 В или 3,3 В). Для Arduino UNO R3 – 5 В.
  • INTERNAL – внутреннее опорное напряжение 1,1 В для плат с контроллерами ATmega168 и ATmega328, для ATmega8 – 2,56 В.
  • INTERNAL1V1 – внутреннее опорное напряжение 1,1 В для контроллеров Arduino Mega.
  • INTERNAL2V56 – внутреннее опорное напряжение 2,56 В для контроллеров Arduino Mega.
  • EXTERNAL – внешний источник опорного напряжения, подключается к входу AREF.

analogReference(INTERNAL); // опорное напряжение равно 1,1 В

Двухканальный вольтметр на Ардуино.

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

Решим, что вольтметр должен измерять напряжение в пределах не меньше 0…20 В и разработаем схему подключения входов вольтметра к плате Arduino UNO.

Если мы зададим опорное напряжение равным 5 В, то аналоговые входы платы будут измерять напряжение в пределах 0…5 В. А нам надо как минимум 0…20 В. Значит надо использовать делитель напряжения.

Напряжение на входе и выходе делителя связаны соотношением:

Uвыхода = (Uвхода / (R1 + R2)) * R2

Коэффициент передачи:

K = Uвыхода / Uвхода = R2 / (R1 + R2)

Нам необходим коэффициент передачи 1/4 (20 В * 1/4 = 5 В).

Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм. Поэтому выбираем резистор R2 равным 4,22 кОм. Рассчитываем сопротивление резистора R1.

0,25 = 4,22 / (R1 + 4,22)
R1 = 4,22 / 0.25 – 4,22 = 12,66 кОм

У меня с ближайшим номиналом нашлись резисторы сопротивлением 15 кОм. С резисторами R1 = 15 кОм и R2 = 4,22:

5 / (4,22 / (15 + 4,22)) = 22,77 В.

Схема вольтметра на базе Ардуино будет выглядит так.

Два делителя напряжения подключены к аналоговым входам A0 и A1. Конденсаторы C1 и C2 вместе с резисторами делителя образуют фильтры нижних частот, которые убирают из сигналов высокочастотные шумы.

Я собрал эту схему на макетной плате.

Первый вход вольтметра я подключил к регулируемому источнику питания, а второй к питанию 3,3 В платы Ардуино. Для контроля напряжения к первому входу я подключил вольтметр. Осталось написать программу.

Программа для измерения напряжения с помощью платы Ардуино.

Алгоритм простой. Надо:

  • с частотой два раза в секунду считывать код АЦП;
  • пересчитывать его в напряжение;
  • посылать измеренные значения по последовательному порту на компьютер;
  • программой монитор порта Arduino IDE отображать полученные значения напряжений на экране компьютера.

Приведу скетч программы сразу полностью.

// программа измерения напряжения
// на аналоговых входах A0 и A1

#include

время периода измерения
#define R1 15. // сопротивление резистора R1
#define R2 4.22 // сопротивление резистора R2


float u1, u2; // измеренные напряжения

void setup() {
Serial.begin(9600); //

MsTimer2::start(); // разрешение прерывания
}

void loop() {

// период 500 мс
if (timeCount >= MEASURE_PERIOD) {
timeCount= 0;

//

// чтение кода канала 2 и пересчет в напряжение
u2= ((float)analogRead(A1)) * 5. / 1024. / R2 * (R1 + R2);

// передача данных через последовательный порт
Serial.print("U1 = "); Serial.print(u1, 2);
Serial.print(" U2 = "); Serial.println(u2, 2);
}
}

// обработка прерывания 1 мс
void timerInterupt() {
timeCount++;
}

Поясню строчку, в которой пересчитывается код АЦП в напряжение:

// чтение кода канала 1 и пересчет в напряжение
u1= ((float)analogRead(A0)) * 5. / 1024. / R2 * (R1 + R2);

  • Считывается код АЦП: analogRead(A0) .
  • Явно преобразуется в формат с плавающей запятой: (float) .
  • Пересчитывается в напряжение на аналоговом входе: * 5. / 1024. Точка в конце чисел показывает, что это число с плавающей запятой.
  • Учитывается коэффициент передачи делителя: / R2 * (R1 + R2) .

Загрузим программу в плату, запустим монитор последовательного порта.

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

Измерение среднего значения сигнала.

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

Значения напряжения первого канала на экране монитора все время дергаются, скачут. А показания контрольного вольтметра вполне стабильны. Это объясняется тем, что контрольный вольтметр измеряет среднее значение сигнала, в то время как плата Ардуино считывает отдельные выборки каждые 500 мс. Естественно, момент чтения АЦП попадает в разные точки сигнала. А при высоком уровне пульсаций амплитуда в этих точках разная.

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

Решение – сделать несколько частых выборок и усреднить измеренное значение. Для этого:

  • в обработчике прерывания считываем код АЦП и суммируем его с предыдущими выборками;
  • отсчитываем время усреднения (число выборок усреднения);
  • при достижении заданного числа выборок – сохраняем суммарное значение кодов АЦП;
  • для получения среднего значения сумму кодов АЦП делим на число выборок усреднения.

Задача из учебника математики 8 класса. Вот скетч программы, двух канального вольтметра среднего значения.

// программа измерения среднего напряжения
// на аналоговых входах A0 и A1

#include

#define MEASURE_PERIOD 500 // время периода измерения
#define R1 15. // сопротивление резистора R1
#define R2 4.22 // сопротивление резистора R2

int timeCount; // счетчик времени
long sumU1, sumU2; // переменные для суммирования кодов АЦП
long avarageU1, avarageU2; // сумма кодов АЦП (среднее значение * 500)
boolean flagReady; // признак готовности данных измерения

void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
MsTimer2::set(1, timerInterupt); // прерывания по таймеру, период 1 мс
MsTimer2::start(); // разрешение прерывания
}

void loop() {

if (flagReady == true) {
flagReady= false;
// пересчет в напряжение и передача на компьютер
Serial.print("U1 = ");
Serial.print((float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
Serial.print(" U2 = ");
Serial.println((float)avarageU2 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
}
}

// обработка прерывания 1 мс
void timerInterupt() {

timeCount++; // +1 счетчик выборок усреднения
sumU1+= analogRead(A0); // суммирование кодов АЦП
sumU2+= analogRead(A1); // суммирование кодов АЦП

// проверка числа выборок усреднения
if (timeCount >= MEASURE_PERIOD) {
timeCount= 0;
avarageU1= sumU1; // перегрузка среднего значения
avarageU2= sumU2; // перегрузка среднего значения
sumU1= 0;
sumU2= 0;
flagReady= true; // признак результат измерений готов
}
}

В формулу пересчета кода АЦП в напряжение добавилось /500 – число выборок. Загружаем, запускаем монитор порта (Cntr+Shift+M).

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

Число выборок надо выбирать, учитывая:

  • число выборок определяет время измерения;
  • чем больше число выборок, тем меньше будет влияние помех.

Основным источником помех в аналоговых сигналах является сеть 50 Гц. Поэтому желательно выбирать время усреднения кратное 10 мс – времени полупериода сети частотой 50 Гц.

Оптимизация вычислений.

Вычисления с плавающей запятой просто пожирают ресурсы 8ми разрядного микроконтроллера. Любая операция с плавающей запятой требует денормализацию мантиссы, операцию с фиксированной запятой, нормализацию мантиссы, коррекцию порядка… И все операции с 32 разрядными числами. Поэтому необходимо свести к минимуму употребление вычислений с плавающей запятой. Как это сделать я расскажу в следующих уроках, но давайте хотя бы оптимизируем наши вычисления. Эффект будет значительный.

В нашей программе пересчет кода АЦП в напряжение записан так:

(float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2)

Сколько здесь вычислений, и все с плавающей запятой. А ведь большая часть вычислений – операции с константами. Часть строки:

/ 500. * 5. / 1024. / R2 * (R1 + R2)

(float)avarageU1 * 0.00004447756

Умные компиляторы сами распознают вычисления с константами и рассчитывать их на этапе компиляции. У меня возник вопрос, насколько умный компилятор Андруино. Решил проверить.

Я написал короткую программу. Она выполняет цикл из 10 000 проходов, а затем передает на компьютер время выполнения этих 10 000 циклов. Т.е. она позволяет увидеть время выполнения операций, размещенных в теле цикла.

// проверка оптимизации вычислений

int x= 876;
float y;
unsigned int count;
unsigned long timeCurrent, timePrev;

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

void loop() {
count++;
// y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);
// y= (float)x * 0.00004447756 ;

if (count >= 10000) {
count= 0;
timeCurrent= millis();
Serial.println(timeCurrent - timePrev);
timePrev= timeCurrent;
}
}

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

Т.е. 10 000 пустых циклов выполняются за 34 мс.

Затем я открыл строку:

y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);

повторяет наши вычисления. Результат 10 000 проходов за 922 мс или

(922 – 34) / 10 000 = 88,8 мкс.

Т.е. эта строка вычислений с плавающей запятой требует на выполнение 89 мкс. Я думал будет больше.

Теперь я закрыл эту строку комментарием и открыл следующую, с умножением на заранее рассчитанную константу:

y= (float)x * 0.00004447756 ;

Результат 10 000 проходов за 166 мс или

(166 – 34) / 10 000 = 13,2 мкс.

Потрясающий результат. Мы сэкономили 75,6 мкс на одной строке. Выполнили ее почти в 7 раз быстрее. У нас таких строк 2. Но ведь их в программе может быть и гораздо больше.

Вывод – вычисления с константами надо производить самим на калькуляторе и применять в программах как готовые коэффициенты . Компилятор Ардуино их на этапе компиляции не рассчитает. В нашем случае следует сделать так:

#define ADC_U_COEFF 0.00004447756 // коэффициент перевода кода АЦП в напряжение

Serial.print((float)avarageU1 * ADC_U_COEFF, 2);

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

О других способах оптимизации программ Ардуино я буду рассказывать в будущих уроках по мере необходимости. Но без решения этого вопроса невозможно разрабатывать сложные программы на 8ми разрядном микроконтроллере.

На сайте появился еще один урок (

В этой статье приводится интересная схема для любителей экспериментов и 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