Измерение постоянного тока и напряжения на Arduino

Проблема

 

В сети есть много примеров превращения Arduino в Амперметр и Вольтметр с использованием в качестве измерения тока датчика холла ASC712, а для напряжения — простого делителя напряжения на двух резисторах. Но вот незадача, большинство из них предполагает, что напряжение питания строго 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;
}

Измерение постоянного тока и напряжения на Arduino: 10 комментариев

  1. Особенно красиво выглядит это:
    const float dividerRatio = (R1+R2)/R2;
    Даешь стране как можно больше операций с плавающей точкой!
    Особенно R2/R2…

    1. А в чем проблема? Это же константа, которая высчитывается один раз на этапе компиляции.

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

    1. Спасибо за доверие. Этот блог для души. Обратитесь к фрилансерам, можно со ссылкой на мой блог. Будет дёшево и сердито.

  3. Из даташита на ACS712 ясно, что нулевая точка принимается равной половине напряжения питания Vcc/2. Чувствительность также зависит от напряжения питания (Vcc/2-Vcc*.1)/maxCurrent. Для maxCurrent=20А ниже приведен график из даташита.

    Для датчика на 20 А 100мВ/А… датчик +-20А …. 40А*на 0.1В=4В… Следовательно максимальное разброс 4В, а вход ардуино 1023 на 5В( ну или около того, в зависимости от измеренного). По вашей формуле сильно врет…

    1. Написано вроде все правильно, но не понял про 4В и 5В. Ноль получается при 2.5 В если питание ровно 5В. -20 А при напряжении на входе 0,5 В. +20А при напряжении 4,5 В. За 5В не выходим. В чем подвох? Приведенная формула относится к определению чувствительности, а не к определению тока.

      1. как заставить ардуино понять что 4.5в предел для датчика… я переделал данную формулу под 30А датчик, в итоге получил расхождение 1А на 10А, и 1.3А на 15А…. Выше проверить не смог лабораторник не позволяет… Исходя из формулы скетча получается, что входной диапазон делится, как будто датчик выдает весь спектр напряжений в районе напряжения питания его и ардуино, а реально, судя по даташиту от 0.5 до 4.5В… или я не понимаю чего то?

        1. Датчик выдает от 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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *