/* **********************************************************************
True RMS current measurement on 3 phases with :
- Current transformers (DL-CT1005A).
- Arduino: working on all board based on the ATmega328P
(UNO - NANO - Pro Mini ...
- Display Module: 20x4 Character HD44780 LCD with Backlight
hardwired 4 or 8 bit mode or I2C (with ransom of loss of about
212 bytes SRAM in this case).
Depending wich one is used, comment and uncomment proper lines...
Current transformer unconnect-protection is done with two BZX84C5v6 head to tail connected. Their 120 Ohm load resistor is located near to the Arduino nano. A 1Kohm/1uF lowpass for fast transients rejection goes to the analog inputs. The nano inputs are protected by two Schottky SDM10P45 diodes connected from GND to the input and from the input to VCC. The common of transformers is connected to a divider from VCC (~+2.5V named "baseline"). Transformers are wired to the Arduino with 2 meter recycled cat.4 ethernet cable.
For the schema, see the joined file "AC_3Phase_meter Diagram.pdf"
120 mesurement are taken over 3 full cycles for each phase. The reference "baseline" is averaged and refreshed on each loop so any eventual deviation will be compensated. The measurement starts at a positive edge zero crossing. Instantaneous current is aquired over 3 cycles of mains AC and stored in an 3*120 array. The RMS value is calculated for each phase from this array and the maximum is memorized. The moving averages are calculated over about 10s and the maximums are memorized. The results and their maximums are displayed, for each phase, on a 4 lines x 20 char LCD
Many thanks to Rob Tillaart for his library "RunningAverage-0.4.0.zip" :
https://github.com/RobTillaart/RunningAverage
and to Frank de Brabander for "Arduino-LiquidCrystal-I2C-library"
https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
July 30, 2021 Daniel Engel
************************************************************************
Reminder: analog conversion takes about 112s (predivisor set to 128)
*/
// #include <Wire.h>
#include <LiquidCrystal_I2C.h> // -> For I2C LCD
#include "RunningAverage.h"
#define baseLinePin 3 // Sense the mid-point of the voltage divider
#define phasePin_1 0 // Current inputs of each phase
#define phasePin_2 1 // WARNING: these labels are also used as index to fill in
#define phasePin_3 2 // the array "rawTabVal", so they shall start at "0"
#define testPin 13 // Change state at each interrupt (some sort of heart beat)
LiquidCrystal_I2C lcd(0x3f, 20, 4); // -> For I2C LCD
//LiquidCrystal_I2C lcd(0x27, 20, 4); // -> For I2C, alternate address
// #include <LiquidCrystal.h> // For wired LCD (4 or 8 bit)
/* LCD wired 8 bit mode */
//const int rs = 11, en = 10, d0 = 9, d1 = 8, d2 = 7, d3 = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
//LiquidCrystal lcd(rs, en, d0, d1, d2, d3, d4, d5, d6, d7);
/* LCD wired 4 bit mode */
//const int rs = 11, en = 10, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
//LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
const uint16_t numMean = 16; // number of values to average (16 for ~10s)
RunningAverage meanPh_1(numMean); // constructors initialize one instance
RunningAverage meanPh_2(numMean);
RunningAverage meanPh_3(numMean);
const unsigned long sampleTime = 60000UL; // sample over 60ms, 3 cycles for 50Hz
const unsigned long numSamples = 120UL; // samples number to divide sampleTime exactly, low enough for the ADC
/* Caution: strange behavior may occur if there is not enough free SRAM (heap and stack may collide).
The first apparent flaw is the inability to reach the max average value in the display column "I_avg"
Some tests showed if "numMean = 20", at least 400 byte of SRAM shall be free
( 300 should be free if "numMean = 10" and 650 if "numMean = 40 ).
More tests should be done to show if there is no "memory leak" or heap and stack collision over time
*/
const unsigned long sampleInterval = sampleTime / numSamples; // sampling interval, must be longer than ADC conversion time
int rawTabVal[numSamples][3]; // [LIGNES][COLONNES] (col_0: phase 1; col_1: phase 2; col_2: phase 3)
volatile bool heartbeatFlag = false; // flag set by timer interrupt
float rmsPhase_1 = 0; // Instantaneous RMS current (over 3 cycles)
float rmsPhase_2 = 0;
float rmsPhase_3 = 0;
float maxPh_1 = 0; // hold the maximum reached
float maxPh_2 = 0;
float maxPh_3 = 0;
float maxAveragedPh_1 = 0; ; // hold the Averaged maximum
float maxAveragedPh_2 = 0; ;
float maxAveragedPh_3 = 0; ;
float scale = ((float) 0.089) ; // 1/step value (in Volt)
unsigned int baseLine = 100; // default value for tests
unsigned long previousTime;
const unsigned long timeout = 200000;
const unsigned int timer1_counter = 34286; //interrupt 3037; => 1s /or/ 34286; => 0.5s
void setup()
{
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = timer1_counter; // preload timer
TCCR1B |= (1 << CS12); // 256 prescaler (1 << CS12) | (1 << CS10); // 1024 prescaler //
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
interrupts(); // enable all interrupts
pinMode(testPin, OUTPUT);
/* This section may be commented out to save 62 bytes of SRAM */
//
// Serial.begin(115200);
// Serial.println(__FILE__);
// Serial.print(F("Version: "));
// Serial.println(RUNNINGAVERAGE_LIB_VERSION);
// Serial.println(F("Test de mesures"));
// Serial.print(F("Default baseLine: "));
// Serial.print(baseLine);
// Serial.print(F(" "));
// Serial.print(F(" sampleInterval: "));
// Serial.print (sampleInterval);
// Serial.println(F(" ns "));
//
/* END of section */
baseLine = averageMeasure(baseLinePin, 16); // use ONLY 16 or modify bitshift in "averageMeasure"
lcd.begin(); // -> For I2C LCD
lcd.backlight(); // -> For I2C LCD
lcd.clear(); // -> For I2C LCD
// lcd.begin(20, 4); // For hard wired LCD
lcd.setCursor(1, 0);
lcd.print(F("Mesure courant RMS"));
lcd.setCursor(1, 1);
lcd.print(F("ZeroCrossing synch"));
lcd.setCursor(4, 2);
lcd.print(F("Scale : "));
lcd.print(scale, 3);
lcd.setCursor(1, 3);
lcd.print(F("Baseline : "));
lcd.print(baseLine);
for (int i = 0; i < numSamples; i++) { // Initialize the array
rawTabVal[i][phasePin_1] = 0;
rawTabVal[i][phasePin_2] = 0;
rawTabVal[i][phasePin_3] = 0;
}
meanPh_1.clear(); // clear the instance of "RunningAverage"
meanPh_2.clear();
meanPh_3.clear();
delay(3500);
lcd.clear();
}
void loop()
{
baseLine = averageMeasure(baseLinePin, 16); // use ONLY 16 or modify bitshift in "averageMeasure"
/* If not powers of 2, "bitshift" shall be replaced by "divide" in averageMeasure procedure
*/
zeroCrossing(phasePin_1);
aquireArrayRawValue(phasePin_1, numSamples, sampleInterval);
zeroCrossing(phasePin_2);
aquireArrayRawValue(phasePin_2, numSamples, sampleInterval);
zeroCrossing(phasePin_3);
aquireArrayRawValue(phasePin_3, numSamples, sampleInterval);
rmsPhase_1 = rmsValue(phasePin_1, numSamples);
rmsPhase_2 = rmsValue(phasePin_2, numSamples);
rmsPhase_3 = rmsValue(phasePin_3, numSamples);
if (rmsPhase_1 > maxPh_1) maxPh_1 = rmsPhase_1;
if (rmsPhase_2 > maxPh_2) maxPh_2 = rmsPhase_2;
if (rmsPhase_3 > maxPh_3) maxPh_3 = rmsPhase_3;
if (heartbeatFlag) {
meanPh_1.addValue(rmsPhase_1);
meanPh_2.addValue(rmsPhase_2);
meanPh_3.addValue(rmsPhase_3);
heartbeatFlag = false;
}
if ( meanPh_1.getAverage() > maxAveragedPh_1) maxAveragedPh_1 = meanPh_1.getAverage();
if ( meanPh_2.getAverage() > maxAveragedPh_2) maxAveragedPh_2 = meanPh_2.getAverage();
if ( meanPh_3.getAverage() > maxAveragedPh_3) maxAveragedPh_3 = meanPh_3.getAverage();
displayValue(); // could be added in the "if (heartbeatFlag)" section if the reading changes
// too quikly to be commfortable, the following "delay(250)" could then be removed
delay(250); // leads to about one measurement every half second
}
/*----------------------------------------------------------------
Timet 1 interrupt
----------------------------------------------------------------
*/
ISR(TIMER1_OVF_vect) // interrupt service routine
{
TCNT1 = timer1_counter; // preload timer
digitalWrite(testPin, !digitalRead(testPin));
heartbeatFlag = true;
}
/*----------------------------------------------------------------
Display values and their max on the LCD
----------------------------------------------------------------
*/
void displayValue()
{
// lcd.clear();
lcd.setCursor (1, 0);
// 12345678901234567890 // for display formatting spread over 20 characters
lcd.print(F("I_max I_avg I_eff"));
lcd.setCursor (1, 1);
lcdPrintAlign(maxPh_1, 2, ' ');
lcd.print(F(" "));
lcdPrintAlign(maxAveragedPh_1, 2, ' ');
lcd.print(F(" "));
lcdPrintAlign(rmsPhase_1, 2, ' ');
// lcd.print(F(" A"));
lcd.setCursor (1, 2);
lcdPrintAlign(maxPh_2, 2, ' ');
lcd.print(F(" "));
lcdPrintAlign(maxAveragedPh_2, 2, ' ');
lcd.print(F(" "));
lcdPrintAlign(rmsPhase_2, 2, ' ');
// lcd.print(F(" A"));
lcd.setCursor (1, 3);
lcdPrintAlign(maxPh_3, 2, ' ');
lcd.print(F(" "));
lcdPrintAlign(maxAveragedPh_3, 2, ' ');
lcd.print(F(" "));
lcdPrintAlign(rmsPhase_3, 2, ' ');
// lcd.print(F(" A"));
}
/*----------------------------------------------------------------
Calculation of rms value from the array "rawTabVal"
----------------------------------------------------------------
*/
float rmsValue(byte phase, byte number)
{
float somme_1 = 0;
float ValEff = 0;
for (byte i = 0; i < number; i++) {
somme_1 += ((float)(rawTabVal[i][phase]) * (rawTabVal[i][phase]));
}
ValEff = sqrt(somme_1 / number);
ValEff = ValEff * (float)(scale);
return (ValEff) ;
}
/*----------------------------------------------------------------
Acquire raw values for one phase and store them in the array
"rawTabVal"
----------------------------------------------------------------
*/
void aquireArrayRawValue(byte input, byte number, uint16_t interval)
{
for (int i = 0; i < number; i++) {
previousTime = micros();
rawTabVal[i][input] = (int)(analogRead(input) - baseLine);
//15624 digitalWrite(testPin, !digitalRead(testPin));
while (micros() - previousTime < interval) ;
}
}
/*----------------------------------------------------------------
Detect the rising edge zero-crossing on any input pin
----------------------------------------------------------------
*/
void zeroCrossing(byte inputPin)
{
int value;
unsigned long zeroStartTime = micros(); // used for timeout if no input voltage
value = (analogRead(inputPin) - baseLine);
while ((int)(analogRead(inputPin) - baseLine) >= 0) { // wait for the negative alternation
if (micros() - zeroStartTime > timeout) {
Serial.print(micros());
Serial.print(F(" timer actuel, dmar : "));
Serial.println(zeroStartTime);
Serial.print(F("timeout alternance positive, entre ")); // for debug
Serial.println(inputPin);
break;
}
}
while ((int)(analogRead(inputPin) - baseLine) < 0) {
if (micros() - zeroStartTime > timeout) {
Serial.print(F("timeout alternance ngative, entre ")); // for debug
Serial.println(inputPin);
break;
}
}
}
/*----------------------------------------------------------------
Average the analog measure on any input pin on N measures.
Give care not to exceed the variables limits while summing !!!
----------------------------------------------------------------
*/
uint16_t averageMeasure(byte inputPin, byte numberOfAverage)
{
unsigned long average = 0;
byte i = 0;
for (i = 0; i < numberOfAverage; i++) {
average += analogRead(inputPin);
}
// average = average / numberOfAverage; // bitshift is more efficient than divide...
average = average >> 4; // => divide by 16
return average;
}
/*----------------------------------------------------------------
Serial Print the values stored in the array "rawTabVal"
(used while debugging)
----------------------------------------------------------------
*/
/*
void printArrayRawValue()
{
for (int i = 0; i < numSamples; i++) {
Serial.print (i);
if (i < 10) Serial.print (' ');
Serial.print (F(" R : "));
Serial.print (rawTabVal[i][phasePin_1]);
Serial.print(F(" S : "));
Serial.print (rawTabVal[i][phasePin_2]);
Serial.print(F(" T : "));
Serial.println (rawTabVal[i][phasePin_3]);
}
}
/
/* \\\\\\\\\\\\\\\\\\\\ END //////////////////// */
Comments