Проблема
В сети есть много примеров превращения Arduino в Амперметр и Вольтметр с использованием в качестве измерения тока датчика холла ACS712, а для напряжения — простого делителя напряжения на двух резисторах. Но вот незадача, большинство из них предполагает, что напряжение питания строго 5В. Однако, у меня USB порт дает всего 4.6В. По-умолчанию, АЦП Arduino в качестве опорного использует напряжение питания, поэтому большинство примеров в моем случае дают ошибку более 10%. Вроде есть способ использовать внутренний стабилизированный источник напряжения 1.1В, но на поверку оказывается, что и его показания плавают +-10% и к тому же, диапазон уменьшается в 5 раз. Для датчика тока все еще хуже. У моего датчика тока предел +-20А и чувствительность получается 0,1В/А. Т.е. чтобы померить 1А надо измерить напряжение хотябы с точностью 0.05В, что на порядок точнее чем дают примеры.
Решение
На микропроцессорах ATMEGA есть возможность «измерить» внутреннее напряжение. Правда точность «плавает» от образца к образцу и вдобавок погрешность вносят провода от Меги к датчикам, на которых падает часть напряжения. Однако, эту погрешность можно единожды измерить и внести в код программы. Есть правда еще и температурная зависимость датчиков, но в моем случае все измерения проходят при комнатной температуре, поэтому я ее не учитваю.
Идея в том, чтобы рассчитать поправочный коэффициент исходя из реального напряжения питания.
Из даташита на ACS712 ясно, что нулевая точка принимается равной половине напряжения питания Vcc/2. Чувствительность также зависит от напряжения питания (Vcc/2-Vcc*.1)/maxCurrent. Для maxCurrent=20А ниже приведен график из даташита.
Таким образом удалось получить точность измерения напряжения 0.01В и тока датчиком ACS712-20 в 0.2А, что очень неплохо.
// аналоговый пин подключенный к средней точке делителя напряжения
const int VoltageSensorPin = A0;
// аналоговый пин подключенный к выходу датчика тока ACS712
const int CurrentSensorPin = A1;
const int MaxCurrent = 20; // 20A для ACS712-20
float VoltageSensorValue = 0;
float CurrentSensorValue = 0;
// эту константу нужно подобрать рекурсивно при помощи эталонного вольтметра
// измеряем напряжение на выводе Vcc и подбираем коэффициент до совпадения
const float typVbg = 1.08; // 1.0 - 1.2
// рассчёт делителя
const float R1 = 30000; // 30 kOm
const float R2 = 7500; // 7.5 kOm
// коэффициент для расчёта напряжения на входе делителя
const float dividerRatio = (R1+R2)/R2;
void setup() {
Serial.begin(9600);
analogReference(DEFAULT);
}
void loop() {
float Vcc = readVcc();
// падение напряжения в питающих датчик тока проводах (замеряем вольтметром под нагрузкой)
float Vcca = Vcc - 0.02;
// коэффициент перевода имерений в напряжение
float voltRatio = Vcc/1023 ;
VoltageSensorValue = analogRead(VoltageSensorPin);
CurrentSensorValue = analogRead(CurrentSensorPin);
// рассчитаем в вольтах
float VoltageValue = VoltageSensorValue * voltRatio * dividerRatio;
float kVolt = (Vcca/2-Vcca*.1)/MaxCurrent;
float CurrentPinVoltage = voltRatio * CurrentSensorValue;
float CurrentValue = (Vcca/2 - CurrentPinVoltage)/kVolt;
// выведем результаты
Serial.print("CurrentSensorValue");
Serial.print(char(9));
Serial.print(CurrentSensorValue,0);
Serial.print(char(9));
Serial.print("CurrentPinVoltage");
Serial.print(char(9));
Serial.print(CurrentPinVoltage,3);
Serial.print(char(9));
Serial.print("CurrentValue");
Serial.print(char(9));
Serial.print(CurrentValue,3);
Serial.print(char(9));
Serial.print("batterySensorValue" );
Serial.print(char(9));
Serial.print(VoltageSensorValue);
Serial.print(char(9));
Serial.print("Vcc" );
Serial.print(char(9));
Serial.print(Vcc,3);
Serial.print(char(9));
Serial.print("batteryVoltage" );
Serial.print(char(9));
Serial.println(VoltageValue,3);
delay(100);
}
// функция вычисления напряжения питания
float readVcc() {
byte i;
float result = 0.0;
float tmp = 0.0;
ADCSRA |= (1<<ADEN);
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
// works on an Arduino 168 or 328
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(3); // Wait for Vref to settle
for (i = 0; i < 5; i++) {
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
tmp = (high<<8) | low;
tmp = (typVbg * 1023.0) / tmp;
result = result + tmp;
delay(5);
}
result = result / 5;
return result;
}
Последнее обновление 29.03.2022 в 11:00
Как на всё время находишь? )))
Да, это основная проблема.
Особенно красиво выглядит это:
const float dividerRatio = (R1+R2)/R2;
Даешь стране как можно больше операций с плавающей точкой!
Особенно R2/R2…
А в чем проблема? Это же константа, которая высчитывается один раз на этапе компиляции.
приветствую, хотел бы у вас заказать код связанный с измерением напряжения в сети автомобиля,хотелось что б на незначительные изменения напряжения через ардуино ускорялись обороты сервомоторчика
Спасибо за доверие. Этот блог для души. Обратитесь к фрилансерам, можно со ссылкой на мой блог. Будет дёшево и сердито.
Из даташита на ACS712 ясно, что нулевая точка принимается равной половине напряжения питания Vcc/2. Чувствительность также зависит от напряжения питания (Vcc/2-Vcc*.1)/maxCurrent. Для maxCurrent=20А ниже приведен график из даташита.
Для датчика на 20 А 100мВ/А… датчик +-20А …. 40А*на 0.1В=4В… Следовательно максимальное разброс 4В, а вход ардуино 1023 на 5В( ну или около того, в зависимости от измеренного). По вашей формуле сильно врет…
Написано вроде все правильно, но не понял про 4В и 5В. Ноль получается при 2.5 В если питание ровно 5В. -20 А при напряжении на входе 0,5 В. +20А при напряжении 4,5 В. За 5В не выходим. В чем подвох? Приведенная формула относится к определению чувствительности, а не к определению тока.
как заставить ардуино понять что 4.5в предел для датчика… я переделал данную формулу под 30А датчик, в итоге получил расхождение 1А на 10А, и 1.3А на 15А…. Выше проверить не смог лабораторник не позволяет… Исходя из формулы скетча получается, что входной диапазон делится, как будто датчик выдает весь спектр напряжений в районе напряжения питания его и ардуино, а реально, судя по даташиту от 0.5 до 4.5В… или я не понимаю чего то?
Датчик выдает от Vcc*0.1 до Vcc*.9
Ардуина умеет измерять от 0 до Vcc
В чем проблема? Код в скетче играет от измеренного Vcc, которое не обязательно 5В. В этом его основное преимущество.
float kVolt = (Vcca/2-Vcca*.1)/MaxCurrent; — определяем коэф В/А., например (5/2-5*0,1)/30 = 0,06666(6)
float CurrentPinVoltage = voltRatio * CurrentSensorValue; — Сколько вольт выдает нам датчик тока, для нуля будет 5/1023*512=2,5
float CurrentValue = (Vcca/2 — CurrentPinVoltage)/kVolt; — Наш искомый ток (5/2 — 2,5)/0,06666(6) = 0
Есть у этих датчиков неприятный момент — они реагируют на внешние магнитные поля. От ориентации датчика в пространстве показания могут меняться до 0.3А при постоянном токе через датчик. Если надо точно измерять ток, то рекомендуют ставить два таких датчика включенных в разном направлении. Тогда внешняя машнитная наводка будет скомпенсирована. Может это и не принципиально в вашем случае, но если соберетесь измерять ток более-менее точно с помощью этих датчиков — имейте ввиду такой эффект.
Применял такой датчик, только 5A, да показания гуляют, я решил так- датчик поместил в пермаллоевый корпус , ( согнул из ш обрезной пластины магнитопровода) , а на вход Ардуино фильтр из резистора 1ком и конденсатора 1 мкф.Теперь гуляет в районе 10 мА.