50/60Hz PWM Inversion Library Feature
📋 Overview
This section aims to document the functionality of the Atinverter library’s PWM generation feature, used to synthesize a sinusoidal waveform using a timer-driven software control scheme. The PWM waveform is composed of high-frequency switching pulses generated by Timer1 interrupts, which step through a precomputed sine lookup table at a rate of 31,372 kHz. Simultaneously, Timer0 is configured to Phase Correct PWM mode and alternates the output between two hardware PWM pins to approximate a 50Hz or 60Hz AC signal. The software manages duty cycle updates and pin toggling using ISR logic, ultimately enabling fully software-defined sinusoidal PWM output from the ATMEGA328P.
📌 Pin Assignments
| Pin Description | Pin Name (Code) | Pin Number | Pin State |
|---|---|---|---|
| PWM A Side | PWM_A_PIN | 5 | OUTPUT |
| PWM B Side | PWM_B_PIN | 6 | OUTPUT |
| Gate Shutdown | GATESD_PIN | 8 | INPUT |
| Protection Reset | PRORESET_PIN | 9 | OUTPUT |
📂 Header File Definitions
Implementation in Atinverter.h:
// AC RMS Sampling Period
#define MS_PER_SECOND 1000
// PWM Inversion Parameters
#define SIN_50HZ_PWM_SIZE 313
#define SIN_60HZ_PWM_SIZE 260
// PWM Pin Definitions
const int PWM_A_PIN = 5;
const int PWM_B_PIN = 6;
const int GATESD_PIN = 8;
const int PRORESET_PIN = 9;
// Parameters for PWM
static bool is50Hz;
static int sin_i;
static int pwm_i;
static int OK;
static const int sin50HzPWM[312];
static const int sin60HzPWM[261];
// Methods
void startPWM(uint16_t frequency);
static void pwmISR();
void enablePWM();
void disablePWM();
🧮 50Hz and 60Hz Sinusoid Lookup Table
// Define the sinusoidal 50Hz sample array (312 points)
const int Atinverter::sin50HzPWM[] = {
1,2,5,7,10,12,15,17,19,22,24,27,30,32,34,37,39,42,44,47,49,52,54,57,59,61,64,66,
69,71,73,76,78,80,83,85,88,90,92,94,97,99,101,103,106,108,110,113,115,117,119,121,
124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,
166,168,169,171,173,175,177,178,180,182,184,185,187,188,190,192,193,195,196,198,199,
201,202,204,205,207,208,209,211,212,213,215,216,217,219,220,221,222,223,224,225,226,
227,228,229,230,231,232,233,234,235,236,237,237,238,239,240,240,241,242,242,243,243,
244,244,245,245,246,246,247,247,247,248,248,248,248,249,249,249,249,249,255,255,255,
255,249,249,249,249,249,248,248,248,248,247,247,247,246,246,245,245,244,244,243,243,
242,242,241,240,240,239,238,237,237,236,235,234,233,232,231,230,229,228,227,226,225,
224,223,222,221,220,219,217,216,215,213,212,211,209,208,207,205,204,202,201,199,198,
196,195,193,192,190,188,187,185,184,182,180,178,177,175,173,171,169,168,166,164,162,
160,158,156,154,152,150,148,146,144,142,140,138,136,134,132,130,128,126,124,121,119,
117,115,113,110,108,106,103,101,99,97,94,92,90,88,85,83,80,78,76,73,71,69,66,64,61,
59,57,54,52,49,47,44,42,39,37,34,32,30,27,24,22,19,17,15,12,10,7,5,2,1};
// Define the sinusoidal 60Hz sample array (261 points)
const int Atinverter::sin60HzPWM[] = {
1,3,6,9,12,15,18,21,24,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,73,76,79,
82,85,88,91,93,96,99,102,104,107,110,112,115,118,120,123,126,128,131,133,136,138,
141,143,146,148,151,153,155,158,160,162,165,167,169,171,173,176,178,180,182,184,
186,188,190,192,194,196,197,199,201,203,205,206,208,210,211,213,214,216,217,219,
220,222,223,224,226,227,228,229,230,232,233,234,235,236,237,238,239,239,240,241,
242,243,243,244,244,245,246,246,247,247,247,248,248,248,249,249,249,249,249,249,
249,249,249,249,249,249,248,248,248,247,247,247,246,246,245,244,244,243,243,242,
241,240,239,239,238,237,236,235,234,233,232,230,229,228,227,226,224,223,222,220,
219,217,216,214,213,211,210,208,206,205,203,201,199,197,196,194,192,190,188,186,
184,182,180,178,176,173,171,169,167,165,162,160,158,155,153,151,148,146,143,141,
138,136,133,131,128,126,123,120,118,115,112,110,107,104,102,99,96,93,91,88,85,82,
79,76,73,71,68,65,62,59,56,53,50,47,44,41,38,35,32,29,26,24,21,18,15,12,9,6,3,1};
📝 Method Descriptions
void startPWM(uint16_t frequency)
Purpose: Begins PWM and initializes all required timers and registers for 50Hz or 60Hz PWM generation.
Pseudocode:
- Disable global interrupts
- If the frequency input is not 50Hz or 60Hz
- Set a default frequency of 50Hz
- Set global frequency mode flag used by pwmISR based on frequency input for sample array selection
- Compute the sampling time window used in getRmsAC() and getZeroPoint()
- Configure PWM pins as outputs
- Set reset logic pin (PRORESET) as output and drive LOW to ensure gate driver is on
- Set gate shutdown pin (GATESD) as input to allow logic protection circuitry to control gate drivers
- Reset Timer0 control register A, B, and the counter
- Set up Timer0 for Phase Correct PWM (8-bit) with no prescaler
- Reset Timer1 control register B and the counter
- Set compare match value depending on frequency mode flag
- Set up Timer1 for CTC mode to trigger the waveform stepping ISR
- Enable Timer1 Compare A interrupt
- Re-enable global interrupts
Implementation in Atinverter.cpp:
void Atinverter::startPWM(uint16_t frequency) {
cli();
if (frequency != 50 && frequency != 60) {
frequency = 50;
}
is50Hz = (frequency == 50);
period = MS_PER_SECOND / frequency;
pinMode(PWM_A_PIN, OUTPUT);
pinMode(PWM_B_PIN, OUTPUT);
pinMode(PRORESET_PIN, OUTPUT);
digitalWrite(PRORESET_PIN, LOW);
pinMode(GATESD_PIN, INPUT);
TCCR0A = 0;
TCCR0B = 0;
TCNT0 = 0;
TCCR0A = 0b10100001;
TCCR0B = 0b00000001;
TCCR1A = is50Hz ? 0b1000010 : 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = is50Hz ? 624 : 509;
TCCR1B = 0b00001001;
TIMSK1 |= (1 << OCIE1A);
sei();
}
static void pwmISR()
Purpose: To generate the alternating sinusoidal PWM signals on the digital pins 5 and 6 by iterating through a lookup table of precomputed PWM values.
Pseudocode:
- Select sinusoid lookup table based on frequency mode flag
is50Hz - Determine the max index for lookup table based on the frequency mode flag
- If the end of the lookup table has been reached and the digital pin flag
OKis set LOW (indicates that digital pin 6 is currently active except for firstpwmISR()iteration):- Reset lookup table index and toggle the digital pin flag to activate pin 5 and deactivate pin 6 in the same cycle
- If the end of the lookup table has been reached and the digital pin flag
OKis set HIGH (indicates that digital pin 5 is currently active except for firstpwmISR()iteration):- Reset lookup table index and toggle to digital pin flag to activate pin 6 and deactivate pin 5 in the same cycle
- Load PWM pulse value from lookup table
- Increment the lookup table index
sin_ito move to the next sample - If OK flag is off:
- Set digital pin 5 (
OCR0B) off - Set digital pin 6 (
OCR0A) to output current PWM pulse
- Set digital pin 5 (
- If OK flag is on:
- Set digital pin 6 (
OCR0A) off - Set digital pin 5 (
OCR0B) to output current PWM pulse
- Set digital pin 6 (
Implementation in Atinverter.cpp:
void Atinverter::pwmISR() {
const int* sinPWM = is50Hz ? sin50HzPWM : sin60HzPWM;
int maxIndex = is50Hz ? SIN_50HZ_PWM_SIZE : SIN_60HZ_PWM_SIZE;
if (sin_i > maxIndex && OK == 0) {
sin_i = 0;
OK = 1;
}
else if (sin_i > maxIndex && OK == 1) {
sin_i = 0;
OK = 0;
}
pwm_i = sinPWM[sin_i];
sin_i++;
if (OK == 0) {
OCR0B = 0;
OCR0A = pwm_i;
}
else {
OCR0A = 0;
OCR0B = pwm_i;
}
}
void enablePWM()
Purpose: Enables the Timer1 interrupts so that PWM generation resumes.
Pseudocode:
- Disable global interrupts
- Enable the Timer1 Compare Match A interrupt (
OCIE1A) - Re-enable global interrupts
Implementation in Atinverter.cpp:
void Atinverter::enablePWM() {
cli();
TIMSK1 |= (1 << OCIE1A);
sei();
}
void disablePWM()
Purpose: Disables the Timer1 interrupts to stop PWM generation without affecting other global interrupts.
Pseudocode:
- Disable global interrupts
- Disable the Timer1 Compare Match A interrupt interrupts (
OCIE1A) - Re-enable global interrupts
Implementation in Atinverter.cpp:
void Atinverter::disablePWM() {
cli();
TIMSK1 &= ~(1 << OCIE1A);
sei();
}
⏹️ ISR Description
ISR(TIMER1_COMPA_vect)
Purpose: This interrupt service routine (ISR) is triggered at each Timer1 compare match and calls the pwmISR() method to update the output PWM waveform.
*Note: A separate static method Atinverter::pwmISR() is used instead of embedding the PWM logic directly inside the ISR(TIMER1_COMPA_vect) since ISRs cannot access instances of a class as they are not tied directly to an object. The PWM parameters is50Hz, sin_i, pwm_i, OK, sin50HzPWM, and sin60HzPWM are based on an instance of the Atinverter class.
Pseudocode:
- Call
Atinverter::pwmISR()to step through the sinusoidal array samples
Implementation in Atinverter.cpp:
ISR(TIMER1_COMPA_vect) {
Atinverter::pwmISR();
}