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:

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);
}

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.

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 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.

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);
}

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.

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:

unsigned long previousMicros;
const unsigned long interval = 60000UL;
void setup()
{
...
previousMicros = micros();
}
void loop()
{
...
if ( micros() - previousMicros >= interval)
{
previousMicros += interval;
// take a reading
...
}
...
}

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);
}

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!!

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?

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.

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.

Variable, autoadjusted zero voltage one could be like this?

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);
}

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.

otherwise the multiplication may overflow. However, this overflow will only occur at large currents, so I suspect that isn't the problem you are seeing.

I don't see where the constant 75.7576 comes from. The output of the ACS712-20 (i.e. the 20A version) is 100mV/A. So 5V would correspond to 50A, and that constant should be changed to 50.0 to get the output in amps, or 50000.0 to get the output in mA. But once again, this doesn't explain your readings.

Hi dc42,
Thank you for taking time to reply!! I appreciate it
I did make the two changes you suggested. As you guessed that is not fixing my problem
I am sure I am using the correct Arduino pin, tested it using an LED and running blink program on pin #3, then plugged the same wire back into ACS712.

Next I am planning to measure DC current, not because I need to, but I am running out of ideas. If ACS712 module can't read DC either, then I will assume I have a bad module and order another one.

Current output:
// Switch is off here
estimating avg. quiscent voltage:2517 mV
ACS712@A2:0.046 mA
ACS712@A2:0.044 mA
ACS712@A2:0.046 mA
ACS712@A2:0.044 mA
ACS712@A2:0.044 mA
ACS712@A2:0.044 mA
ACS712@A2:0.043 mA
ACS712@A2:0.046 mA
ACS712@A2:0.044 mA
ACS712@A2:0.045 mA
ACS712@A2:0.044 mA
ACS712@A2:0.045 mA
ACS712@A2:0.043 mA
ACS712@A2:0.046 mA
ACS712@A2:0.044 mA
ACS712@A2:0.046 mA
ACS712@A2:0.044 mA
ACS712@A2:0.043 mA
ACS712@A2:0.044 mA
ACS712@A2:0.044 mA
ACS712@A2:0.044 mA
ACS712@A2:0.043 mA
ACS712@A2:0.044 mA
ACS712@A2:0.044 mA
// switch is turned on
ACS712@A2:0.044 mA
ACS712@A2:0.042 mA
ACS712@A2:0.044 mA
ACS712@A2:0.043 mA

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 = 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);
pinMode(currentPin, INPUT);
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)
{
long adc_raw = analogRead(currentPin) - adc_zero;
currentAcc += (unsigned long)(adc_raw * adc_raw);
++count;
prevMicros += sampleInterval;
}
}
float rms = sqrt((float)currentAcc/(float)numSamples) * (50 / 1024.0); return rms;
Serial.print "RMS: "; Serial.println(rms);
}

Are you quite sure that current is passing through the ACS712? The readings you are getting suggest to me that it isn't. I suggest you connect both your multimeter and the ACS712 in series with the load, just to make sure.

I see what you are saying and the readings certainly suggest that ACS712 is not seeing the current flowing through.

HOWEVER, if ACS712 was not part of the series circuit the load motor would not turn on when the switch is flipped on. So yes, I'm pretty sure current passes through the two terminals of ACS712 in series.

Strange thing is that in the module I have it seems to have 2 LEDs instead of four I see in some of the online pictures. Whats more none of the 2 LEDs ever turn on. Below is the link to the unit I have.

dragonrobo7:
I see what you are saying and the readings certainly suggest that ACS712 is not seeing the current flowing through.

HOWEVER, if ACS712 was not part of the series circuit the load motor would not turn on when the switch is flipped on. So yes, I'm pretty sure current passes through the two terminals of ACS712 in series.

Fair enough.

dragonrobo7:
Strange thing is that in the module I have it seems to have 2 LEDs instead of four I see in some of the online pictures. Whats more none of the 2 LEDs ever turn on. Below is the link to the unit I have.