/**********************************************************************************
*
* Accurate & Stable AC voltage and frequency measurement using Arduino MEGA board
* and current-type voltage transformer.
* Calculated voltage values are printed on 1602 LCD screen and serial monitor.
* This is a free software with NO WARRANTY - Use it at your own risk!
* https://simple-circuit.com/
*
**********************************************************************************/
#include <LiquidCrystal.h> // include Arduino LCD library
// LCD module connections (RS, RW, E, D4, D5, D6, D7)
LiquidCrystal lcd(3, 4, 5, 6, 7, 8, 9);
#define CPU_CLK 16 // define CPU clock frequency in MHz
#define Voltage_Correction 1.0116 // define voltage correction coefficient
// variable declaration
const uint8_t n = 64; // number of samples per cycle
volatile int16_t Samples[2*n]; // voltage signal samples saving array
volatile uint16_t SampleSum;
volatile uint32_t period = 0;
volatile bool ZC_Event = 0;
uint8_t m1 = 0, // used to specify the writing array (sample collecting and saving array)
m2 = 0; // used to specify the reading array
volatile uint8_t m; // used for sample saving in the array Samples[]
// Arduino setup function
void setup() {
Serial.begin(9600);
Serial.println( "Starting..." );
lcd.begin(16, 2); // Set up the LCD's number of columns and rows
// clear sample array
for (uint8_t i = 0; i < 2*n; i++)
Samples[i] = 0;
SampleSum = 0;
// Timer1 configuration
TCCR1A = 0;
TCCR1B = 0x09; // CTC mode, set timer prescaler to CPU_CLK/1 (Timer clock = 16 MHz)
// output compare registers configuration with default line frequency of 50Hz, where:
// 5000 = 20000 us (50Hz cycle period) * CPU_CLK (MHz) / (n * TIMER1_PRESCALER) = 20000*16/(64*1)
// Optional: for 60Hz use the value 4167
OCR1A = 5000;
OCR1B = 5000; // for 60Hz use the value:
// ADC module configuration
ADMUX = 0x00; // set ADC +ive reference to external AREF
// use right adjusted ADC result presentation
// select analog channel 0
ADCSRA = 0xFF; // enable ADC module
// enable ADC Auto Trigger
// enable ADC interrupt & clear flag bit (ADIF)
// set ADC prescaler to 128 --> ADC CLK = 125kHz
ADCSRB = 0x05; // set ADC trigger source to Timer/Counter1 Compare Match B
// Timer3 configuration
TCCR3A = 0;
TCCR3B = 0x02; // set timer prescaler to CPU_CLK/8 (Timer clock = 16/8 = 2 MHz)
TIFR3 = 0x01; // reset timer overflow interrupt flag
TIMSK3 = 0x01; // enable timer overflow interrupt
pinMode(2, INPUT_PULLUP);
attachInterrupt( digitalPinToInterrupt(2), get_cycle_period, FALLING );
}
// external interrupt ISR (attached on Arduino pin 2)
void get_cycle_period() {
period += TCNT3;
TCNT3 = 0; // reset Timer3
ZC_Event = HIGH;
}
// TIMER3 overflow ISR
ISR (TIMER3_OVF_vect) {
period += 40000; // set cycle period to 20ms
ZC_Event = HIGH;
}
// ADC ISR
ISR (ADC_vect) {
uint16_t an = ADCL | (uint16_t)ADCH << 8;
Samples[m1 + m] = an;
SampleSum += an;
m++;
TIFR1 = (1 << OCF1B); // clear OCB1B flag (Timer1 Output Compare B Match Flag)
}
// main loop function
void loop() {
static uint8_t cycle = 0;
static uint32_t Voltage_RMS = 0;
while (m < n) ; // wait for 1 cycle to complete
TCCR1B &= ~(1 << CS10); // stop Timer1
m = 0;
// toggle between reading & writing arrays
if (m1 > 0) {
m1 = 0;
m2 = 64;
}
else {
m1 = 64;
m2 = 0;
}
uint16_t Average = SampleSum / n; // average calculation. n is number of samples per cycle
SampleSum = 0;
while ( ZC_Event == LOW ) ; // wait for zero crossing event
TCNT1 = OCR1A - 1;
TCCR1B |= (1 << CS10); // start Timer1 with No Prescaling (CLKin = CPU_CLK = 16MHz)
ZC_Event = 0;
// calculate last cycle RMS voltage
uint32_t ch_voltage = 0;
for ( uint8_t i = 0; i < n; i++ ) {
Samples[m2 + i] -= Average ;
Samples[m2 + i] = Samples[m2 + i] * Voltage_Correction ;
uint16_t j = abs( Samples[m2 + i] ) ;
ch_voltage += (uint32_t)j * j ;
}
ch_voltage = ch_voltage / n;
ch_voltage = sqrt(ch_voltage) * 100; // 100 to get a more precise value
// (2 significant digits after the decimal point)
// calculate real RMS voltage applied to MCU analog channel (multiplied by 100)
// 2 = 2048/1024 where 2048 is ADC +ive VREF = 2048 mV and 1024 is 10-bit ADC max digital value (aprroximately)
// Note that this voltage is measured in milliVolts multiplied by 100 always, this is also the voltage
// under measure (x100)
ch_voltage *= 2;
uint16_t RMS = ch_voltage;
if (RMS > 5000) // if cycle RMS Voltage > 50.00V
Voltage_RMS += RMS;
cycle++;
if (cycle >= 25) {
uint32_t acc_pr, freq;
acc_pr = period;
period = 0;
// calculate average cycle period in us.
// 2 = CPU_CLK(MHz)/TIMER3_PRESCALER = 16/8
uint16_t cycle_pr = acc_pr / (cycle * 2);
// update OCR registers according to new average cycle period. 16=CPU_CLK(MHz)/TIMER1_PRESCALER = 16/1
// n is number of samples per one cycle
OCR1A = ( (uint32_t)cycle_pr * 16 ) / n;
OCR1B = OCR1A;
// calculate line frequency.
// 1000000 to go from us to s, and 100 to get 2 significant digits after the decimal point
freq = (1000000 * 100) / cycle_pr;
// print voltage values on the LCD and Serial Monitor
char display_buffer[12];
Voltage_RMS /= cycle;
sprintf( display_buffer, "V = %03u.%01u V", (uint16_t)(Voltage_RMS/100), (uint8_t)( (Voltage_RMS % 100) / 10) );
lcd.setCursor(0, 0); // Move cursor to column 0, row 0 [position (0, 0)]
lcd.print(display_buffer);
Serial.println(display_buffer);
// print line frequency values on the LCD and Serial Monitor
sprintf( display_buffer, "F = %02u.%02u Hz", (uint8_t)(freq/100), (uint8_t)(freq % 100) );
lcd.setCursor(0, 1); // Move cursor to column 0, row 1 [position (0, 1)]
lcd.print(display_buffer);
Serial.println(display_buffer);
Serial.println();
Voltage_RMS = 0;
cycle = 0;
}
}
// end of code.