Better AC sensing circuit for ADC conversion

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
you have switched controller several times (Arduino Uno, PIC, Arduino Nano, Proteus...). are you sure that ADC resolution is 1024 counts? because i know that even on Arduino Uno, there is a difference between R3 and R4. read datasheets - that is what they are for.

the crude but simple method is to get a multimeter, measure AC voltage (say you get 226VAC), then look at ADC raw value (say 580) and calculate scaling factor:

226/580=0.39

then your AC voltage is

float vac= 0.39 * analogRead(A3);
If I put 5V dc to the head of zenar diode D5, then the voltage divider improves to 4.97V, I was wrong to check it. But, if we think its a 8-bit ADC, https://docs.arduino.cc/resources/datasheets/A000005-datasheet.pdf, then Resolution = Vref /(2n) − 1 = 0.019, if I place it in code it does not work. Correct me if I am wrong.

My last response to you, I was talking about this code when the relay for AC210V changes its position.
C:
int acval = 0, acvalold, actarget = 210;
int relayPin[] = {3, 4, 5, 6};

void setup() {
  Serial.begin(9600);
  Serial.println("meaw");
  for (int i = 0; i < 4; i++) {
    pinMode(relayPin[i], OUTPUT);
    digitalWrite(relayPin[i], HIGH);
    delay(100);
  }
}

void loop() {
  acval = analogRead(A0); // read the value
  if (acval != acvalold) { // only update if acval updates
    acvalold = acval; // store old value
    Serial.print("ACval ");
    Serial.print(acval);
    if (acval < actarget) {
      Serial.print(" is less than ");
      Serial.print(actarget);
      Serial.println("V. Relays ON.");
      for (int i = 0; i < 4; i++) {
        digitalWrite(relayPin[i], HIGH); // relays ON
      }
    } else {
      Serial.println(". Relays OFF.");
      for (int i = 0; i < 4; i++) {
        digitalWrite(relayPin[i], LOW); // relays OFF
      }
    }
  }
}
 
Last edited:

panic mode

Joined Oct 10, 2011
5,013
If I put 5V dc to the head of zenar diode D5, then the voltage divider improves to 4.97V, I was wrong to check it.
not sure what you mean... connecting 4V give you (almost) 5V at the ADC? it should, ADC input is high impedance. btw connecting 5V to 5V zener without current limit can be destructive.

But, if we think its a 8-bit ADC, https://docs.arduino.cc/resources/datasheets/A000005-datasheet.pdf, then Resolution = Vref /(2n) − 1 = 0.019, if I place it in code it does not work. Correct me if I am wrong.
why are you telling us that? this is not productive. we still no idea what exact controller you have there... so we cannot even tell if you are reading correct documentation.

when you have a problem that you cannot resolve on your own, you should tell us what exact product and settings you use. then we can try to help.

My last response to you, I was talking about this code
C:
int acval = 0, acvalold, actarget = 210;
int relayPin[] = {3, 4, 5, 6};

void setup() {
  Serial.begin(9600);
  Serial.println("meaw");
  for (int i = 0; i < 4; i++) {
    pinMode(relayPin[i], OUTPUT);
    digitalWrite(relayPin[i], HIGH);
    delay(100);
  }
}
//
}
what is the point in turning all relays on? it should never be more than one active.
 

panic mode

Joined Oct 10, 2011
5,013
According to the manuals I've read the ADC is 10bit.
"acval = analogRead(A0);"
Shouldn't that be using (A6) or (A7), they are the ADC inputs.
the hardware resolution on Uno before Rev4 is 10bit. on Rev4 it is 14-bit. similarly Nano was originally 10-bit bit never variants may be 12-bit for example (Nano33).

in software one can specify desired resolution. raw value is simply shifted left/right based on user program. the least significant bits are padded with zeros or discarded:

https://docs.arduino.cc/language-reference/en/functions/analog-io/analogReadResolution/

so still waiting on Hasan2019 to state what exact board type is being used.
 

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
not sure what you mean... connecting 4V give you (almost) 5V at the ADC? it should, ADC input is high impedance. btw connecting 5V to 5V zener without current limit can be destructive.
Yes, I was trying to follow you what you have pointed out in post #219, Proteus dont care about damage on the board, but yes its an issue . I think result is same if I keep as it was.

why are you telling us that? this is not productive. we still no idea what exact controller you have there... so we cannot even tell if you are reading correct documentation.
Its a virtual model from www.enginneringprojects.com , I can not remember when I have downloaded it. Its Arduino Nano

when you have a problem that you cannot resolve on your own, you should tell us what exact product and settings you use. then we can try to help.
Can you suggest else from proteus model ? Or may be I can use Arduino Uno again, because Nano is also 10 bit.

what is the point in turning all relays on? it should never be more than one active.
Intention was clear, I had a targeted AC value where I wanted to activate relay. But it is also wrong. Do you think in my schematic the priority selection IC, relay driver arrangement is wrong? Read this project, Dont mind if its very easy, dirty, old type to you. See the attached file.
 

Attachments

Last edited:

panic mode

Joined Oct 10, 2011
5,013
i do not have Proteus.

you still did not state what version of Nano. as stated before there are different versions of it and capabilities are not the same.

no, your intent was not clear. that project is something you considered around post #70. now we are past #240 and you keep moving goal posts. i don't have time for this so good luck to you.
 
Last edited:

MrAl

Joined Jun 17, 2014
13,720
I believed it from beginning but people in other forum did NOT agree to avoid true RMS, anyway professor Smith try to figure out the problem in post #212, #217
Hi,

Oh, are you saying you do need true RMS or you might need true RMS?
In either case, you would have to sample the sine itself you can't filter. You can still rectify, but no filtering.
If this is line frequency, then you can probably do it. If the frequency is too high though it requires faster ADC devices.

The idea is the same as obtaining the average but instead of using samples 'x' you use squared samples x^2.
sum=sum+Vsample^2-avg
avg=sum/N

The difference however is that you either have to sync to each half cycle, or you have to accumulate over a large number of half cycles. Going over a large number of half cycles, you get the average RMS value, syncing means you get the RMS of each half cycle in turn.
 

MrAl

Joined Jun 17, 2014
13,720
@Hasan2019

Ok, I went over the algorithm given just before this post and I found that it is just an approximation, which will come out either higher than 0.7071 or lower than 0.7071 for a true sine wave. It depends on what sample we stop sampling at.

To improve the algorithm, we'd have to provide a correction that involves summing small error terms. To avoid that, it would probably be better to sync to the half cycle, compute the RMS value, and for the average RMS we simply sum the RMS values over several half cycles, then divide by the number of half cycles. If the RMS values were stored in an array that would look like this:

for k=1 to N_halfcycles do
sum=sum+RMS[k]
end for
RMS=sum/N

but since we can simply sum the RMS values in the original loop, we just need to add the line:
sum_RMS=sum_RMS+RMS

where RMS was calculated over the half cycle.

The advantage of doing it like this is that we get very close to the actual RMS value even if the sine wave is flat topped, which is typical for line voltages.
For a pure sine wave test signal we get an exact RMS value using 16 digit floating point found in most math software:
0.7071067812 (result)
0.7071067812 (1/sqrt(2) exactly)

Of course if we keep everything an integer as before we can divide by a power of 2 again too and keep the calculations fairly fast.

The value of the previous algorithm would be closer to 0.695 or 0.711 depending on what sample we stop sampling on just before we display the result. That's not exactly too bad either though.

Note when you divide the peak by sqrt(2) to get the RMS value, that only works with a pure sine wave. If you don't need more accuracy though then it will probably be just fine as long as the input is always at least close to a pure sine wave.
 

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
@Hasan2019

Ok, I went over the algorithm given just before this post and I found that it is just an approximation, which will come out either higher than 0.7071 or lower than 0.7071 for a true sine wave. It depends on what sample we stop sampling at.

To improve the algorithm, we'd have to provide a correction that involves summing small error terms. To avoid that, it would probably be better to sync to the half cycle, compute the RMS value, and for the average RMS we simply sum the RMS values over several half cycles, then divide by the number of half cycles. If the RMS values were stored in an array that would look like this:

for k=1 to N_halfcycles do
sum=sum+RMS[k]
end for
RMS=sum/N

but since we can simply sum the RMS values in the original loop, we just need to add the line:
sum_RMS=sum_RMS+RMS

where RMS was calculated over the half cycle.

The advantage of doing it like this is that we get very close to the actual RMS value even if the sine wave is flat topped, which is typical for line voltages.
For a pure sine wave test signal we get an exact RMS value using 16 digit floating point found in most math software:
0.7071067812 (result)
0.7071067812 (1/sqrt(2) exactly)

Of course if we keep everything an integer as before we can divide by a power of 2 again too and keep the calculations fairly fast.

The value of the previous algorithm would be closer to 0.695 or 0.711 depending on what sample we stop sampling on just before we display the result. That's not exactly too bad either though.

Note when you divide the peak by sqrt(2) to get the RMS value, that only works with a pure sine wave. If you don't need more accuracy though then it will probably be just fine as long as the input is always at least close to a pure sine wave.
@MrAI, I will look at it and your other posts also. Better you copy some code here and make comment where I need to modify them. I also think that all circuits are somewhat ok , but firmware is crucial here.
 

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
i do not have Proteus.

you still did not state what version of Nano. as stated before there are different versions if it and capabilities are not the same.

no, your intent was not clear. that project is something you considered around post #70. now we are past #240 and you keep moving goal posts. i don't have time for this so good luck to you.
Great @panic mode . You did a good contribution here which I should agree with. I dont think hardware is a problem here, root cause is coding. Bye. Thanks.
 

eetech00

Joined Jun 8, 2013
4,709
Hi again.

Here is a proteus simulation using just the AC to ADC interface.
I've used an arduino library called TrueRMS. The simulation shows the results of a 120vac input.
The scaling is done automatically by the library routines after providing some values.
Note: V1 is shown grounded for simulation purposes only.
1768931474522.png
 

MrAl

Joined Jun 17, 2014
13,720
@MrAI, I will look at it and your other posts also. Better you copy some code here and make comment where I need to modify them. I also think that all circuits are somewhat ok , but firmware is crucial here.
Hi,

Yes we can talk about this more too.

The half cycle sync expressions are:
sum=sum+x^2
over one half cycle from 1 to N. Then:
RMS=sqrt(sum/N)
Then if you want to average the RMS value you can do the regular moving average calculations.
That's a literal Root of the Mean of the Square.

Something like this (x is the new sample):
RMSsum=0
for i=1 to M do
sum=0
for k=1 to N do
sum=sum+x^2
end for
RMS=sqrt(sum/N)
RMSsum=RMSsum+RMS-avgRMS
avgRMS=RMSsum/N
end for

Note there are two loops, an inner loop that calculates the half cycle RMS value, then the outer loop that calculates the average RMS value. Averaging the RMS values from the multiple half cycles helps to keep the measurement stable. If you intend to build a data set on half cycle RMS values though then you would want to save each RMS half cycle result without averaging.

I think you should strive to understand these algorithms rather than just take one and plug it into your program. You'll get more out of it too.
 

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
Hi again.

Here is a proteus simulation using just the AC to ADC interface.
I've used an arduino library called TrueRMS. The simulation shows the results of a 120vac input.
The scaling is done automatically by the library routines after providing some values.
Note: V1 is shown grounded for simulation purposes only.
View attachment 362457
Great, I am busy on office, so I had no chance to make it. But you did well. But let me know how you did without writing firmware.
 

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
Yes we can talk about this more too.

The half cycle sync expressions are:
sum=sum+x^2
over one half cycle from 1 to N. Then:
RMS=sqrt(sum/N)
Then if you want to average the RMS value you can do the regular moving average calculations.
That's a literal Root of the Mean of the Square.
Are you talking about this formula ?

1768986180490.png

Something like this (x is the new sample):
RMSsum=0
for i=1 to M do
sum=0
for k=1 to N do
sum=sum+x^2
end for
RMS=sqrt(sum/N)
RMSsum=RMSsum+RMS-avgRMS
avgRMS=RMSsum/N
end for
May be you are witting in C/Matlab or asm, let me write it here in Arduino, kindly read it
C:
/**
 * Average of Per-Block RMS (Arduino)
 *
 * Computes RMS for each block of N samples and then averages those RMS values over M blocks.
 *
 * Hardware: Any Arduino with analog input. Default pin: A0.
 * Notes:
 *  - By default, RMS includes DC (no detrending). You can enable per-block DC removal.
 *  - On 10-bit ADC (UNO), analogRead() ∈ [0,1023]. Adjust reference if needed.
 */

#define ANALOG_PIN           A0
#define ADC_RESOLUTION_BITS  10        // 10 for Uno/Nano/Pro Mini; 12 for some boards (adjust if needed)
#define ADC_REF_VOLTAGE      5.0f      // Volts; set to 3.3f if using 3.3V reference or use analogReference()
#define USE_VOLT_UNITS       false     // If true, convert ADC counts to volts before RMS

// ---- Sampling & block parameters ----
const uint16_t N = 256;                // Samples per block (power of 2 not required)
const uint16_t M = 50;                 // Number of blocks to average
const uint32_t SAMPLE_PERIOD_US = 200; // Sampling period per sample (us). 200us -> 5 kHz sample rate

// ---- Optional DC removal per block ----
// If true, subtract per-block mean before squaring (RMS of AC component).
const bool REMOVE_DC_PER_BLOCK = false;

// ---- Internal buffers/accumulators ----
float avgBlockRMS = 0.0f;              // Running average of block RMS
uint16_t blocksProcessed = 0;          // How many blocks have been processed

// A small function to get a single sample (as float), optionally in volts
inline float readSample() {
  int raw = analogRead(ANALOG_PIN); // 0..(2^ADC_RESOLUTION_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_RESOLUTION_BITS) - 1);
    return raw * scale;              // Convert to volts
  } else {
    return static_cast<float>(raw);  // Keep in ADC counts
  }
}

/**
 * Compute RMS for one block of N samples.
 * If REMOVE_DC_PER_BLOCK == true, subtract mean before RMS (i.e., AC RMS).
 * Sampling is time-controlled by SAMPLE_PERIOD_US between reads.
 */
float computeBlockRMS() {
  // Optional two-pass method for DC removal: first get mean, then get mean of squared (x-mean)^2
  if (REMOVE_DC_PER_BLOCK) {
    // Pass 1: mean
    float sum = 0.0f;
    unsigned long tStart = micros();
    for (uint16_t k = 0; k < N; ++k) {
      sum += readSample();
      // Simple timing control
      unsigned long tNext = tStart + (k + 1) * SAMPLE_PERIOD_US;
      while ((long)(micros() - tNext) < 0) { /* wait */ }
    }
    float mean = sum / N;

    // Pass 2: mean of squared deviations
    float sumsq = 0.0f;
    tStart = micros();
    for (uint16_t k = 0; k < N; ++k) {
      float x = readSample() - mean;
      sumsq += x * x;
      unsigned long tNext = tStart + (k + 1) * SAMPLE_PERIOD_US;
      while ((long)(micros() - tNext) < 0) { /* wait */ }
    }
    return sqrtf(sumsq / N);
  } else {
    // Single pass: mean of squares
    float sumsq = 0.0f;
    unsigned long tStart = micros();
    for (uint16_t k = 0; k < N; ++k) {
      float x = readSample();
      sumsq += x * x;
      unsigned long tNext = tStart + (k + 1) * SAMPLE_PERIOD_US;
      while ((long)(micros() - tNext) < 0) { /* wait */ }
    }
    return sqrtf(sumsq / N);
  }
}

/**
 * Update running (cumulative) average of per-block RMS using:
 * avg_i = avg_{i-1} + (RMS_i - avg_{i-1}) / i
 */
inline void updateRunningAverage(float blockRMS) {
  blocksProcessed += 1;
  avgBlockRMS += (blockRMS - avgBlockRMS) / blocksProcessed;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) { /* wait on some boards */ }

  // If needed, you can change analog reference here (board dependent):
  // analogReference(DEFAULT); // DEFAULT, INTERNAL, EXTERNAL (varies by board)
  // For AVR with 1.1V internal reference:
  // analogReference(INTERNAL);
  // Then adjust ADC_REF_VOLTAGE accordingly.

  Serial.println(F("Average of Per-Block RMS"));
  Serial.print(F("N (samples/block): ")); Serial.println(N);
  Serial.print(F("M (blocks to avg): ")); Serial.println(M);
  Serial.print(F("Sample period (us): ")); Serial.println(SAMPLE_PERIOD_US);
  Serial.print(F("Units: ")); Serial.println(USE_VOLT_UNITS ? F("Volts") : F("ADC counts"));
  Serial.print(F("DC removal per block: ")); Serial.println(REMOVE_DC_PER_BLOCK ? F("Yes (AC RMS)") : F("No (includes DC)"));
  Serial.println();
}

void loop() {
  if (blocksProcessed < M) {
    float rms = computeBlockRMS();
    updateRunningAverage(rms);

    // Print results for this block
    Serial.print(F("Block ")); Serial.print(blocksProcessed);
    Serial.print(F(" RMS = ")); Serial.print(rms, 6);
    Serial.print(USE_VOLT_UNITS ? F(" V") : F(" counts"));
    Serial.print(F(" | avgRMS = ")); Serial.print(avgBlockRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));
  } else {
    // After M blocks, you can hold or continue
    Serial.println(F("Done. Final avgRMS (per-block):"));
    Serial.print(F("avgRMS = ")); Serial.print(avgBlockRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));

    // Option A: stop here
    while (true) { /* halt */ }

    // Option B: reset and continue (uncomment if desired)
    // blocksProcessed = 0;
    // avgBlockRMS = 0.0f;
  }
}
Other case is here,
1768990051006.png


Take a look at this implemented code,

C:
/**
 * Streaming Average of Per-Block RMS (No RMSsum)
 *
 * Computes RMS for each block of N samples from analog input A0,
 * and maintains a running average of the per-block RMS using:
 *   avgRMS_i = avgRMS_{i-1} + (RMS_i - avgRMS_{i-1}) / i
 *
 * Notes:
 *  - RMS here includes DC (no detrending). If you want AC RMS, see tip below.
 *  - Uses no large buffers; samples are streamed and accumulated per block.
 *  - Timing is paced with micros() to approximate a fixed sample rate.
 */

#define ANALOG_PIN           A0

// ---- Sampling & block parameters ----
const uint16_t N = 256;                 // Samples per block
const uint16_t M = 50;                  // Total number of blocks to average (stop after M)
const uint32_t SAMPLE_PERIOD_US = 200;  // 200 us => ~5 kHz sample rate

// ---- Internal state (no RMSsum) ----
float    avgRMS = 0.0f;                 // Running average of block RMS
uint16_t blocksProcessed = 0;           // Number of blocks processed so far

// ---- Optional: print in volts (false => raw ADC counts) ----
const bool USE_VOLT_UNITS = false;
const float ADC_REF_VOLTAGE = 5.0f;     // Set to 3.3f for 3.3V boards/reference
const uint8_t ADC_BITS = 10;            // 10 for Uno/Nano; 12 for some others

inline float readSample() {
  int raw = analogRead(ANALOG_PIN);     // 0..(2^ADC_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_BITS) - 1);
    return raw * scale;                 // Volts
  } else {
    return (float)raw;                  // ADC counts
  }
}

/**
 * Compute the RMS of one block of N samples.
 * (Includes DC. For AC RMS, subtract the block mean before squaring—see notes.)
 */
float computeBlockRMS() {
  float sumsq = 0.0f;

  // Start a time schedule based on micros() for steady pacing
  unsigned long t0 = micros();
  for (uint16_t k = 0; k < N; ++k) {
    float x = readSample();
    sumsq += x * x;

    // Wait until the next scheduled sample time
    unsigned long tNext = t0 + (unsigned long)((k + 1) * SAMPLE_PERIOD_US);
    // Use signed subtraction to avoid rollover issues
    while ((long)(micros() - tNext) < 0) {
      /* busy-wait to maintain sampling period */
    }
  }

  return sqrtf(sumsq / (float)N);
}

/**
 * Update the running average of per-block RMS:
 *   avgRMS <- avgRMS + (blockRMS - avgRMS) / i
 * where i := blocksProcessed after increment.
 */
inline void updateRunningAverage(float blockRMS) {
  blocksProcessed += 1;
  avgRMS += (blockRMS - avgRMS) / (float)blocksProcessed;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) { /* wait for USB serial (on some boards) */ }

  // Optionally set analog reference depending on your board:
  // analogReference(DEFAULT);   // ~Vcc
  // analogReference(INTERNAL);  // e.g., ~1.1V on some AVRs (then set ADC_REF_VOLTAGE accordingly)
  // analogReference(EXTERNAL);  // use AREF pin

  Serial.println(F("Streaming Average of Per-Block RMS (no RMSsum)"));
  Serial.print(F("N (samples/block): ")); Serial.println(N);
  Serial.print(F("M (blocks total):  ")); Serial.println(M);
  Serial.print(F("Sample period (us): ")); Serial.println(SAMPLE_PERIOD_US);
  Serial.print(F("Units: ")); Serial.println(USE_VOLT_UNITS ? F("Volts") : F("ADC counts"));
  Serial.println();
}

void loop() {
  if (blocksProcessed < M) {
    float rms = computeBlockRMS();
    updateRunningAverage(rms);

    Serial.print(F("Block "));
    Serial.print(blocksProcessed);
    Serial.print(F(" | RMS = "));
    Serial.print(rms, 6);
    Serial.print(USE_VOLT_UNITS ? F(" V") : F(" counts"));
    Serial.print(F(" | avgRMS = "));
    Serial.print(avgRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));
  } else {
    Serial.println();
    Serial.println(F("Done."));
    Serial.print(F("Final average of per-block RMS = "));
    Serial.print(avgRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));

    // Halt here; or reset counters to continue running.
    while (true) { /* stop */ }

    // To continue continuously, uncomment:
    // blocksProcessed = 0;
    // avgRMS = 0.0f;
  }
}




Note there are two loops, an inner loop that calculates the half cycle RMS value, then the outer loop that calculates the average RMS value. Averaging the RMS values from the multiple half cycles helps to keep the measurement stable. If you intend to build a data set on half cycle RMS values though then you would want to save each RMS half cycle result without averaging.
Yes, well said. Now kindly read the 2 set of code in upper and suggest which of them I should go.

I think you should strive to understand these algorithms rather than just take one and plug it into your program. You'll get more out of it too.
Yes, I will read carefully. I wont do anything before I understand.
 

eetech00

Joined Jun 8, 2013
4,709
Great, I am busy on office, so I had no chance to make it. But you did well. But let me know how you did without writing firmware.
I'm not sure I understand your question.
My proteus software has a license to run arduino simulations. After that, there are specific steps that one must take to setup proteus to run arduino simulations. The initial setup requires an arduino hex file loaded into proteus.
The library provides arduino source code example(s). I simply loaded the code, made code adjustments, and ran it in proteus to demonstrate it.
All the math (TrueRMS, RMS, AVG, etc) is done by the library routines. The code simply calls the routines, so you don;t have to write all that stuff shown in posts #253-255.
 
Last edited:

MrAl

Joined Jun 17, 2014
13,720
Yes we can talk about this more too.



Are you talking about this formula ?

View attachment 362486



May be you are witting in C/Matlab or asm, let me write it here in Arduino, kindly read it
C:
/**
* Average of Per-Block RMS (Arduino)
*
* Computes RMS for each block of N samples and then averages those RMS values over M blocks.
*
* Hardware: Any Arduino with analog input. Default pin: A0.
* Notes:
*  - By default, RMS includes DC (no detrending). You can enable per-block DC removal.
*  - On 10-bit ADC (UNO), analogRead() ∈ [0,1023]. Adjust reference if needed.
*/

#define ANALOG_PIN           A0
#define ADC_RESOLUTION_BITS  10        // 10 for Uno/Nano/Pro Mini; 12 for some boards (adjust if needed)
#define ADC_REF_VOLTAGE      5.0f      // Volts; set to 3.3f if using 3.3V reference or use analogReference()
#define USE_VOLT_UNITS       false     // If true, convert ADC counts to volts before RMS

// ---- Sampling & block parameters ----
const uint16_t N = 256;                // Samples per block (power of 2 not required)
const uint16_t M = 50;                 // Number of blocks to average
const uint32_t SAMPLE_PERIOD_US = 200; // Sampling period per sample (us). 200us -> 5 kHz sample rate

// ---- Optional DC removal per block ----
// If true, subtract per-block mean before squaring (RMS of AC component).
const bool REMOVE_DC_PER_BLOCK = false;

// ---- Internal buffers/accumulators ----
float avgBlockRMS = 0.0f;              // Running average of block RMS
uint16_t blocksProcessed = 0;          // How many blocks have been processed

// A small function to get a single sample (as float), optionally in volts
inline float readSample() {
  int raw = analogRead(ANALOG_PIN); // 0..(2^ADC_RESOLUTION_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_RESOLUTION_BITS) - 1);
    return raw * scale;              // Convert to volts
  } else {
    return static_cast<float>(raw);  // Keep in ADC counts
  }
}

/**
* Compute RMS for one block of N samples.
* If REMOVE_DC_PER_BLOCK == true, subtract mean before RMS (i.e., AC RMS).
* Sampling is time-controlled by SAMPLE_PERIOD_US between reads.
*/
float computeBlockRMS() {
  // Optional two-pass method for DC removal: first get mean, then get mean of squared (x-mean)^2
  if (REMOVE_DC_PER_BLOCK) {
    // Pass 1: mean
    float sum = 0.0f;
    unsigned long tStart = micros();
    for (uint16_t k = 0; k < N; ++k) {
      sum += readSample();
      // Simple timing control
      unsigned long tNext = tStart + (k + 1) * SAMPLE_PERIOD_US;
      while ((long)(micros() - tNext) < 0) { /* wait */ }
    }
    float mean = sum / N;

    // Pass 2: mean of squared deviations
    float sumsq = 0.0f;
    tStart = micros();
    for (uint16_t k = 0; k < N; ++k) {
      float x = readSample() - mean;
      sumsq += x * x;
      unsigned long tNext = tStart + (k + 1) * SAMPLE_PERIOD_US;
      while ((long)(micros() - tNext) < 0) { /* wait */ }
    }
    return sqrtf(sumsq / N);
  } else {
    // Single pass: mean of squares
    float sumsq = 0.0f;
    unsigned long tStart = micros();
    for (uint16_t k = 0; k < N; ++k) {
      float x = readSample();
      sumsq += x * x;
      unsigned long tNext = tStart + (k + 1) * SAMPLE_PERIOD_US;
      while ((long)(micros() - tNext) < 0) { /* wait */ }
    }
    return sqrtf(sumsq / N);
  }
}

/**
* Update running (cumulative) average of per-block RMS using:
* avg_i = avg_{i-1} + (RMS_i - avg_{i-1}) / i
*/
inline void updateRunningAverage(float blockRMS) {
  blocksProcessed += 1;
  avgBlockRMS += (blockRMS - avgBlockRMS) / blocksProcessed;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) { /* wait on some boards */ }

  // If needed, you can change analog reference here (board dependent):
  // analogReference(DEFAULT); // DEFAULT, INTERNAL, EXTERNAL (varies by board)
  // For AVR with 1.1V internal reference:
  // analogReference(INTERNAL);
  // Then adjust ADC_REF_VOLTAGE accordingly.

  Serial.println(F("Average of Per-Block RMS"));
  Serial.print(F("N (samples/block): ")); Serial.println(N);
  Serial.print(F("M (blocks to avg): ")); Serial.println(M);
  Serial.print(F("Sample period (us): ")); Serial.println(SAMPLE_PERIOD_US);
  Serial.print(F("Units: ")); Serial.println(USE_VOLT_UNITS ? F("Volts") : F("ADC counts"));
  Serial.print(F("DC removal per block: ")); Serial.println(REMOVE_DC_PER_BLOCK ? F("Yes (AC RMS)") : F("No (includes DC)"));
  Serial.println();
}

void loop() {
  if (blocksProcessed < M) {
    float rms = computeBlockRMS();
    updateRunningAverage(rms);

    // Print results for this block
    Serial.print(F("Block ")); Serial.print(blocksProcessed);
    Serial.print(F(" RMS = ")); Serial.print(rms, 6);
    Serial.print(USE_VOLT_UNITS ? F(" V") : F(" counts"));
    Serial.print(F(" | avgRMS = ")); Serial.print(avgBlockRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));
  } else {
    // After M blocks, you can hold or continue
    Serial.println(F("Done. Final avgRMS (per-block):"));
    Serial.print(F("avgRMS = ")); Serial.print(avgBlockRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));

    // Option A: stop here
    while (true) { /* halt */ }

    // Option B: reset and continue (uncomment if desired)
    // blocksProcessed = 0;
    // avgBlockRMS = 0.0f;
  }
}
Other case is here,
View attachment 362495


Take a look at this implemented code,


C:
/**
* Streaming Average of Per-Block RMS (No RMSsum)
*
* Computes RMS for each block of N samples from analog input A0,
* and maintains a running average of the per-block RMS using:
*   avgRMS_i = avgRMS_{i-1} + (RMS_i - avgRMS_{i-1}) / i
*
* Notes:
*  - RMS here includes DC (no detrending). If you want AC RMS, see tip below.
*  - Uses no large buffers; samples are streamed and accumulated per block.
*  - Timing is paced with micros() to approximate a fixed sample rate.
*/

#define ANALOG_PIN           A0

// ---- Sampling & block parameters ----
const uint16_t N = 256;                 // Samples per block
const uint16_t M = 50;                  // Total number of blocks to average (stop after M)
const uint32_t SAMPLE_PERIOD_US = 200;  // 200 us => ~5 kHz sample rate

// ---- Internal state (no RMSsum) ----
float    avgRMS = 0.0f;                 // Running average of block RMS
uint16_t blocksProcessed = 0;           // Number of blocks processed so far

// ---- Optional: print in volts (false => raw ADC counts) ----
const bool USE_VOLT_UNITS = false;
const float ADC_REF_VOLTAGE = 5.0f;     // Set to 3.3f for 3.3V boards/reference
const uint8_t ADC_BITS = 10;            // 10 for Uno/Nano; 12 for some others

inline float readSample() {
  int raw = analogRead(ANALOG_PIN);     // 0..(2^ADC_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_BITS) - 1);
    return raw * scale;                 // Volts
  } else {
    return (float)raw;                  // ADC counts
  }
}

/**
* Compute the RMS of one block of N samples.
* (Includes DC. For AC RMS, subtract the block mean before squaring—see notes.)
*/
float computeBlockRMS() {
  float sumsq = 0.0f;

  // Start a time schedule based on micros() for steady pacing
  unsigned long t0 = micros();
  for (uint16_t k = 0; k < N; ++k) {
    float x = readSample();
    sumsq += x * x;

    // Wait until the next scheduled sample time
    unsigned long tNext = t0 + (unsigned long)((k + 1) * SAMPLE_PERIOD_US);
    // Use signed subtraction to avoid rollover issues
    while ((long)(micros() - tNext) < 0) {
      /* busy-wait to maintain sampling period */
    }
  }

  return sqrtf(sumsq / (float)N);
}

/**
* Update the running average of per-block RMS:
*   avgRMS <- avgRMS + (blockRMS - avgRMS) / i
* where i := blocksProcessed after increment.
*/
inline void updateRunningAverage(float blockRMS) {
  blocksProcessed += 1;
  avgRMS += (blockRMS - avgRMS) / (float)blocksProcessed;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) { /* wait for USB serial (on some boards) */ }

  // Optionally set analog reference depending on your board:
  // analogReference(DEFAULT);   // ~Vcc
  // analogReference(INTERNAL);  // e.g., ~1.1V on some AVRs (then set ADC_REF_VOLTAGE accordingly)
  // analogReference(EXTERNAL);  // use AREF pin

  Serial.println(F("Streaming Average of Per-Block RMS (no RMSsum)"));
  Serial.print(F("N (samples/block): ")); Serial.println(N);
  Serial.print(F("M (blocks total):  ")); Serial.println(M);
  Serial.print(F("Sample period (us): ")); Serial.println(SAMPLE_PERIOD_US);
  Serial.print(F("Units: ")); Serial.println(USE_VOLT_UNITS ? F("Volts") : F("ADC counts"));
  Serial.println();
}

void loop() {
  if (blocksProcessed < M) {
    float rms = computeBlockRMS();
    updateRunningAverage(rms);

    Serial.print(F("Block "));
    Serial.print(blocksProcessed);
    Serial.print(F(" | RMS = "));
    Serial.print(rms, 6);
    Serial.print(USE_VOLT_UNITS ? F(" V") : F(" counts"));
    Serial.print(F(" | avgRMS = "));
    Serial.print(avgRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));
  } else {
    Serial.println();
    Serial.println(F("Done."));
    Serial.print(F("Final average of per-block RMS = "));
    Serial.print(avgRMS, 6);
    Serial.println(USE_VOLT_UNITS ? F(" V") : F(" counts"));

    // Halt here; or reset counters to continue running.
    while (true) { /* stop */ }

    // To continue continuously, uncomment:
    // blocksProcessed = 0;
    // avgRMS = 0.0f;
  }
}






Yes, well said. Now kindly read the 2 set of code in upper and suggest which of them I should go.



Yes, I will read carefully. I wont do anything before I understand.

Hi,

It looks like A is correct, and B is just saying that RMS=RMS because the M disappears if all blocks are the same, and we are left with the first expression in A anyway. In other words, the average RMS is equal to one block RMS if all the blocks are the same. That's not the general case though so forget about B.

I don't have time to look over your code yet, it gets a little long. It should be almost exactly equal to the forms shown already, but yes you also need code to sync to the half wave which it looks like you did. You detect the zero crossings and go from there.
It does look like you may have to subtract the mean if you have a DC offset.
You also have to be conscious of the analog read sample time if you increase the number of samples.
You might also note that it looks like the first few blocks may change the result more than the last few blocks.
Could add a PLL-like structure to improve the zero-crossing detection, but it could get complicated and probably not really necessary.
The CPU usage will be high, but I am not sure if that is uncommon, it is probably common.
I haven't checked every line though.
 

Thread Starter

Hasan2019

Joined Sep 5, 2019
199
It looks like A is correct, and B is just saying that RMS=RMS because the M disappears if all blocks are the same, and we are left with the first expression in A anyway. In other words, the average RMS is equal to one block RMS if all the blocks are the same. That's not the general case though so forget about B.
I tested this 2 codes in same circuit that was made for 5V ADC (1023), in both case.
Final avg RMS (per-block): 830 ! but when the circuit is made for 2.5V ADC then its different number !
Do you agree with this number or any calculations in mind?
OK, then we finalize option A.

I don't have time to look over your code yet, it gets a little long. It should be almost exactly equal to the forms shown already, but yes you also need code to sync to the half wave which it looks like you did. You detect the zero crossings and go from there. It does look like you may have to subtract the mean if you have a DC offset.
I should be thankful to you because many of us was ignoring. Yes, DC offsets are subtracted. I will look for zero-crossing also.

You also have to be conscious of the analog read sample time if you increase the number of samples.
You might also note that it looks like the first few blocks may change the result more than the last few blocks.
Could add a PLL-like structure to improve the zero-crossing detection, but it could get complicated and probably not really necessary. The CPU usage will be high, but I am not sure if that is uncommon, it is probably common. I haven't checked every line though.
If I put 1000 samples, the Arduino IDE will tell that memory is not possible to manage. So, I intendent to do it in 256. If we consider option A algorithm then its true that average somewhat acceptable, we are not losing data, thinking that previous data are not lost. I dont know much about PLL structure, I will look at it. But what is your suggestions to add this input code to my exiting code in post #169, #212, should I use *calibration* formula again or not ?

Or are not we doing here?

C:
inline float readSample() {
  int raw = analogRead(ANALOG_PIN); // 0..(2^ADC_RESOLUTION_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_RESOLUTION_BITS) - 1);
    return raw * scale;              // Convert to volts

If the RMS average value is 830 then, don't I need to make suitable loop for voltage level to trigger out specific relay at post #169? The post #1 circuit is non-realistic but more hypothetic because without RV1 POT you can not change your AC main input therefore can not understand which relay is responding.

But if all that matters for sampling frequency then may be I should follow this thread ? 50/60 Hz signals and ADC sampling frequency - Page 1 https://www.eevblog.com/forum/microcontrollers/5060-hz-signals-and-adc-sampling-frequency/?all
 
Last edited:

MrAl

Joined Jun 17, 2014
13,720
I tested this 2 codes in same circuit that was made for 5V ADC (1023), in both case.
Final avg RMS (per-block): 830 ! but when the circuit is made for 2.5V ADC then its different number !
Do you agree with this number or any calculations in mind?
OK, then we finalize option A.



I should be thankful to you because many of us was ignoring. Yes, DC offsets are subtracted. I will look for zero-crossing also.



If I put 1000 samples, the Arduino IDE will tell that memory is not possible to manage. So, I intendent to do it in 256. If we consider option A algorithm then its true that average somewhat acceptable, we are not losing data, thinking that previous data are not lost. I dont know much about PLL structure, I will look at it. But what is your suggestions to add this input code to my exiting code in post #169, #212, should I use *calibration* formula again or not ?

Or are not we doing here?

C:
inline float readSample() {
  int raw = analogRead(ANALOG_PIN); // 0..(2^ADC_RESOLUTION_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_RESOLUTION_BITS) - 1);
    return raw * scale;              // Convert to volts

If the RMS average value is 830 then, don't I need to make suitable loop for voltage level to trigger out specific relay at post #169? The post #1 circuit is non-realistic but more hypothetic because without RV1 POT you can not change your AC main input therefore can not understand which relay is responding.

But if all that matters for sampling frequency then may be I should follow this thread ? 50/60 Hz signals and ADC sampling frequency - Page 1 https://www.eevblog.com/forum/microcontrollers/5060-hz-signals-and-adc-sampling-frequency/?all
Hi,

A PLL loop in this context could simply mean 'syncing' the program flow to the line, not just detecting the zero crossing.
In other words, you detect a few zero crossings and then start to predict when the next one will come. If it came late one time then you subtract a little time from the stored half cycle time, and if it came early one time then you add a little time to the stored half cycle time. Eventually you get very close to the zero crossings without even having to detect them, but you do still detect them to keep the flow synced.

I don't think everyone has the time to check over long lines of code that's the problem sometimes. You have to do much of the work yourself :)
You seem to be doing very well though so don't worry too much, just test, test, and retest, you have to do that anyway.
 

drjohsmith

Joined Dec 13, 2021
1,615
I tested this 2 codes in same circuit that was made for 5V ADC (1023), in both case.
Final avg RMS (per-block): 830 ! but when the circuit is made for 2.5V ADC then its different number !
Do you agree with this number or any calculations in mind?
OK, then we finalize option A.



I should be thankful to you because many of us was ignoring. Yes, DC offsets are subtracted. I will look for zero-crossing also.



If I put 1000 samples, the Arduino IDE will tell that memory is not possible to manage. So, I intendent to do it in 256. If we consider option A algorithm then its true that average somewhat acceptable, we are not losing data, thinking that previous data are not lost. I dont know much about PLL structure, I will look at it. But what is your suggestions to add this input code to my exiting code in post #169, #212, should I use *calibration* formula again or not ?

Or are not we doing here?

C:
inline float readSample() {
  int raw = analogRead(ANALOG_PIN); // 0..(2^ADC_RESOLUTION_BITS - 1)
  if (USE_VOLT_UNITS) {
    const float scale = ADC_REF_VOLTAGE / ((1UL << ADC_RESOLUTION_BITS) - 1);
    return raw * scale;              // Convert to volts

If the RMS average value is 830 then, don't I need to make suitable loop for voltage level to trigger out specific relay at post #169? The post #1 circuit is non-realistic but more hypothetic because without RV1 POT you can not change your AC main input therefore can not understand which relay is responding.

But if all that matters for sampling frequency then may be I should follow this thread ? 50/60 Hz signals and ADC sampling frequency - Page 1 https://www.eevblog.com/forum/microcontrollers/5060-hz-signals-and-adc-sampling-frequency/?all
why have you decided to return to using true rms ?
what does the extra complication give you ?

can you post your latest code and schematics please ,
its hard in this long post to keep up as to where your up to,
so a snapshot of current condition would be usefull
 
Top