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


Table X. ATMEGA328P Pin Configuration for PWM Control
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:

  1. Disable global interrupts
  2. If the frequency input is not 50Hz or 60Hz
    • Set a default frequency of 50Hz
  3. Set global frequency mode flag used by pwmISR based on frequency input for sample array selection
  4. Compute the sampling time window used in getRmsAC() and getZeroPoint()
  5. Configure PWM pins as outputs
  6. Set reset logic pin (PRORESET) as output and drive LOW to ensure gate driver is on
  7. Set gate shutdown pin (GATESD) as input to allow logic protection circuitry to control gate drivers
  8. Reset Timer0 control register A, B, and the counter
  9. Set up Timer0 for Phase Correct PWM (8-bit) with no prescaler
  10. Reset Timer1 control register B and the counter
  11. Set compare match value depending on frequency mode flag
  12. Set up Timer1 for CTC mode to trigger the waveform stepping ISR
  13. Enable Timer1 Compare A interrupt
  14. 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:

  1. Select sinusoid lookup table based on frequency mode flag is50Hz
  2. Determine the max index for lookup table based on the frequency mode flag
  3. If the end of the lookup table has been reached and the digital pin flag OK is set LOW (indicates that digital pin 6 is currently active except for first pwmISR() iteration):
    • Reset lookup table index and toggle the digital pin flag to activate pin 5 and deactivate pin 6 in the same cycle
  4. If the end of the lookup table has been reached and the digital pin flag OK is set HIGH (indicates that digital pin 5 is currently active except for first pwmISR() iteration):
    • Reset lookup table index and toggle to digital pin flag to activate pin 6 and deactivate pin 5 in the same cycle
  5. Load PWM pulse value from lookup table
  6. Increment the lookup table index sin_i to move to the next sample
  7. If OK flag is off:
    • Set digital pin 5 (OCR0B) off
    • Set digital pin 6 (OCR0A) to output current PWM pulse
  8. If OK flag is on:
    • Set digital pin 6 (OCR0A) off
    • Set digital pin 5 (OCR0B) to output current PWM pulse

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:

  1. Disable global interrupts
  2. Enable the Timer1 Compare Match A interrupt (OCIE1A)
  3. 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:

  1. Disable global interrupts
  2. Disable the Timer1 Compare Match A interrupt interrupts (OCIE1A)
  3. 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:

  1. Call Atinverter::pwmISR() to step through the sinusoidal array samples

Implementation in Atinverter.cpp:

ISR(TIMER1_COMPA_vect) {
  Atinverter::pwmISR();
}