Датчик напряжения ардуино. Секретный вольтметр в Arduino — измерение напряжения батареи средствами микроконтроллера

Схема самодельного прибора на основе 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 и добавленная к ней схема заряда могут быть использованы для мониторинга и управления зарядкой никель-металл-гидридных аккумуляторов, например, так:

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

Аккумуляторные батареи являются отличным способом для питания вашей портативной электроники. Они могут сэкономить вам много денег при правильной зарядке. Для того, чтобы вы могли получить максимальную отдачу от ваших аккумуляторных батарей, их необходимо правильно заряжать. Это означает, что вам необходимо хорошее зарядное устройство. Вы можете потратить кучу денег, купив готовое зарядное устройство, а можете получить удовольствие, сделав его сами. В данной статье мы рассмотрим, как можно создать зарядное устройство, управляемое 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 секунд перед следующей итерацией цикла }

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

Как известно, 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. Результат получается гораздо ближе к показаниям вольтметра, ищу теоретическую базу, способную объяснить этот факт...

  • Tutorial

Введение

Всем привет! После завершения цикла по датчикам были вопросы различного плана по измерению параметров потребления бытовых и не очень электроприборов. Кто сколько потребляет, как что подключать чтобы измерить, какие бывают тонкости и так далее. Пришло время раскрыть все карты в этой области.
В этом цикле статей мы рассмотрим тему измерения параметров электроэнергии. Этих параметров на самом деле очень даже большое количество, о которых я постараюсь постепенно рассказать небольшими сериями.
Пока в планах три серии:
  • Измерение электроэнергии.
  • Качество электроэнергии.
  • Устройства измерения параметров электроэнергии.
В процессе разбора будем решать те или иные практические задачи на микроконтроллерах до достижения результата. Разумеется, большая часть данного цикла будет посвящена измерению переменного напряжения и может пригодиться всем любителям контролировать электроприборы своего умного дома.
По итогам всего цикла мы изготовим некий умный электросчетчик с выходом в интернет. Совсем отъявленные любители контролировать электроприборы своего умного дома могут оказать посильную помощь в реализации коммуникационной части на базе, например MajorDomo. Сделаем OpenSource умный дом лучше, так сказать.
В этой серии в двух частях мы разберем следующие вопросы:
  • Подключение датчиков тока и напряжения в устройствах постоянного тока, а также однофазных и трехфазных цепей переменного тока;
  • Измерение действующих значений тока и напряжения;
  • Измерение коэффициента мощности;
  • Полная, активная и реактивная мощность;
  • Потребление электроэнергии;
Подкатом вы найдете ответы на первые два вопроса данного списка. Я намеренно не затрагиваю вопросы точности измерения показателей и с данной серии лишь радуюсь полученным результатам с точностью плюс-минус лапоть. Этому вопросу я обязательно посвящу отдельную статью в третьей серии.

1. Подключение датчиков


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


Рис 1. Понижающий преобразователь с ШИМ
Нашей задачей является обеспечение стабилизированного выходного напряжения. Кроме того, на основании информации с датчика тока возможно контролировать режим работы дросселя L1, не допуская его насыщения, а также реализовывать токовую защиту преобразователя. И честно говоря, вариантов установки датчиков особо и нет.
Датчик напряжения в виде резистивного делителя R1-R2, который единственный способен работать на постоянном токе, устанавливается на выходе преобразователя. Как правило специализированная микросхема преобразователя имеет вход обратной связи, и прилагает все усилия для того, чтобы на этом входе (3) оказался определённый уровень напряжения, прописанный в документации на микросхему. Например 1,25В. Если наше выходное напряжение с этим уровнем совпадает - все хорошо - мы напрямую подаем выходное напряжение на этот вход. Если нет, то устанавливаем делитель. Если нам надо обеспечить выходное напряжение в 5В, то делитель должен обеспечивать коэффициент деления 4, т. е. Например R1 = 30к, R2 = 10к.
Датчик тока обычно устанавливается между источником питания и преобразователем и на микросхему. По разности потенциалов между точками 1 и 2, и при известном сопротивлении резисторы Rs возможно определить текущее значение тока нашего дросселя. Устанавливать датчик тока между источников и нагрузкой не самая хорошая идея, так как конденсатор фильтра будет отрезан резистором от потребителей импульсных токов. Установка резистора в разрыв общего провода тоже нге сулит ничего хорошего - будет два земляных уровня с которыми возиться то еще удовольствие.
Проблемы падения напряжения можно избежать путем использования бесконтактных датчиков тока - например датчиков холла:


Рис 2. Бесконтактный датчик тока
Однако есть более хитрый способ измерения тока. Ведь на транзисторе точно также падает напряжение и через него течет тот же самый ток что и индуктивность. Следовательно, по падению напряжения на нем можно также определить текущее значение тока. Честно говоря, если посмотреть на внутреннюю структуру микросхем преобразователей, например, от Texas Instruments - то такой способ встречается так же часто как и предыдущие. Точность такого способа конечно не самая высокая, но для работы токовой отсечки этого вполне достаточно.


Рис 3. Транзистор в качестве датчика тока
Аналогично поступаем в других схемах подобных преобразователей, будь то повышающий или инвертирующий.
Однако необходимо отдельно упомянуть о трансформаторных прямоходовом и обратноходовом преобразователях.


Рис 4. Подключение датчиков тока в обратноходовых преобразователях
В них точно также может использоваться либо внешнее сопротивление, либо транзистор в его роли.
На этом с подключением датчиков в преобразователи постоянного тока мы закончили. Если у вас есть предложения по другим вариантам - с удовольствием дополню ими статью.
1.2 Подключение датчиков в однофазные цепи переменного тока
В цепях переменного тока у нас гораздо больший выбор возможных датчиков. Рассмотрим несколько вариантов.
Самый простой - использование резистивного делителя напряжения и токового шунта.


Рис 5.Подключение резисторных датчиков
Однако, у нее усть пара существенных недостатков:
Во-первых, либо мы обеспечим значительную амплитуду сигнала с токового шунта, выделив большое количество мощности на нем, либо будем довольствоваться малой амплитудой сигнала и впоследствии усиливать его. А во-вторых, резистор создает разность потенциалов между нейтралью сети и нейтралью прибора. Если прибор изолирован - то это не имеет значения, если же у прибора есть вывод заземления, то мы рискуем остаться без сигнала с датчика тока, так как закоротим его. Пожалуй стоит попробовать датчики, работающие на других принципах.
Например, воспользуемся трансформаторами тока и напряжения, либо датчиком тока на эффекте холла и трансформатором напряжения. Здесь гораздо больше возможностей по работе с оборудованием, так как нулевой провод не имеет потерь, а главное - в обоих случаях присутствует гальваническая развязка измерительного оборудования, что часто может пригодиться. Однако, необходимо учитывать, что трансформаторные датчики тока и напряжения имеют ограниченную частотную характеристику и если мы захотим измерить гармонический состав искажений, то у нас это не факт что выйдет.


Рис 6.Подключение трансформаторных и бесконтактных датчиков тока и напряжения
1.3 Подключение датчиков в многофазные цепи сетей переменного тока
В многофазных сетях наши возможности по подключению датчиков тока немного меньше. Связано это с тем, что токовый шунт использовать совсем не получится, так как разность потенциалов между шунтами фаз будет колебаться в пределах сотен вольт и мне не известен ни один контроллер общего применения, аналоговые входы которого способны выдержать такое издевательство.
Один способ использовать токовые шунты конечно есть - для каждого канала необходимо сделать гальванически развязанный аналоговый вход. Но гораздо проще и надежнее использовать другие датчики.
В своем анализаторе качества я использую резистивные делители напряжения и выносные датчики тока на эффекте холла.

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


Рис 8.Использование трансформаторов напряжения в трехфазной сети

2 Действующее значение тока и напряжения


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


Рис 9. Серия мгновенных значений напряжения
Наша задача - произвести подсчет действующего значения. Для начала воспользуемся формулой интеграла:
(1)
В цифровой системе приходится ограничиваться неким квантом времени, так что мы переходим к сумме:
(2)
Где - период дискретизации нашего сигнала, а - число отсчетов за период измерения. Где-то здесь я в видео начинаю втирать дичь про равенство площадей. Надо было выспаться в тот день. =)
В микроконтроллерах MSP430FE4252, которые применяются в однофазных электросчетчиках Меркурий, за период измерения равный 1, 2 или 4 секунд производится 4096 отсчетов. На T=1с и N=4096 мы и будем опираться в дальшейнем. Более того, 4096 точек в секунду позволят нам использовать алгоритмы быстрого преобразования фурье для определения гармонического спектра вплоть до 40 гармоники, как того требует ГОСТ. Но об этом в следующей серии.
Набросаем алгоритм для нашей программы. Нам требуется обеспечить стабильный запуск АЦП каждую 1/8192 секунды, так как у нас два канал и измерять мы будем эти данные попеременно. Для этого настроим таймер и сигнал прерывания будет автоматически перезапускать АЦП. Все АЦП так умеют.
Писать будущую программу будем на arduino, так как она у многих под рукой. У нас пока чисто академический интерес.
Имея частоту системного кварца 16МГц и 8-разрядный таймер (чтобы жизнь медом не казалась) нам необходимо обеспечить частоту срабатывания хоть какого прерывания таймера с частотой 8192Гц.
Печалимся по поводу того что 16МГц цело не делится как нам надо и итоговая частота работы таймера 8198Гц. Закрываем глаза на погрешность в 0,04% и все равно считываем по 4096 выборок на канал.
Печалимся по поводу того, что прерывание по переполнению в arduino занято расчетом времени (отвечает за millis и delay, так что это работать нормально перестанет), так что пользуемся прерыванием по сравнению.
А еще внезапно понимаем, что сигнал к нам приходит биполярный, и что msp430fe4252 с ним прекрасно справляется. Мы же довольствуемся униполярным АЦП, поэтому на операционном усилителе собираем простой преобразователь биполярного сигнала в униполярный:


Рис 10.Преобразователь биполярного сигнала в униполярный
Причем наша задача обеспечить колебание нашей синусоиды относительно половины опорного напряжения - тогда мы либо отнимем половину диапазона либо активируем опцию в настройках АЦП и получим знаковые значения.
В Arduino 10-разрядный АЦП, поэтому из беззнакового результата в пределах 0-1023 будем вычитать половину и получим -512- 511.
Проверяем модель, собранную в LTSpiceIV и убеждаемся, что все работает как надо. В видеоматериале дополнительно убеждаемся экспериментально.


Рис 11.результат моделирования. Зеленым исходный сигнал, синим - выходной

Скетч для Arduino для одного канала

void setup() { autoadcsetup(); DDRD |=(1<

Программа написана в среде Arduino IDE для микроконтроллера ATmega1280. На моей отладочной плате первые 8 каналов разведены для внутренних нужд платы поэтому используется канал ADC8. Возможно использовать данный скетч и для платы с ATmega168, однако необходимо выбрать правильный канал.
Внутри прерываний передергиваем пару служебных пинов чтобы наглядно видеть рабочую частоту оцифровки.
Пару слов о том, откуда взялся коэффициент 102. При первом запуске с генератора подавался сигнал различной амплитуды, с осциллографа считывалось показание действующего значения напряжения, а из консоли забиралось рассчитанное значение в абсолютных единицах АЦП.

Umax, В Urms, В Counted
3 2,08 212
2,5 1,73 176
2 1,38 141
1,5 1,03 106
1 0,684 71
0,5 0,358 36
0,25 0,179 19

Разделив значения третьего столбца на значения второго получаем в среднем 102. Это и будет наш «калибровочный» коэффициент. Однако можно заметить, что при снижении напряжения точность резко падает. Это происходит из-за низкой чувствительности нашего АЦП. Фактически 10 разрядов для точных расчётов катастрофически мало и если напряжение в розетке измерить таким образом вполне получится, то поставить 10-разрядный АЦП на измерение потребляемого нагрузкой тока будет преступлением против метрологии.

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

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

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

Плата 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ми разрядном микроконтроллере.

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