RobTillaart / ACS712

Arduino library for ACS Current Sensor - 5A, 20A, 30A
MIT License
113 stars 31 forks source link

Complex AC RMS Current Reading Issues #18

Closed imk2021 closed 1 year ago

imk2021 commented 1 year ago

Hello Rob I am having issues with the ACS712 library ver 0.2.7/6 when reading complex AC waveform and i am wondering if you want my full project details and waveform photos to investigate. Basically the load on the AC line that the ACS712 is monitoring has two loads: Load One is an old 100w incandescent lamp controlled via a simple switch. Load Two is an old 200w incandescent lamp controlled the RBDdimmer function.

Switching on load One gives a clean sine ACS712 output and your library produces very good results. Switching on load Two gives a switched sine ACS712 output and your library produces good but variable results depend on the dimmer.setPower( n ); power level.

Switching on load One and load Two produces a complex ACS712 waveform (Sine plus Dimmer Spike) and I am afraid your ACS712 library produces very inconsistent results. compared to the in series BRYMEN BM257 RMS meter I am using.

I have had a look on Github for your email to send you full info, scope screen shots and my code and project wire info but i can't seem to find it so i am wondering how to proceed.

All best Ian Knight PS Ignore the jitter in the image, this is my poor camera

IMG_5803

RobTillaart commented 1 year ago

Thanks for reporting the issue.

This is sort of a known problem as the complexity of the signal changes with the dimmer setting as you stated. I assume the BRYMEN (I have one too, they are great) makes more samples to determine the crest factor (form factor).

Analysis

Could you do the following experiment:

  1. use only the dimmer lamp at 100% (expect a reasonable match in mA)
  2. use only the dimmer lamp at 75%
  3. use only the dimmer lamp at 50%
  4. use only the dimmer lamp at 25%

Thinking out loud

If your dimmer is under program control - I see dimmer.setPower(n) - it might be possible to derive a function to calculate the crest factor. The crest factor can be approached by the area under the sine function times some constant:

integral sin(x) => 1 - cos(x) - (from my head)

The setPower() parameter n goes from 0..maxN which equals x going from 0 .. PI (radians)

So the formula for the crest factor will probably look something like:

const float cf = 1.0/ (2 * sqrt(2));  //  this value is my best guess

float ff = (1 - cos(  (n * PI) / maxN))  * cf; 

PS, email is in the json file

RobTillaart commented 1 year ago

(some more thoughts)

I am afraid your ACS712 library produces very inconsistent results.

The wave form is quite complex indeed, for this kind of wave forms I do not expect the library to be smart enough.

looking at the point of the spike in the signal I assume it is leading edge phase control. https://www.dmlights.com/help/lighting/different-methods-dimming/

Problem is that the form factor changes per setting of the dimmer.

Tip: to make a better picture of the scope you better take some distance, so the autofocus can do its job. Normally that allows to cut the important part with enough resolution afterwards.

RobTillaart commented 1 year ago

Another way to determine the RMS current should be sampling. I have no hardware setup near so please test this function and compare to your BRYMEN. If the function works I can add it to the library.


//  PARAM         TYPICAL VALUE
//  frequency   = 50.0
//  mVperStep   = 5000.0 / 1023.0  (Assume Arduino UNO ADC) = 4.887586
//  mVperAmpere = 100.0
//  analogPin   = A0

float RMS_by_sampling(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin)
{
  uint16_t midpoint   = 511;   // UNO midpoint.
  uint16_t period     = round(1000000UL / frequency);
  float    AmpPerStep = mVperStep / mVperAmpere;
  uint16_t samples    = 0;
  float    sumSquared = 0;

  uint32_t start = micros();
  while (micros() - start < period)
  {
    samples++;
    float current = (analogRead(analogPin) - midpoint) * AmpPerStep;
    sumSquared += (current * current);
  }
  //  Serial.print(samples);
  //  Serial.print("\t");
  float RMS = sqrt(sumSquared / samples);
  return RMS;
}

(compiles)

imk2021 commented 1 year ago

Hello Rob and many thanks for your replies.

So here are the test results for your first github message.

Attached are some ACS721 output pin scope images, without any electronic filter:

ZeroLoadNoise 100W-Not-Dimmed                        BRYMEN    0.435A     ACS712 Library Output , Mean of Ten calls to  ACS.mA_AC()          0.353A

200w-Load-Dimmer-25%              BRYMEN    0.472A     ACS712 Library Output , Mean of Ten calls to  ACS.mA_AC()          0.804A 200w-Load-Dimmer-50%              BRYMEN    0.705A     ACS712 Library Output , Mean of Ten calls to  ACS.mA_AC() 0.683A 200w-Load-Dimmer-75%              BRYMEN    0.815A     ACS712 Library Output , Mean of Ten calls to  ACS.mA_AC()          0.704A 200w-Load-Dimmer-100%           BRYMEN    0.536A     ACS712 Library Output , Mean of Ten calls to  ACS.mA_AC()          0.336A

As can be seen dimmed 25%, 50%, 75% look expected waveform. But something goes wrong I think in the dimmer library as only half power and the lamps are not full brightness,

I will do more testing tomorrow, I hope this help Ian

RobTillaart commented 1 year ago

The mA-AC() works on the peaks of the signal during one period. As the peaks are not representative for the (distorted)e signal the math with the crest factor fails. So definitely not OK.

It surprises me that the DMM measures its highest current somewhere around 75% and not at 100%. Does the voltage drop? Do you have a datasheet / docs /link of the dimmer?

imk2021 commented 1 year ago

Hello Rob,

So i had an hour spare this evening to try the RMS_by_sampling function you said i might try. Unfortunately your version does not seem to work, as it seems to returns immediately  rather than run for many sample, maybe you meant mills and not micros for the timing. So i did a quick hack at it and replaced the timer function with a simple FOR 10000 loop, this seems to produce some results. However the RMS output is off by about a factor of 40 or so, inasmuch as simple 100w lamp load BRYMEN's says 0.435mA and the LCD displays 17.99 or so. It is also interesting to note that sometimes when turning the load on the LCD display only 0.02 or so... This maybe a loose connection on my rig, or a power spike to the Nano....need more testing

Thought you might be interested please see capture of sketch below, all the best Ian

// //    FILE: ROB_RMS_TEST_1.ino //  AUTHOR: Ian Knight, Rob Tillaart // PURPOSE: To test the Return RMS Function //    DATE: 2022-08-16

defineADJUSTABLE_MAX_CURRENT_ANALOG_PIN              A1

defineHYSTERESIS_CURRENT_IN_MA 100

include

defineACS712_ANALOG_PIN                             A0

// initialize the library by associating any needed LCD interface pin // with the arduino pin number it is connected to // const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; // Working OK constintrs = 12, en = 11, d4 = 7, d5 = 6, d6 = 5, d7 = 4; LiquidCrystallcd(rs, en, d4, d5, d6, d7); floatTheRMS; longMaxmA; // Adjusted maximum current //  ----------------------------- Prototypes

voidDisplayToLCDTotalCurrent( void); voidDisplayToLCDMaxCurrent( void); //  PARAM         TYPICAL VALUE //  frequency   = 50.0 //  mVperStep   = 5000.0 / 1023.0  (Assume Arduino UNO ADC) = 4.887586 //  mVperAmpere = 100.0 //  analogPin   = A0 floatRMS_by_sampling( floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin); // ------------------------------------------------ setup()

voidsetup() { // set up the LCD's number of columns and rows: lcd.begin(16, 2);  // Init LCD //lcd.print("hello, world!");   // Print a message to the LCD. } // -------------------------------------------------- loop()

voidloop() { // set the cursor to column 0, line 1   (note: line 1 is the second row, since counting begins with 0): //TheRMS = RMS_by_sampling( 50.0 , 4.887586 , 100.0 , A0 ); TheRMS = RMS_by_sampling( 50.0, 4.887586, 100.0, ACS712_ANALOG_PIN ); DisplayToLCDTotalCurrent(); MaxmA = analogRead( ADJUSTABLE_MAX_CURRENT_ANALOG_PIN ); MaxmA = map( MaxmA , 0, 1023, 0, 20000); // Map ADC 0->1023 to 0->20000 mA DisplayToLCDMaxCurrent(); }

ifdefROB_VERSION

// -------------------------------- RMS_by_sampling()

floatRMS_by_sampling( floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin) { uint16_tmidpoint = 511; uint16_tperiod     = round(1000000UL/ frequency); float   AmpPerStep = mVperStep / mVperAmpere; uint16_tsamples    = 0; float   sumSquared = 0; uint32_tstart = micros(); while( micros() - start < period )  {  samples++; floatcurrent = (analogRead(analogPin) - midpoint) AmpPerStep;  sumSquared += (current current);  } floatRMS = sqrt(sumSquared) / samples; //return( round(RMS) ); return( RMS ); }

endif

// -------------------------------- RMS_by_sampling()

floatRMS_by_sampling( floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin) { uint16_tmidpoint = 511; //uint16_t period     = round(1000000UL / frequency); float   AmpPerStep = mVperStep / mVperAmpere; uint16_tsamples    = 0; float   sumSquared = 0; ///uint32_t start = micros(); for( inti = 0; i < 10000; i++ )  {  samples++; floatcurrent = (float)( analogRead( analogPin ) - midpoint ) AmpPerStep;  sumSquared += (current current);  } floatRMS = sqrt(sumSquared) / samples; return( RMS ); } // ------------------------------------------ DisplayToLCDTotalCurrent( ) ---------------------------------- voidDisplayToLCDTotalCurrent( void) { lcd.setCursor(0, 1); lcd.print("                ");// Clear Line lcd.setCursor(0, 1); lcd.print( TheRMS );  // print the RMS AC current } // ------------------------------------------- DisplayToLCDMaxCurrent()

voidDisplayToLCDMaxCurrent( void) { lcd.setCursor(0, 0);// Col, row lcd.print("                ");// Clear Line lcd.setCursor(0, 0); lcd.print( MaxmA );  // print the System Maximum mA } // -- END OF FILE --

RobTillaart commented 1 year ago

Thanks for testing, So I time to 'disect' that RMS code ... If the error is a constant factor it is easy to patch.

Think I need some hardware to build a test setup. I will look if I have a spare sensor somewhere.

RobTillaart commented 1 year ago

saw one error in my code - already updated above -

Was float RMS = sqrt(sumSquared)/ samples;

should be

float RMS = sqrt(sumSquared / samples);   //   the () should be around the whole expression.

Dividing by samples after doing the square root makes the outcome at least sqrt(samples) too small.

Can you retry? I'll going to check if I have some hardware today


BRYMEN's says 0.435mA ...

I assume a 100W lamp uses 435 mA 😁

imk2021 commented 1 year ago

Morning Rob and many thanks for the support.

I have busy day ahead so i am unable to do more testing until this evening. However i implemented your update to

float RMS = sqrt(sumSquared / samples);

into my FOR 10000 sample function as can be seen below.

Output for 100W 240V resistive load is BRYMEN 0.435A The function returns 2150.5 not sure if A or mA

I should be back about 1900 today to do further testing, have a good day Ian

// -------------------------------- RMS_by_sampling()

floatRMS_by_sampling( floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin) { uint16_tmidpoint = 511; //uint16_t period     = round(1000000UL / frequency); float   AmpPerStep = mVperStep / mVperAmpere; uint16_tsamples    = 0; float   sumSquared = 0; ///uint32_t start = micros(); for( inti = 0; i < 10000; i++ )  {  samples++; floatcurrent = (float)( analogRead( analogPin ) - midpoint ) AmpPerStep;  sumSquared += (current current);  } //float RMS = sqrt(sumSquared) / samples; floatRMS = sqrt(sumSquared / samples); return( RMS ); }

RobTillaart commented 1 year ago

Do you have a datasheet / docs /link of the dimmer?

imk2021 commented 1 year ago

Hello Rob,

Please find below link to the 8A dimmer i am currently testing with. However the testing I have done with the RMS_by_sampling() function have all been done with a NONE dimmed 100W load. Although the dimmer module is still wired to the NANO but is not software enabled.

https://robotdyn.com/ac-dimmer-module-8a-with-ac-dc-power-supply-1-channel-3-3v-5v-logic-ac-110-240v-8a.html

Am home again now and able to do more testing if you wish, all best Ian

RobTillaart commented 1 year ago

Thanks for the link, I had a quick look at the library for it - https://github.com/RobotDynOfficial/RBDDimmer and it looks pretty dedicated (timer interrupts etc) per platform.

Did not make progress today (too much "non maskable interrupts") so I have to catch up tasks. I'll be online around 21:00 GMT again, maybe earlier.

RobTillaart commented 1 year ago

Minor changes in the RMS code, added some print

//  PARAM         TYPICAL VALUE
//  frequency   = 50.0
//  mVperStep   = 5000.0 / 1023.0  (Assume Arduino UNO ADC) = 4.887586
//  mVperAmpere = 100.0
//  analogPin   = A0

float RMS_by_sampling(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin)
{
  uint16_t midpoint   = 511;   // UNO midpoint.
  uint32_t period     = round(1000000UL / frequency);  //  should be ~20k micros
  Serial.println(period);
  float    AmpPerStep = mVperStep / mVperAmpere;       // should be ~0.048875 Ampere per step.
  Serial.println(AmpPerStep, 5);

  uint32_t samples    = 0;
  float    sumSquared = 0;

  uint32_t start = micros();
  while (micros() - start < period)
  {
    samples++;
    float current = (analogRead(analogPin) - midpoint) * AmpPerStep;   // unit = Amperes.
    sumSquared += (current * current);       //  unit = Ampere squared.
  }
  float RMS = sqrt(sumSquared / samples);    //  unit = Ampere.
  Serial.println(RMS, 5);  
  return RMS;                                //  current = Amperes.
}

If you run this with the 100Watt lamp 100W / 230 V = 0.435 Ampere, what does it return?

RobTillaart commented 1 year ago

Test proposal

Can you do the test with a load of ~1000 Watt e.g. an iron? 1000W/230V = 4.4 Ampere

Rationale

A problem with a load of 100 W could be that the point2point (max signal - min signal) is only 25 steps. So the peak above the midpoint is only 12 or 13 steps. (Ampere per step = 5000/1023/100 = 0.048875. As noise can easily be a 2-5 steps this is a substantial part (30%) of the peak => so even more of the non peak part.

A 1000 W load should be less affected by the noise.

How I got the 25 steps.

from ma_AC() float mA = (500.0 * point2point) * _mVperStep * _formFactor / _mVperAmpere; 500 25 4.8875 / sqrt(2) / 100 = 432 mA (that is what BRYMEN measured)

imk2021 commented 1 year ago

Hello Rob,

Quick note re RobotDyn Library interrupts/timers etc.

During my early testing couple of weeks ago, I was doing a long Serial.print message in loop every second. This caused the dimmer output to dim at the same same rate as the Serial.print.

I Think the serial out interrupt was interfering with the Dimmer Library interrupts/timers. This I will build a proper rig for when i get a moment and test thoroughly as is potential problem.

I shall do other testing for you in next hour or so and send to you, Ian

imk2021 commented 1 year ago

Rob,

Tested at 9600 bps serial, it is interesting to note that the serial output is very fast, implying that is doing not do many samples in  RMS_by_sampling.

Also note micro() wraps after a few minutes, so may produce inconstant results, maybe us mills()

Output from Serial Print Version of RMS_by_sampling() without any load: Brymen 0.007A

Serial Output from Arduino IDE V2.0.0 RC9.2 with time stamp:

21:58:23.702 -> 0.04888 21:58:23.702 -> 0.36488 21:58:23.733 -> 20000 21:58:23.733 -> 0.04888 21:58:23.733 -> 0.36870 21:58:23.765 -> 20000 21:58:23.765 -> 0.04888 21:58:23.765 -> 0.36849 21:58:23.807 -> 20000 21:58:23.807 -> 0.04888 21:58:23.807 -> 0.36226 21:58:23.842 -> 20000 21:58:23.842 -> 0.04888 21:58:23.842 -> 0.36585 21:58:23.877 -> 20000 21:58:23.877 -> 0.04888 21:58:23.877 -> 0.36594 21:58:23.913 -> 20000 21:58:23.913 -> 0.04888 21:58:23.913 -> 0.36660 21:58:23.947 -> 20000 21:58:23.947 -> 0.04888 21:58:23.947 -> 0.36245 21:58:23.982 -> 20000

Output from Serial Print Version of RMS_by_sampling() with 100W load at 237V:

Brymen 0.436A

Serial Output from Arduino IDE V2.0.0 RC9.2 with time stamp:

22:3:33.409 -> 1525.90014 22:3:33.442 -> 20000 22:3:33.442 -> 0.04888 22:3:33.442 -> 1531.34667 22:3:33.483 -> 20000 22:3:33.483 -> 0.04888 22:3:33.483 -> 1501.86999 22:3:33.518 -> 20000 22:3:33.518 -> 0.04888 22:3:33.518 -> 1477.44860 22:3:33.555 -> 20000 22:3:33.555 -> 0.04888 22:3:33.555 -> 1452.61437 22:3:33.591 -> 20000 22:3:33.591 -> 0.04888 22:3:33.591 -> 1525.90161 22:3:33.627 -> 20000 22:3:33.627 -> 0.04888 22:3:33.627 -> 1531.33801 22:3:33.662 -> 20000 22:3:33.662 -> 0.04888 22:3:33.662 -> 1501.87451 22:3:33.699 -> 20000 22:3:33.699 -> 0.04888 22:3:33.699 -> 1477.44470 22:3:33.735 -> 20000 22:3:33.735 -> 0.04888 22:3:33.735 -> 1477.44641 22:3:33.770 -> 20000 22:3:33.770 -> 0.04888 22:3:33.770 -> 1477.44799 22:3:33.808 -> 20000 22:3:33.808 -> 0.04888 22:3:33.808 -> 1477.44799 22:3:33.844 -> 20000 22:3:33.844 -> 0.04888 22:3:33.844 -> 1452.61511 22:3:33.880 -> 20000 22:3:33.880 -> 0.04888 22:3:33.880 -> 1477.45007

Hope that helps Ian

RobTillaart commented 1 year ago

Sure this helps, at least two parameters are as expected.

The micros() does wrap every 10 minutes or so. As the code uses subtraction in the comparison it works.

Serial output is handled in the background, and yes it affects the analogRead() Lets count the number of samples. (load 100W for now) to find out.

Can you verify that the midpoint = 2.5V ?


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

void loop()
{
  float amps = RMS_by_sampling(50, 4.887586, 100, A0);
  Serial.println(amps);
  Serial.println();
  delay(1000);
}

//  PARAM         TYPICAL VALUE
//  frequency   = 50.0
//  mVperStep   = 5000.0 / 1023.0  (Assume Arduino UNO ADC) = 4.887586
//  mVperAmpere = 100.0
//  analogPin   = A0

float RMS_by_sampling(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin)
{
  uint16_t midpoint   = 511;   // UNO midpoint.
  uint32_t period     = round(1000000UL / frequency);  //  should be ~20k micros
  float    AmpPerStep = mVperStep / mVperAmpere;       // should be ~0.048875 Ampere per step.

  uint32_t samples    = 0;
  float    sumSquared = 0;

  uint32_t start = micros();
  while (micros() - start < period)
  {
    samples++;
    float current = (analogRead(analogPin) - midpoint) * AmpPerStep;   // unit = Amperes.
    sumSquared += (current * current);       //  unit = Ampere squared.
  }
  float RMS = sqrt(sumSquared / samples);    //  unit = Ampere.
  Serial.println(samples);  
  Serial.println(RMS, 5);  
  return RMS;                                //  current = Amperes.
}

Note to myself: There is an optimization, the * AmpPerStep; in the loop can be outside the loop. Would save a float multiply every loop, and adds 2 float multiplies instead.

imk2021 commented 1 year ago

Hello Rob, Please find test result for your mini app ROB_RMS_TEST_2.ino

5V Uno supply from USB 4.68v ACS721 No load midpoint 2.35v

No load BryMen 0.006A, Your app results below

15:35:02.373 -> 0.49 15:35:02.373 -> 15:35:03.402 -> 139 15:35:03.402 -> 0.49355 15:35:03.402 -> 0.49 15:35:03.402 -> 15:35:04.418 -> 139 15:35:04.418 -> 0.49249 15:35:04.418 -> 0.49 15:35:04.418 -> 15:35:05.447 -> 139 15:35:05.447 -> 0.49468 15:35:05.447 -> 0.49 15:35:05.447 -> 15:35:06.461 -> 139 15:35:06.461 -> 0.49259 15:35:06.461 -> 0.49 15:35:06.461 -> 15:35:07.475 -> 139 15:35:07.475 -> 0.49369 15:35:07.475 -> 0.49 15:35:07.475 -> 15:35:08.523 -> 139 15:35:08.523 -> 0.49046 15:35:08.523 -> 0.49 15:35:08.523 -> 15:35:09.539 -> 139 15:35:09.539 -> 0.49319 15:35:09.539 -> 0.49 15:35:09.539 -> 15:35:10.537 -> 139 15:35:10.537 -> 0.49216 15:35:10.537 -> 0.49 15:35:10.537 -> 15:35:11.539 -> 139 15:35:11.539 -> 0.49611 15:35:11.539 -> 0.50 15:35:11.539 ->

100W load BryMen 0.431A, Your app results below

15:39:40.996 -> 1912.74 15:39:40.996 -> 15:39:42.040 -> 143 15:39:42.040 -> 1949.89526 15:39:42.040 -> 1949.90 15:39:42.040 -> 15:39:43.071 -> 143 15:39:43.071 -> 1949.89147 15:39:43.071 -> 1949.89 15:39:43.071 -> 15:39:44.086 -> 143 15:39:44.086 -> 1931.40795 15:39:44.086 -> 1931.41 15:39:44.086 -> 15:39:45.115 -> 144 15:39:45.115 -> 1943.10546 15:39:45.115 -> 1943.11 15:39:45.115 -> 15:39:46.129 -> 143 15:39:46.129 -> 1949.89074 15:39:46.129 -> 1949.89 15:39:46.129 -> 15:39:47.121 -> 142 15:39:47.121 -> 1919.46875 15:39:47.121 -> 1919.47 15:39:47.121 -> 15:39:48.164 -> 142 15:39:48.164 -> 1938.19055 15:39:48.164 -> 1938.19 15:39:48.164 -> 15:39:49.197 -> 142 15:39:49.197 -> 1956.74804 15:39:49.197 -> 1956.75 15:39:49.197 -> 15:39:50.210 -> 143 15:39:50.210 -> 1968.20080 15:39:50.210 -> 1968.20 15:39:50.210 ->

RobTillaart commented 1 year ago

Very insightful! thanks a lot

ACS721 No load midpoint 2.35v

Assumed 2.5V so lets adapt the midpoint in code 2.35 / 5.00 * 1023 = 480

5V Uno supply from USB 4.68v

Assumed 5V so lets adapt too 4680 / 1023 = 4,57478 mVperstep.

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

void loop()
{
  float amps = RMS_by_sampling(50, 4.57478, 100,  A0);
  Serial.println(amps);
  Serial.println();
  delay(1000);
}

//  PARAM         TYPICAL VALUE
//  frequency   = 50.0
//  mVperStep   = 5000.0 / 1023.0  (Assume Arduino UNO ADC) = 4.887586
//  mVperAmpere = 100.0
//  analogPin   = A0

float RMS_by_sampling(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin)
{
  uint16_t midpoint   = 480;   // UNO midpoint.
  uint32_t period     = round(1000000UL / frequency);  //  should be ~20k micros
  float    AmpPerStep = mVperStep / mVperAmpere;       // should be ~0.048875 Ampere per step.

  uint32_t samples    = 0;
  float    sumSquared = 0;

  uint32_t start = micros();
  while (micros() - start < period)
  {
    samples++;
    float current = (analogRead(analogPin) - midpoint) * AmpPerStep;   // unit = Amperes.
    sumSquared += (current * current);       //  unit = Ampere squared.
  }
  float RMS = sqrt(sumSquared / samples);    //  unit = Ampere.
  Serial.println(samples);
  return RMS;                                //  current = Amperes.
}

Give it a try, should be closer to the BRYMEN now.

RobTillaart commented 1 year ago

Note: As the midpoint was 31 steps off (511-480) , half of the measurements - and squared - was way too high. So a call to autoMidPoint is mandatory ...

RobTillaart commented 1 year ago

optimized version, replaced a multiplication for every measurement in the loop with one outside the loop

samples in 20 milliseconds goes up from 145 to 152 so ~4% more samples (tested on UNO)

float RMS_by_sampling(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin)
{
  uint16_t midpoint   = 480;   // UNO midpoint.
  uint32_t period     = round(1000000UL / frequency); 
  float    AmpPerStep = mVperStep / mVperAmpere;

  uint16_t samples    = 0;
  float    sumSquared = 0;

  uint32_t start = micros();
  while (micros() - start < period)
  {
    samples++;
    uint32_t current = analogRead(analogPin) - midpoint;
    sumSquared += (current * current);
  }
  float RMS = sqrt(sumSquared / samples) * AmpPerStep;
  Serial.println(samples);
  return RMS;
}
imk2021 commented 1 year ago

Hello Rob, Please find test result for your mini app ROB_RMS_TEST_3.ino

100W load BryMen 0.433A, Your app results below

18:10:19.585 -> 140 18:10:19.585 -> 1.87211 18:10:19.585 -> 1.87 18:10:19.585 -> 18:10:20.599 -> 140 18:10:20.599 -> 1.86943 18:10:20.599 -> 1.87 18:10:20.599 -> 18:10:21.617 -> 140 18:10:21.617 -> 1.86537 18:10:21.617 -> 1.87 18:10:21.617 -> 18:10:22.615 -> 140 18:10:22.616 -> 1.86387 18:10:22.616 -> 1.86 18:10:22.616 -> 18:10:23.663 -> 140 18:10:23.663 -> 1.86361 18:10:23.663 -> 1.86 18:10:23.663 -> 18:10:24.697 -> 141 18:10:24.697 -> 1.86070 18:10:24.697 -> 1.86 18:10:24.697 -> 18:10:25.718 -> 141 18:10:25.718 -> 1.86190 18:10:25.718 -> 1.86 18:10:25.718 ->

Just go your up date this i'll test in couple of minutes and call it ROB_RMS_TEST_4.ino Ian

imk2021 commented 1 year ago

Hello Rob, Please find test result for your mini app ROB_RMS_TEST_4.ino

100W load BryMen 0.434A, Your app results below

18:22:08.664 -> 1.85 18:22:08.664 -> 18:22:09.678 -> 157 18:22:09.678 -> 1.85 18:22:09.678 -> 18:22:10.695 -> 157 18:22:10.695 -> 1.85 18:22:10.695 -> 18:22:11.730 -> 157 18:22:11.730 -> 1.86 18:22:11.730 -> 18:22:12.727 -> 157 18:22:12.727 -> 1.85 18:22:12.727 -> 18:22:13.770 -> 157 18:22:13.770 -> 1.86 18:22:13.770 -> 18:22:14.786 -> 157 18:22:14.786 -> 1.86 18:22:14.786 -> 18:22:15.783 -> 157 18:22:15.783 -> 1.85 18:22:15.783 -> 18:22:16.830 -> 157 18:22:16.830 -> 1.86

Ian

RobTillaart commented 1 year ago

So number of samples increased and still no substantial improvement in the measurement.

Can you run without load? to see if the zero point is improved?

RobTillaart commented 1 year ago

think it is time to get the raw data, as raw as possible.
Should give one period of raw data.

uint16_t samp[250];

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

  uint16_t samples = 0;
  uint32_t start = micros();
  while (micros() - start < 20000)
  {
    samp[samples] = analogRead(A0);
    samples++;
    delayMicroseconds(28);  // simulate math so we get ~145 samples.
  }
  Serial.print("samples: ");
  Serial.println(samples);

  for (int i = 0; i < samples; i++)
  {
    if ((i % 10) == 0) Serial.println();
    Serial.print(samp[i]);
    Serial.print("\t");
  }

  Serial.println("\n\ndone...");
}

void loop()
{
}
imk2021 commented 1 year ago

Rob please results for ROB_RMS_TEST_RAW_DATA_1.ino

Zero Load

19:30:33.115 -> 19:30:33.180 -> samples: 146 19:30:33.180 -> 19:30:33.180 -> 520    520    520    521    520    519 519    520    520    520 19:30:33.213 -> 520    520    521    520    519    520 520    520    520    519 19:30:33.280 -> 520    519    520    520    520    519 519    520    520    520 19:30:33.313 -> 520    520    520    520    519    520 519    520    520    519 19:30:33.346 -> 519    520    521    519    519    520 519    520    519    520 19:30:33.413 -> 520    520    520    519    520    519 519    520    520    520 19:30:33.445 -> 519    520    520    520    520    520 521    520    520    520 19:30:33.479 -> 520    520    519    520    520    520 520    520    520    520 19:30:33.546 -> 520    519    520    520    520    520 520    519    519    519 19:30:33.579 -> 519    519    520    519    521    520 519    519    520    520 19:30:33.612 -> 520    520    520    520    520    520 520    520    520    519 19:30:33.645 -> 521    520    520    520    520    520 520    520    520    520 19:30:33.712 -> 520    520    520    520    520    520 520    520    519    519 19:30:33.745 -> 520    520    520    520    520    520 520    519    520    519 19:30:33.779 -> 520    520    520    519    520    520 19:30:33.814 -> 19:30:33.814 -> done...

Brymen 0.434A load

19:33:45.882 -> 19:33:45.914 -> samples: 146 19:33:45.914 -> 19:33:45.914 -> 535    535    534    534    534    532 532    532    532    532 19:33:45.962 -> 531    530    530    529    529    529 528    528    527    527 19:33:46.040 -> 526    526    525    525    524    524 522    522    522    521 19:33:46.074 -> 522    520    520    521    519    518 518    518    517    517 19:33:46.107 -> 516    516    515    515    515    515 515    514    515    514 19:33:46.173 -> 514    514    513    514    514    513 514    514    514    514 19:33:46.207 -> 513    513    513    513    514    513 514    514    515    516 19:33:46.240 -> 515    516    516    516    517    517 517    517    519    519 19:33:46.306 -> 518    520    519    520    520    520 522    522    522    523 19:33:46.340 -> 524    524    524    525    526    526 527    527    527    528 19:33:46.372 -> 528    529    529    530    530    532 532    533    532    533 19:33:46.406 -> 533    533    534    535    534    535 535    536    536    537 19:33:46.473 -> 536    536    536    537    537    537 536    537    537    538 19:33:46.506 -> 536    537    536    537    538    537 537    538    537    538 19:33:46.540 -> 537    537    536    536    535    536 19:33:46.584 -> 19:33:46.584 -> done...

Hope that help Ian

RobTillaart commented 1 year ago

The first raw measurement shows that the midpoint is about 520 now. ~ that is about 2.54 volt. Compared to the 2.35 V you mentioned earlier this is ~20% fluctuation

imk2021 commented 1 year ago

Rob your:

The first raw measurement shows that the midpoint is about 520 now. ~ that is about 2.54 volt. Compared to the 2.35 V you mentioned earlier this is ~20% fluctuation

The mid measurement i gave was Brymen DC volt at zero load on output pin of ACS712

The data points are from ADC output, not sure why they are different.

Ian

RobTillaart commented 1 year ago

Put the 146 measurements (2nd run) in Excel.

  1. The average (midpoint) is now 525, that is 2.57 Volt (fluctuating even more)
  2. calculated the sumSquared of the 146 measurements minus the average 525 => 10653
  3. calculated SQRT(10653/146) = 8.5420
  4. RMS = 8.5420 * 4.8875 /100 ==> 0.417

0.417 is pretty close to the BRYMEN value, (given the zero level is probably fluctuating)

Conclusions so far

  1. The algorithm is good
  2. The zero level voltage is fluctuating (a lot?) which prevents stable measurements.

Can you post your schematic, exactly how you connected the ACS712 to the Processor?

RobTillaart commented 1 year ago

The mid measurement i gave was Brymen DC volt at zero load on output pin of ACS712 The data points are from ADC output, not sure why they are different.

That's what we need to find out.

RobTillaart commented 1 year ago

Can you post your schematic, exactly how you connected the ACS712 to the Processor?

Most important thing is that the GND of the ACS712 and the GND of the Processor are connected. This forces a common reference for all voltages.

Second, the power supply of both the ACS712 and the Processor should be stable 5V even under a load. The ACS712 needs it to generate 2.5V as midpoint, and the Processor needs it as a reference for its ADC.

RobTillaart commented 1 year ago

Coming soon:

I'm going to implement a 0.2.8 version that includes this sampling function as it has serious added value for non trivial signals. It might replace the mA_AC() in time as the form factor is obsolete.

imk2021 commented 1 year ago

Hello Rob,

I think the present approach of trying to determine the RMS current from a single cycle is a bit optimistic. My reasoning for this is two fold, there is always going to be on the mains noise from sources we have no control over like next doors fridge etc etc. Two during our test I notice that the BRYMEN does not give and instant value of RMS current. As it takes 2, 3 or 4  seconds from the load being switched on to present a steady value, likewise when the load is switched off it take as long as 10 seconds to reach zero.

Hence i think we need a function where one of the parameters is the number of cycles to sample and see how that test rather than chase single cycle noise.

What you think?  Ian

RobTillaart commented 1 year ago

Good thoughts!

I have to look at it from every possible use case , the perspective of a library developer.

The library provides one cycle which is the minimum. A user can call that function in a loop, adjust midpoint every 7 iterations whatever meets his needs. The user can average the values, ignore outliers etc. So many scenario's you cannot parameterize the function to meet all that. So functions should be seen as the building block of a solution.

On the other hand the quality of the software solution can only compensate hardware limitations to some extend. So if there is an electric problem with midpoint the lib cant solve, multiple samples wont fix it.

imk2021 commented 1 year ago

Rob

Re multi calls to your library. Problem is with multiple calls is the call setup (stack pushing etc) means you will have a mini dead band from call to call.

I Still think it better to get in mA_AC() for n cycles, would be interesting to see the difference between: mA_AC() and mA_AC_Mean_Cycles()

Ian

RobTillaart commented 1 year ago

Implement and test your assumption. That is the only way to proof that the parameter add value.

I expect that the overhead of a function call is maybe 20x less than a single analogRead(). So you wont notice the difference, but I can be wrong

RobTillaart commented 1 year ago

this could be the code

float RMS_cycles(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin, uint16_t times)
{
  uint16_t midpoint   = 511;   // UNO midpoint.
  uint32_t period     = round(1000000UL / frequency); 
  float    AmpPerStep = mVperStep / mVperAmpere;

  if (times < 1) times = 1;

  float average = 0;

  for (uint16_t i = 0; i< times; i++)
  {
    uint16_t samples    = 0;
    float    sumSquared = 0;

    uint32_t start = micros();
    while (micros() - start < period)
    {
      samples++;
      uint32_t current = analogRead(analogPin) - midpoint;
      sumSquared += (current * current);
    }
    float RMS = sqrt(sumSquared / samples) * AmpPerStep;
    average += RMS;
 }

  return average/times;
}
RobTillaart commented 1 year ago

Another option could be a parameter to add the number of seconds the sampling should take.

float RMS_seconds(float frequency, float mVperStep, float mVperAmpere, uint8_t analogPin, uint8_t seconds)
{
  uint16_t midpoint   = 511;   // UNO midpoint.
  uint32_t period     = seconds * 1000000UL; 
  float    AmpPerStep = mVperStep / mVperAmpere;
  uint32_t samples    = 0;
  float    sumSquared = 0;

  uint32_t start = micros();
  while (micros() - start < period)
  {
    samples++;
    uint32_t current = analogRead(analogPin) - midpoint;
    sumSquared += (current * current);
  }
  float RMS = sqrt(sumSquared / samples) * AmpPerStep;
  Serial.println(samples);
  return RMS
}
imk2021 commented 1 year ago

Hello Rob,

I'll will give both versions a test tonight for you on 100W, 200W and 1kW loads this evening and let you know.

Ian

RobTillaart commented 1 year ago

Created a develop branch 0.2.8 with

imk2021 commented 1 year ago

Hello Rob, sorry but things domestic busy so only limited testing this evening, will do more tomorrow.

See below sketch Ian

TheRMS = RMS_Cycles( 50.0, 4.887586, 100.0, ACS712_ANALOG_PIN , 50);

No Load BRYMEN 0.01A LCD RMS 0.1 100W Load BRYMEN 0.433A LCD RMS Jitter 2048.9 to 2062.0 Think jitter is pretty good

TheRMS = RMS_Duration( 50.0, 4.887586, 100.0, ACS712_ANALOG_PIN , 1);

No Load BRYMEN 0.01A LCD RMS 0.12 100W Load BRYMEN 0.433A LCD RMS Jitter 2111.0 to 2128.6 Think jitter is pretty good

I think I have a questionable connection from ACS712 to Nano, will look into this in morning. Question what is you plan to get present RMS output  into mA's PS What you think of Arduino IDE V2, I think is amazing :-)

// //    FILE: ROB_RMS_TEST_SAMPLE_SET_1.ino //  AUTHOR: Ian Knight, Rob Tillaart // PURPOSE: To test the Return RMS Function //    DATE: 2022-08-19

defineADJUSTABLE_MAX_CURRENT_ANALOG_PIN              A1

defineHYSTERESIS_CURRENT_IN_MA 100

include

defineACS712_ANALOG_PIN                             A0

// initialize the library by associating any needed LCD interface pin // with the arduino pin number it is connected to // const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; // Working OK constintrs = 12, en = 11, d4 = 7, d5 = 6, d6 = 5, d7 = 4; LiquidCrystallcd(rs, en, d4, d5, d6, d7); floatTheRMS; longMaxmA; // Adjusted maximum current //  ----------------------------- Prototypes

voidDisplayToLCDTotalCurrent( void); voidDisplayToLCDMaxCurrent( void); //  PARAM         TYPICAL VALUE //  frequency   = 50.0 //  mVperStep   = 5000.0 / 1023.0  (Assume Arduino UNO ADC) = 4.887586 //  mVperAmpere = 100.0 //  analogPin   = A0 //float RMS_by_sampling( float frequency , float mVperStep , float mVperAmpere , uint8_t analogPin ); floatRMS_Duration(floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin, uint8_tseconds); floatRMS_Cycles(floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin, uint16_ttimes); // ------------------------------------------------ setup()

voidsetup() { // set up the LCD's number of columns and rows: lcd.begin(16, 2);  // Init LCD Serial.begin( 9600); //lcd.print("hello, world!");   // Print a message to the LCD. } // -------------------------------------------------- loop()

voidloop() { //TheRMS = RMS_Cycles( 50.0 , 4.887586 , 100.0 , ACS712_ANALOG_PIN , 50 ); // test Hz cycles TheRMS = RMS_Duration( 50.0, 4.887586, 100.0, ACS712_ANALOG_PIN , 1); // test duration DisplayToLCDTotalCurrent(); MaxmA = analogRead( ADJUSTABLE_MAX_CURRENT_ANALOG_PIN ); MaxmA = map( MaxmA , 0, 1023, 0, 20000); // Map ADC 0->1023 to 0->20000 mA DisplayToLCDMaxCurrent(); } // -------------------------------------------  RMS_Duration()

floatRMS_Duration(floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin, uint8_tseconds) { uint16_tmidpoint   = 511;  // UNO midpoint. uint32_tperiod     = seconds 1000000UL; float   AmpPerStep = mVperStep / mVperAmpere; uint16_tsamples    = 0; float   sumSquared = 0; uint32_tstart = micros(); while(micros() - start < period)   {     samples++; uint32_tcurrent = analogRead(analogPin) - midpoint;     sumSquared += (current current);   } floatRMS = sqrt(sumSquared / samples) * AmpPerStep; returnRMS; } // --------------------------------------------------- RMS_Cycles()

floatRMS_Cycles(floatfrequency, floatmVperStep, floatmVperAmpere, uint8_tanalogPin, uint16_ttimes) { uint16_tmidpoint   = 511;  // UNO midpoint. uint32_tperiod     = round(1000000UL/ frequency); float   AmpPerStep = mVperStep / mVperAmpere; if(times < 1) times = 1; floataverage = 0; for(inti = 0; i< times; i++)   { uint16_tsamples    = 0; float   sumSquared = 0; uint32_tstart = micros(); while(micros() - start < period)     {       samples++; uint32_tcurrent = analogRead(analogPin) - midpoint;       sumSquared += (current current);     } floatRMS = sqrt(sumSquared / samples) AmpPerStep;     average += RMS;  } returnaverage/times; } // ------------------------------------------ DisplayToLCDTotalCurrent( ) ---------------------------------- voidDisplayToLCDTotalCurrent( void) { lcd.setCursor(0, 1); lcd.print("                ");// Clear Line lcd.setCursor(0, 1); lcd.print( TheRMS );  // print the RMS AC current } // ------------------------------------------- DisplayToLCDMaxCurrent()

voidDisplayToLCDMaxCurrent( void) { lcd.setCursor(0, 0);// Col, row lcd.print("                ");// Clear Line lcd.setCursor(0, 0); lcd.print( MaxmA );  // print the System Maximum mA } // -- END OF FILE --

RobTillaart commented 1 year ago

Question what is you plan to get present RMS output into mA's

The sampling function will return float mA in 0.2.8

In 0.3.0 I planned to redo all return types. mA_AC() and mA_DC() will also return float. Especially for the ASC712-5A this gives a bit more accuracy for low currents, think ESP32 with 12 bits ADC Furthermore I intent to investigate the use of external ADC's e.g ADS1115 ADC which is 16 bit. This gives a resolution below 1 mA.

Don't know if it will be a new class ACS712_EXT or that it can be build in and add a function to overwrite the analogRead().

PS What you think of Arduino IDE V2, I think is amazing :-) Not used it yet, I'm still on 1.8.19

imk2021 commented 1 year ago

Morning Rob, as promised more test data

TheRMS = RMS_Cycles( 50.0, 4.887586, 100.0, ACS712_ANALOG_PIN , 50);

No Load BRYMEN 0.004A LCD RMS 0.25

100W Load BRYMEN 0.43A LCD RMS Jitter 2130.5 to 2135.5 200W Load BRYMEN 0.824A LCD RMS Jitter 2198.5 to 2201.5 1000W Load BRYMEN 4.505A LCD RMS Jitter 2252 to 2254

TheRMS = RMS_Duration( 50.0, 4.887586, 100.0, ACS712_ANALOG_PIN , 1);

100W Load BRYMEN 0.43A LCD RMS Jitter 2175 to 2186 200W Load BRYMEN 0.824A LCD RMS Jitter 2217 to 2223 1000W Load BRYMEN 4.505A LCD RMS Jitter 2251 to 2256

Note Output of ACS712 monitored on scope as follows: 100W PP 140mV to 168mV, RMS 40.9 mV 200W PP 240mV to 256mV, RMS 76.5 mV 300W PP 352mV to 368mV, RMS 116.2 mV 1000W PP 1.2V to 368mV, RMS 421.5 mV See Image

Am about today if more testing required Ian

RobTillaart commented 1 year ago

The BRYMEN measurements look all consistent with each other.

The RMS scope measurements also look consistent, although the RMS measured by scope is ~10% too low.

100 Watt => 40.9 mV RMS with 100mVperAmpere => 0.409 Ampere which is pretty close to the 0.43 200 Watt => 76.5 mV RMS with 100mVperAmpere => 0.765 Ampere which is pretty close to the 0.824 1000 Watt => 421 mV RMS with 100mVperAmpere => 4.21 Ampere which is pretty close to the 4.505

RobTillaart commented 1 year ago
100W Load BRYMEN 0.43A LCD RMS Jitter 2175 to 2186
200W Load BRYMEN 0.824A LCD RMS Jitter 2217 to 2223
1000W Load BRYMEN 4.505A LCD RMS Jitter 2251 to 2256

The output on the LCD is remarkable close to each other while one should expect a factor 10 between 100W and 1000W. Can you verify you have the right analog port A0 ?

imk2021 commented 1 year ago

Good Afternoon Rob,

RE your: The output on the LCD is remarkable close to each other while one should expect a factor 10 between 100W and 1000W. Can you verify you have the right analog port A0 ?

Yes jumper wire goes from ACS712 Out to NANO A0 (Pin 14) then I plug another DuPont  jump into scope prob. Only cheap scope but it does most jobs fairly well, although on the 1000W load i noticed that the sine seems to have a flat top. Sign of transformer at end of road is saturating and only summer am expecting blackouts this winter :-(

All best Ian

imk2021 commented 1 year ago

https://github.com/RobotDynOfficial/RBDDimmer/issues/58

RobTillaart commented 1 year ago

Cheap scopes are remarkable good and useful. I do a lot with an "el cheapo" JYE Tech DSO150 - I have two of those. 95% of the time it is just to confirm there is a signal and that it looks as expected.

Sign of transformer at end of road is saturating and only summer am expecting blackouts this winter :-(

Does not sound like most reliable infrastructure. Do you have backups in place? I would invest in a bunch of car batteries and some LED light infrastructure.

RobTillaart commented 1 year ago

RobotDynOfficial/RBDDimmer#58

The Serial print is interrupt based => that disrupts the timing of the dimmer which uses a HWtimer interrupt.

imk2021 commented 1 year ago

The Serial print is interrupt based => that disrupts the timing of the dimmer which uses a HWtimer interrupt.

Yes that is the issue; one wonder why they didn't think serial out was an issue or didn't bother to test it Give that serial out must be one of the most used feature of Arduino.