Измерение постоянного тока и напряжения на 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: 4 комментария

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

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

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

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