Average Measurements

Hello everyone,

I am new in this world called Arduino and I am doing a tachometer that reads and storage the RPM from a motor (12V DC) everything is working well, but, I would like to calculate the average of every minute and storage it instead of all measurements.

BTW this is my code.

#include <SPI.h>
#include <SD.h>

const int chipSelect = 4;

File dataFile;

int encoder_pin = 2; // The pin the encoder is connected
unsigned int rpm; // rpm reading
volatile byte pulses; // number of pulses
unsigned long timeold; // The number of pulses per revolution
unsigned int pulsesperturn = 1; //Number of blades
void counter()
{
//Update count
pulses++;
}
//-----------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}

Serial.print("Initializing SD card...");
// make sure that the default chip select pin is set to
// output, even if you don't use it:
pinMode(SS, OUTPUT);

// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
while (1) ;
}
Serial.println("card initialized.");

// Open up the file we're going to log to!
dataFile = SD.open("datalog.txt", FILE_WRITE);
if (! dataFile) {
Serial.println("error opening datalog.txt");
// Wait forever since we cant write data
while (1) ;
}

//-----------code for RPM---------------
//Use statusPin to flash along with interrupts
pinMode(encoder_pin, INPUT);
//Interrupt 0 is digital pin 2, so that is where the IR detector is connected
//Triggers on FALLING (change from HIGH to LOW)
attachInterrupt(0, counter, FALLING);
// Initialize
pulses = 0;
rpm = 0;
timeold = 0;
}
void loop()
{
if (millis() - timeold >= 1000){
/Uptade every one second, this will be equal to reading frecuency (Hz)./
//Don't process interrupts during calculations
detachInterrupt(0);

rpm = (60 * 1000L / pulsesperturn )/ (millis() - timeold)* pulses;
timeold = millis();
pulses = 0;
//Write it out to serial port
Serial.print("RPM = ");
Serial.println(rpm,DEC);

//Restart the interrupt processing
attachInterrupt(0, counter, FALLING);
}
// make a string for assembling the data to log:
String dataString = "";

// read three sensors and append to the string:
for (int analogPin = 0; analogPin < 3; analogPin++) {
int sensor = analogRead(analogPin);
dataString += String(sensor);
if (analogPin < 2) {
dataString += ",";
}
}

dataFile.print("RPM = ");
dataFile.println(rpm);

dataFile.flush();
delay(1000);

}

See this article in Arduino playground about running average:

https://playground.arduino.cc/Main/RunningAverage

See this article about how to use the forum.

First, don't ever detach the interrupt. You will miss counts that way. The proper way of temporarily suspending interrupts while you grab a "clean copy" of the volatile variable is to use the noInterrupts() function. This works on all variants on the Arduino. Sometimes you will see cli() used in the same way, but that only works on the AVR Arduinos. noInterrupts() is actually a macro which calls the correct variant of cli() for your board.

Second, to get a long-term average of something like RPM, it's mathematically incorrect to average the RPM of each second. You're taking an average of averages. It's much better to count the pulses for the whole minute and then divide that number by 60 seconds.

Do you still need to have the one-second readings? It's not clear from your initial request if you still need to display the 1-second readings somewhere. If all you need to do is change from 1 second to 60 seconds, then replace every instance of "1000" with "DATALOG_INTERVAL" and then put this at the top of your code:

#define DATALOG_INTERVAL 60000 //milliseconds - the time between logging the data readings

Once you've done that, then changing from 60 seconds to any other period is very easy - just change that one line and all the calculations dependent on it will change.

MorganS:
Second, to get a long-term average of something like RPM, it's mathematically incorrect to average the RPM of each second. You're taking an average of averages. It's much better to count the pulses for the whole minute and then divide that number by 60 seconds.

#define DATALOG_INTERVAL 60000 //milliseconds - the time between logging the data readings

With your perspective on this, the average will only update every minute. Every "averaging" algorithm I have come across in industry uses a "rolling average", which can update every second, for example.

To do this you need some sort of "shift register" or "rotating storage". You can save the current RPM each second, losing the value 60 seconds ago, and average the whole data file.

Logging every 60 seconds would not produce a good average if the process was stopped 2 seconds into a minute cycle, then re-started 2 seconds before that minute ended.

And it is not "mathematically incorrect" to average averages, most measurement techniques use that.

A simple way is shown below - you get an updated running everage every sample.

int aveLen = 50;
float altitude,ave2,ave3;


   ave2 += altitude;
    ave2 *= (1-1/aveLen);
    ave3 = ave2/(aveLen-1);

aveLen is the number of samples over which to average.
ave3 is the result.

As you may guess, this was for an altimeter (using a BME280)

Allan

That's not an average. That's a single pole IIR (Infinite Impulse Response) filter.

If you want to get a 1-minute average updated every second then you need to store the previous 60 readings. That way you can always see what the reading was 60 seconds before "now". But the original code didn't do anything like that. It writes the average of the last N seconds to the SD card every N seconds. (N=1.000) So it doesn't need to do any running average.

That's not an average. That's a single pole IIR (Infinite Impulse Response) filter.

If you like it's single pole lowpass FIR filter.

It can't be an IIR because there's no feedback

Allan

Yes, ave2 contains the sum of the current altitudes and 49 50ths of all the previous altitudes.