Go Down

Topic: Monitoring AC Current with Arduino and ACS712 (Read 139270 times) previous topic - next topic

bhaelsoccer

This is my first post, so I hope it is in the correct place. I have been attempting to measure the AC current and voltage from a 350 V and 30 A line using an ACS712 and arduino nano. I was able to get decently accurate results when measuring DC. I understand for AC, I need to use some sort of sampling and RMS conversion. However, my implementation must be incorrect as I am not getting accurate current readings. Do I need to sample at 1/60 Hz intervals?

My first code attempt for AC current only:

Code: [Select]
float samplesnum = 1000;
float adc_zero = 510; //relative digital zero of the arudino input from ACS712
long currentacc = 0;
long currentac = 0;
long adc_raw = 0;
long currentAD = 0;

void setup()
{
 Serial.begin(9600);
}
void loop()
{
 
 for(int i=0; i<samplesnum; i++)
 {  
 adc_raw = analogRead(3);
 currentacc += (adc_raw - adc_zero) * (adc_raw - adc_zero);  //rms
 }
 currentAD = (currentacc * 75.7576)/ 1024; //D to A conversion
 currentac = sqrt(currentAD)/samplesnum; //rms
 Serial.println(currentac);
}


Second attempt:

Code: [Select]
float c_val = 0;
float volts = 0;
float val = 0;

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

void loop()
{
 c_val = 511 - analogRead(3);
 val = analogRead(4);
 float c_average = 0;
 float v_average = 0;
 float currentAC = 0;
 float amps = 0;
 
 
 for(int i = 0; i<100; i++)
 {
 
   c_average += sq(amps) / 100;//rms conversion
   currentAC = sqrt(c_average);
   
 for(int i = 0; i <1000; i++)
 {
   volts = val * (5.0 / 1024.0);
   amps = amps + ((c_val * 75.7576)/ 1024 / 1000);//averaging samples and D to A conversion
   v_average = (v_average + volts) / 1000;
 }  
 }
   
 Serial.println(currentAC);
 Serial.println(v_average);
}


The 75.7576 for the conversion comes from solving for X, 5 V / X = .066 V / 1 A from the arduino max analog input voltage and the ACS712 sensitivity on the spec sheet respectively. Any help would be greatly appreciated.

dc42

To measure alternating current accurately, you need to take a large number of readings at regular intervals, over a time period that is an exact multiple of the mains period (1/60 second in your case). Sum the squares of the individual current readings. Then divide the sum of the squares to get the average, and finally take the square root of the sum.

To take readings at regular intervals, call micros() in a loop until a reading is due. Then take a reading, and repeat until you have the required number of readings.

If you know that the current is AC, you can also find the zero reading (which you assumed is 510 in your code) - because it will be the average reading.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

bhaelsoccer

I tried two different approaches to implementing the micros() function for regular interval readings. The first gave no output on the serial and the second yielded a measure of 0 amps, no matter how much current was applied.

Code: [Select]

float samplesnum = 1000;
float adc_zero = 510; //relative digital zero of the arudino input from ACS712
long currentacc = 0;
long currentac = 0;
long adc_raw = 0;
long currentAD = 0;
int count = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  while(count < 1000)
  {
    if((micros() % 60000) == 0)
    {
      adc_raw = analogRead(3);
      currentacc += (adc_raw - adc_zero) * (adc_raw - adc_zero); 
     
      count++;
    }
  }
 
  currentAD = (currentacc * 75.7576)/ 1024; //D to A conversion
  currentac = sqrt(currentAD)/samplesnum; //rms
  Serial.println(currentac);
}


Code: [Select]

float samplesnum = 1000;
float adc_zero = 510; //relative digital zero of the arudino input from ACS712
long currentacc = 0;
long currentac = 0;
long adc_raw = 0;
long currentAD = 0;
int count = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  while(count < 1000)
  {
    if((micros() % 60000) == 0)
    {
      adc_raw = analogRead(3);
      currentacc += (adc_raw - adc_zero) * (adc_raw - adc_zero); 
     
      count++;
    }
  }
 
  currentAD = (currentacc * 75.7576)/ 1024; //D to A conversion
  currentac = sqrt(currentAD)/samplesnum; //rms
  Serial.println(currentac);
}


Any help would be greatly appreciated. I don't understand what is wrong with my implementation.

dc42

Don't use the expression "micros() % 60000", because if the loop takes more than 16 clock cycles to execute or you get an interrupt while it is executing, you will miss it. Use:

Code: [Select]

unsigned long previousMicros;
const unsigned long interval = 60000UL;

void setup()
{
  ...
  previousMicros = micros();
}

void loop()
{
 ...
 if ( micros() - previousMicros >= interval)
 {
    previousMicros += interval;
    // take a reading
    ...
 }
 ...
}
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

dc42

Try this (warning: untested code!):

Code: [Select]

const int currentPin = 3;
const unsigned long sampleTime = 100000UL;                           // sample over 100ms, it is an exact number of cycles for both 50Hz and 60Hz mains
const unsigned long numSamples = 250UL;                               // choose the number of samples to divide sampleTime exactly, but low enough for the ADC to keep up
const unsigned long sampleInterval = sampleTime/numSamples;  // the sampling interval, must be longer than then ADC conversion time
const int adc_zero = 510;                                                     // relative digital zero of the arudino input from ACS712 (could make this a variable and auto-adjust it)

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

void loop()
{
 unsigned long currentAcc = 0;
 unsigned int count = 0;
 unsigned long prevMicros = micros() - sampleInterval ;
 while (count < numSamples)
 {
   if (micros() - prevMicros >= sampleInterval)
   {
     int adc_raw = analogRead(currentPin) - adc_zero;
     currentAcc += (unsigned long)(adc_raw * adc_raw);
     ++count;
     prevMicros += sampleInterval;
   }
 }
 
 float rms = sqrt((float)currentAcc/(float)numSamples) * (75.7576 / 1024.0);
 Serial.println(rms);
}

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

bhaelsoccer


dc42

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

bhaelsoccer

Yes, it worked very well up to measuring 10 A. The setup was producing randomly measured numbers after 10 A. However, I don't think this is a fault with the code and rather a problem with the sensor. Implementing the design with a CT to step the 30 A down before the ACS712 should fix this. Thank you very much!!

dc42

#8
Jul 31, 2013, 05:29 pm Last Edit: Jul 31, 2013, 05:35 pm by dc42 Reason: 1
If you want to measure up to 30A RMS, you need a sensor that can handle at least +/- 50A peak. Instead of using a current transformer, why not use this sensor instead http://uk.farnell.com/allegro-microsystems/acs756kca-050b-pff-t/sensor-hall-effect-linear-120khz/dp/2112639? Also available on a breakout board, http://www.ebay.co.uk/itm/50A-100A-150A-200A-Bi-Uni-AC-DC-Current-Sensor-Module-arduino-compatible-/111040360152?pt=LH_DefaultDomain_0&var=&hash=item19da856ed8.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

bhaelsoccer

I was considering doing that, but I figured the added isolation from the power source through a CT would be beneficial for long term use. Do you agree, or do you think the isolation that the boards supplies will be plenty?

polymorph


To measure alternating current accurately, you need to take a large number of readings at regular intervals, over a time period that is an exact multiple of the mains period (1/60 second in your case). Sum the squares of the individual current readings. Then divide the sum of the squares to get the average, and finally take the square root of the sum.

To take readings at regular intervals, call micros() in a loop until a reading is due. Then take a reading, and repeat until you have the required number of readings.

If you know that the current is AC, you can also find the zero reading (which you assumed is 510 in your code) - because it will be the average reading.


I'm a bit confused. If you measure 60 times per second, that is going to line up with the same point on the sine wave each time, or more likely it is going to be slightly off and cause a slowly rising and falling measurement.

I'd think more like 120 measurements per cycle, sync'd to the sine wave zero crossing. Then just Root Mean Square it.

Or did you mean to say, 60 measurements per cycle? That would make more sense.
Steve Greenfield AE7HD
Drawing Schematics: tinyurl.com/23mo9pf - tinyurl.com/o97ysyx - https://tinyurl.com/Technote8
Multitasking: forum.arduino.cc/index.php?topic=223286.0
gammon.com.au/blink - gammon.com.au/serial - gammon.com.au/interrupts

dc42



To measure alternating current accurately, you need to take a large number of readings at regular intervals, over a time period that is an exact multiple of the mains period (1/60 second in your case). Sum the squares of the individual current readings. Then divide the sum of the squares to get the average, and finally take the square root of the sum.

To take readings at regular intervals, call micros() in a loop until a reading is due. Then take a reading, and repeat until you have the required number of readings.

If you know that the current is AC, you can also find the zero reading (which you assumed is 510 in your code) - because it will be the average reading.


I'm a bit confused. If you measure 60 times per second, that is going to line up with the same point on the sine wave each time, or more likely it is going to be slightly off and cause a slowly rising and falling measurement.

I'd think more like 120 measurements per cycle, sync'd to the sine wave zero crossing. Then just Root Mean Square it.

Or did you mean to say, 60 measurements per cycle? That would make more sense.


I said "a large number of readings", however I should have qualified that by saying the number of readings per cycle needs to be large too. For example, 30 or more (the more, the better) readings per complete cycle of the mains.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

elik745i

Variable, autoadjusted zero voltage one could be like this?
Code: [Select]
const int currentPin = 4;
const unsigned long sampleTime = 100000UL;                           // sample over 100ms, it is an exact number of cycles for both 50Hz and 60Hz mains
const unsigned long numSamples = 250UL;                               // choose the number of samples to divide sampleTime exactly, but low enough for the ADC to keep up
const unsigned long sampleInterval = sampleTime/numSamples;  // the sampling interval, must be longer than then ADC conversion time
//const int adc_zero = 522;                                                     // relative digital zero of the arudino input from ACS712 (could make this a variable and auto-adjust it)
int adc_zero;                                                                   //autoadjusted relative digital zero

void setup()
{
  Serial.begin(9600);
   adc_zero = determineVQ(currentPin); //Quiscent output voltage - the average voltage ACS712 shows with no load (0 A)
  delay(1000);
}

void loop(){
  Serial.print("ACS712@A2:");Serial.print(readCurrent(currentPin),3);Serial.println(" mA");
  delay(150);
}

int determineVQ(int PIN) {
  Serial.print("estimating avg. quiscent voltage:");
  long VQ = 0;
  //read 5000 samples to stabilise value
  for (int i=0; i<5000; i++) {
    VQ += analogRead(PIN);
    delay(1);//depends on sampling (on filter capacitor), can be 1/80000 (80kHz) max.
  }
  VQ /= 5000;
  Serial.print(map(VQ, 0, 1023, 0, 5000));Serial.println(" mV");
  return int(VQ);
}

float readCurrent(int PIN)
{
  unsigned long currentAcc = 0;
  unsigned int count = 0;
  unsigned long prevMicros = micros() - sampleInterval ;
  while (count < numSamples)
  {
    if (micros() - prevMicros >= sampleInterval)
    {
      int adc_raw = analogRead(currentPin) - adc_zero;
      currentAcc += (unsigned long)(adc_raw * adc_raw);
      ++count;
      prevMicros += sampleInterval;
    }
  }
  float rms = sqrt((float)currentAcc/(float)numSamples) * (75.7576 / 1024.0);
  return rms;
  //Serial.println(rms);
}
Wake up NEO...

polymorph

Steve Greenfield AE7HD
Drawing Schematics: tinyurl.com/23mo9pf - tinyurl.com/o97ysyx - https://tinyurl.com/Technote8
Multitasking: forum.arduino.cc/index.php?topic=223286.0
gammon.com.au/blink - gammon.com.au/serial - gammon.com.au/interrupts

dragonrobo7

#14
Dec 15, 2013, 02:09 am Last Edit: Dec 15, 2013, 03:20 am by dragonrobo7 Reason: 1

Thank you very much for the help.


Good to see you got this code to work.
I am stuck though!

My wiring schema attached far below as an attachment.

I have connected a load in with ACS712 module series to a 110V AC input.

Here is the unit I am using:  http://www.ebay.com/itm/20A-Range-Current-Sensor-Module-ACS712-ACS712ELCTR-20A-For-Arduino-Module-/321240008554?ssPageName=ADME:L:OU:US:3160 
LATER, I disconnected the ACS712 and instead I connected a multi-meter to the same points where ACS712 is connected.
Arduino reports junk with this program as below, vs. the multimeter which show approx 1 Amp when switch is flipped on and 0 Amp when switch is off.

I really need this to work .. going bonkers for last 5 hours... !!

I would appreciate any help pretty please !!

// Starting serial monitor - switch is off
estimating avg. quiscent voltage:2517 mV
ACS712@A2:0.074 mA
ACS712@A2:0.074 mA
ACS712@A2:0.075 mA
ACS712@A2:0.073 mA
ACS712@A2:0.076 mA
ACS712@A2:0.075 mA
ACS712@A2:0.075 mA
ACS712@A2:0.074 mA

// Switch is flipped on
ACS712@A2:0.073 mA
ACS712@A2:0.075 mA
ACS712@A2:0.075 mA
ACS712@A2:0.074 mA
ACS712@A2:0.074 mA
ACS712@A2:0.075 mA
ACS712@A2:0.074 mA
ACS712@A2:0.074 mA
ACS712@A2:0.074 mA
ACS712@A2:0.074 mA
ACS712@A2:0.075 mA
ACS712@A2:0.073 mA
ACS712@A2:0.073 mA
ACS712@A2:0.075 mA
ACS712@A2:0.076 mA
ACS712@A2:0.074 mA

// switch is turned off
ACS712@A2:0.075 mA
ACS712@A2:0.075 mA
ACS712@A2:0.074 mA
ACS712@A2:0.075 mA
ACS712@A2:0.074 mA
ACS712@A2:0.073 mA


*** I connected either Multimeter or the ACS712 at any point in time, not both together. Sorry about any confusion from the sketch below.

Go Up