Measure PWM Current with a Modified Moving Average

How do you measure a signal that keeps changing?

Pulse Width Modulation (PWM) makes it possible to dim lights, control the speed of motors, and (with the help of filters) generate analog reference voltages. When measuring the voltage or current of a PWM signal, there are unique challenges. You can use this tutorial to measure PWM current with a modified moving average (MMA).

As I’ve mentioned in a few posts, I’m working on an RGB LED IoT controller. Soon I’ll post some detailed information on that. In the meantime, one choice I made was to include an INA219 current sensor on the LED power supply. For prototyping, I’m using the Adafruit Breakout Board, but my final design uses the same parts integrated into a custom PCB.

IoT RGB 8266 Controller

When using this current sensor I ran into an unexpected issue. It’s fast, and it’s precise. (Isn’t that a good thing?) The way it works is by measuring the voltage drop across a precision shunt resistor that the load current flows through. Using Ohm’s Law, it comes up with a current measurement. However, it is much faster than I expected and was providing (nearly) instant answers. So each time I checked the sensor, I got a seemingly random value.

The problem is the value depended on when I sampled the PWM waveform, like illustrated below. I’d get the “high” current, “low” current (none), or a transition. The “high” current was useful, but none of the other values.

4 Random Sample Points

Using a simple averaging algorithm I sometimes got stable values, but not by much. Plus, it was taking up hundreds of bytes of RAM just to get an idea of the RMS current used in my design. I needed that so I could more easily calculate power usage.

That’s when I learned about Modified Moving Average or MMA. This simple algorithm uses only a few bytes of RAM and is more accurate than a simple averaging algorithm. Plus if you’re using a slower processor, like the Uno’s ATmega328p this method uses much less floating point. (This article explains other types of moving averages.)

Algorithm to measure PWM current

When using MMA, you only need to store three values: “Moving Average Sum,” “Moving Average,” and the number of samples. Obviously, the number of samples can be a constant, saving a byte or two of memory.

Moving Average Sum

Instead of collecting N samples and keep a history of samples, or measuring N samples all at once (a horrible idea, btw), the Moving Average Sum is just that. The previous measurements summed together, with a slight twice.

Moving Average

After a new sample is added to the modified moving average, a new average value is calculated. This math is the most time-consuming step since division is involved. (If you didn’t know, most processors don’t know how to divide.)

How MMA Works

modified-moving-average-equation-05

The algorithm works like this: Subtract the previous average from the sum. Get a new sample and add that to the sum, replacing the “average” you just removed. Then, recalculate the average.

The idea is that since you are removing, effectively, 1 sample and then adding a new sample in, it allows the average to shift in time.

This averaging will smooth out fluctuations while providing an accurate average, all while using only 4-6 bytes of RAM. The best part is that if you increase the number of samples used for the sum, neither the RAM nor the calculation time changes.

MMA Code

During each iteration of the main loop, my RGB LED IoT controller samples a new value and updates the moving average. Before I moved to the ESP8266, this code took about 56us to execute on an Uno. That speed is fast enough; it has little impact on the program’s performance.


//inside of loop()
 float currentCurrentValue = ina219.getCurrent_mA();
 if (currentCurrentValue > maxCurrent)
 maxCurrent = currentCurrentValue;

 movingAverageSum -= movingAverage;
 movingAverageSum += currentCurrentValue;
 movingAverage = movingAverageSum / N;

Comparing Simple Average to MMA

I have prepared two Arduino projects to compare a Simple Average to the Modified Moving Average. You can download the Simple Average Example Here and grab the MMA example from both. For the hardware, you can use almost any Arduino. You just need PWM and Analog In pins. (Be careful on ESP8266s, since the maximum Analog In is only 1 volt.) In my test, I used an Uno and inserted a wire from digital pin 5 to analog pin A0.


// Modified Moving Average for PWM Curerent or PWM Voltage
// by James Lewis
// www.baldengineer.com/mma
//
// Connect a wire between pin 5 (pwmPin) and analog 0 (analogPin)
// Open the serial monitor. Send the + and - characters to change
// the speed. (you can type in multiples like '+++' to change quickly)
//
// The "average" voltage valued display is the PWM average
// uses 244 bytes of RAM versus 442 bytes of RAM for 100 averages
// calcultes in about 56us versus 1068us

// Pins
const byte pwmPin = 5;
const byte analogPin = A0;

// Let's compare an average of 100
const byte averageCount = 100;

// So we can change the PWM value
int PWMvalue = 128; //~50% Duty

// Variables for the Modified Moving Average
float movingAverage;
float movingAverageSum;

// Timer variables
unsigned long previousMillis = 0;
unsigned int printInterval = 500;
unsigned long startTime = 0;
unsigned long stopTime = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(analogPin, INPUT); // not necessary, for FYI
  pinMode(pwmPin, OUTPUT); // not neceessary, for FYI

  // Start the PWM waveform
  analogWrite(pwmPin, PWMvalue); // ~50% Duty Cycle

  // Pre-load MMA
  for (int x=0; x < averageCount; x++)
    movingAverageSum = movingAverageSum + analogRead(analogPin);

   // Calculate inital average
   movingAverage = movingAverageSum / averageCount;
}

void loop() {
  // check for serial control
  handleSerial();

  // each interation of loop, update moving average
  // Get a new sample
  unsigned int currentValue = analogRead(analogPin);

  startTime = micros();
  // Remove previous movingAverage from the sum
  movingAverageSum = movingAverageSum - movingAverage;
  // Replace it with the current sample
  movingAverageSum = movingAverageSum + currentValue;
  // Recalculate movingAverage
  movingAverage = movingAverageSum / averageCount;
  stopTime = micros();

  if (millis() - previousMillis >= printInterval) {
    Serial.print(F("currentValue: "));
    Serial.println(currentValue);
    Serial.print(F("PWM Value: "));
    Serial.println(PWMvalue);
    Serial.print(F("Moving Average: "));
    Serial.println(movingAverage);
    Serial.print("Calculation time: ");
    Serial.print(stopTime - startTime);
    Serial.println(" us");
    Serial.println();
    Serial.flush();

    // reset the millis clock
    previousMillis = millis();

  }

}

void handleSerial() {
  // we only care about two characters to change the pwm
  if (Serial.available() > 0) {
    switch (Serial.read()) {
      case '+':
        PWMvalue = PWMvalue + 32;
        if (PWMvalue >= 255)
          PWMvalue = 255;
      break;
      case '-':
        PWMvalue = PWMvalue - 32;
        if (PWMvalue <= 0)
          PWMvalue = 0;
      break;
    }
    analogWrite(pwmPin, PWMvalue);
  }
}

Here is a sample of the output from the MMA.


currentValue: 1022
PWM Value: 255
Moving Average: 1022.05
Calculation time: 56 us

currentValue: 1022
PWM Value: 159
Moving Average: 595.21
Calculation time: 56 us

This table illustrates why I like this method. First, it is 20 times faster. Second, it uses less RAM., And I see far fewer fluctuations in the average value. The trade-off, of course, can be that if you are looking for any random or instantaneous glitches, they’ll be smoothed out. However, any averaging algorithm will eliminate them.

SMA vs MMA

SMAMMA
Size (bytes)442244
Cal Time (us)106856
AccuracyOkGood!

Conclusion

Whether you are measuring PWM Current or PWM Voltage, using an MMA can give you the average output value. When calculating power, this is helpful because you don’t need to include time in your math. When looking at a voltage, you can get an idea of the effective voltage for a load like a LED or Motor.

Industry Notes

Check outย this application note from Intersil on “Sensing Elements for Current Measurements.” Cool overview of different technologies for current measurements. (In my example here, I am using a Shunt Resistor.)