This sine table calculator lets you choose how many SPWM steps (pillars) you want, and then gives you the correct sineTable[] values (0 to 255 range) to paste directly into Arduino code.

The above image shows one half cycle example of an SPWM waveform, which is a PWM equivalent of a pure sine waveform.
In the above image we see 7 pillars or varying PWM pulses being generated per half wave SPWM cycle. 7 pulses may look good if an iron core transformer is used but the resultant sine waveform might not be pure enough.
Ideally, you must use at least 20 to 30 PWM pulses or pillars on each of these half cycle waveforms, meaning, the higher the number of these pulses, the purer the sine wave will be, and closer it will be to an actual sine waveform.
Features of the Calculator:
- Simple dropdown to select number of SPWM steps
- Calculates values using formula:
PWM = 127.5 + 127.5 × sin(angle) - Displays the array in Arduino-friendly format
What It Does:
When a user selects a value like 11, 21, or 31, it will instantly give you the corresponding duty cycle outputs:
int sineTable[21] = {128, 159, 185, 208, …, 128};You can copy-paste it directly into your Arduino sketch.
Example:
Consider the following example SPWM code for an H-Bridge inverter circuit:
// SPWM code for Arduino Nano Inverter (50Hz output)
// Uses Hardware Timer 1 on Pins 9 and 10 at ~31.3 kHz switching frequency
const int steps = 20;
// A true half-sine wave lookup table (0 to 255 to 0)
const byte sineTable[20] = {
0, 40, 79, 117, 153, 185, 213, 235, 249, 255,
255, 249, 235, 213, 185, 153, 117, 79, 40, 0
};
volatile int stepIndex = 0;
volatile boolean currentHalfCycle = 0; // 0 = Positive Half, 1 = Negative Half
void setup() {
// Set Pin 9 and 10 as outputs
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
// Clear Timer 1 Control Registers
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// Configure Timer 1 for Phase Correct PWM, 8-bit mode (Mode 1)
// This gives a PWM frequency of 16MHz / (2 * 255) = ~31.37 kHz
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
TCCR1B = _BV(CS10); // No prescaling
// Configure Timer 2 to handle the 50Hz timing grid
// 50Hz full wave = 20ms. Each half wave = 10ms.
// 10ms / 20 steps = 500 microseconds per step interval.
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
OCR2A = 124; // Clear Timer on Match (CTC) target
TCCR2A = _BV(WGM21); // CTC Mode
TCCR2B = _BV(CS21) | _BV(CS20); // Prescaler 64 -> (16MHz / 64) = 250kHz clock (4us per tick)
// 125 ticks * 4us = 500us interval
TIMSK2 = _BV(OCIE2A); // Enable Timer 2 Compare Match A Interrupt
}
// Timer 2 Interrupt service routine updates the SPWM duty cycle every 500us
ISR(TIMER2_COMPA_vect) {
byte pwmVal = sineTable[stepIndex];
if (currentHalfCycle == 0) {
// Positive half cycle: Drive Pin 9 with SPWM, keep Pin 10 at 0
analogWrite(10, 0);
analogWrite(9, pwmVal);
} else {
// Negative half cycle: Drive Pin 10 with SPWM, keep Pin 9 at 0
analogWrite(9, 0);
analogWrite(10, pwmVal);
}
stepIndex++;
// If we reach the end of the 20-step half-cycle, switch sides
if (stepIndex >= steps) {
stepIndex = 0;
currentHalfCycle = !currentHalfCycle; // Toggle between positive and negative halves
}
}
void loop() {
// The entire SPWM generation runs perfectly inside the hardware interrupts.
// Your loop stays free to read voltage/current protection overrides!
}1. Variables and Lookup Table (The Blueprint)
C++
const int steps = 20;
const byte sineTable[20] = {
0, 40, 79, 117, 153, 185, 213, 235, 249, 255,
255, 249, 235, 213, 185, 153, 117, 79, 40, 0
};
- What is happening here? See we want to make a smooth AC sine wave. But Arduino is digital, it only understands 0 and 1. So we take one half-cycle of a sine wave and chop it into 20 small pieces (steps).
- The
sineTableis our map, It starts from 0 (zero voltage), goes up to 255 (maximum 5V full peak), and then nicely comes back down to 0.
C++
volatile int stepIndex = 0;
volatile boolean currentHalfCycle = 0;
stepIndexis like a counter. It keeps track of which step out of the 20 we are currently running.currentHalfCycletells us the side. If it is 0, then we are making the positive top half of the AC wave. If it is 1, we are making the negative bottom half.- Desi Tip: Why we use
volatilekeyword? Because these variables are changing inside an Interrupt (ISR). If you do not writevolatile, Arduino's brain gets confused and might skip updating the value.
2. The setup() Part (Setting Up the Dual Engines)
C++
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
- Very simple.... Pin 9 and Pin 10 are made outputs. These two physical pins will give the SPWM signals to your IR2110 IC.
Master Engine 1: Timer 1 (The Super Fast Switching Clock)
C++
TCCR1A = 0; TCCR1B = 0; TCNT1 = 0;
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
TCCR1B = _BV(CS10);
- See carefully: By default Arduino pins toggle at a very slow $490Hz. If you use that then your transformer will make a horrible 'reeeeeee' sound, heat up, and blast the MOSFETs.
- So, we bypass the standard settings. We touch the internal registers (
TCCR1A,TCCR1B) of Timer 1 directly. _BV(WGM10)forces the timer into Phase-Correct 8-bit PWM Mode._BV(CS10)sets the prescaler to 1, meaning the timer runs at full raw hardware speed ($16\text{ MHz}$).- Now instead of 490Hz Pin 9 and Pin 10 will switch at a super fast, totally silent 31.37kHz! This is our switching carrier frequency. It also handles the "dead-time" naturally so both sides never turn ON together.
Master Engine 2: Timer 2 (The 50Hz Timekeeper)
C++
TCCR2A = 0; TCCR2B = 0; TCNT2 = 0;
OCR2A = 124;
TCCR2A = _BV(WGM21);
TCCR2B = _BV(CS21) | _BV(CS20);
TIMSK2 = _BV(OCIE2A);
- Okay, our pins are screaming at $31\text{ kHz}$, but how do we manage the 50Hz frequency for the main AC output?
- A full 50Hz wave takes exactly 20 milliseconds. So, one half-cycle takes 10 milliseconds,10,000 microseconds.
- Since we have 20 steps in our table, we must change the duty cycle every $10,000 / 20 = 500microseconds.
- This Timer 2 setup is configured in CTC Mode (Clear Timer on Compare Match). With a prescaler of 64 and a count target of 124 (
OCR2A = 124), it acts like an alarm clock that rings exactly every 500 microseconds! TIMSK2 = _BV(OCIE2A);tells the chip: "Boss, whenever this 500us alarm rings, stop everything else and jump straight to the ISR function."
3. The ISR (The Brain of the Inverter)
C++
ISR(TIMER2_COMPA_vect) {
byte pwmVal = sineTable[stepIndex];
- Every 500 microseconds, this block of code runs automatically in the background.
- First, it looks at the
stepIndexcounter and grabs the matching value from oursineTablemap (for example: 0, then 40, then 79...).
C++
if (currentHalfCycle == 0) {
analogWrite(10, 0);
analogWrite(9, pwmVal);
} else {
analogWrite(9, 0);
analogWrite(10, pwmVal);
}
- The Switching Logic: * If
currentHalfCycleis 0 (Positive half), we completely shut off Pin 10 (analogWrite(10, 0)) so the right side of the bridge stays dead low. Then, we feed the shifting PWM value to Pin 9.- If
currentHalfCycleis 1 (Negative half), we completely shut off Pin 9 and feed the shifting PWM value to Pin 10.
- If
- This is how the current alternates direction inside your transformer primary!
C++
stepIndex++;
if (stepIndex >= steps) {
stepIndex = 0;
currentHalfCycle = !currentHalfCycle;
}
}
- After applying the value, the counter increases (
stepIndex++). - Once it finishes all 20 steps (meaning 10ms have passed), then it resets the counter back to 0 and flips the side (
currentHalfCycle = !currentHalfCycle). Now it will start driving the opposite pin for the next half-cycle.
4. The loop() Part (The Empty Room)
C++
void loop() {
// Main loop does nothing!
}
- Look now, the entire SPWM creation, phase shifting, timing, and wave shaping is happening automatically inside the hardware silicon chips using interrupts. Your main loop is 100% empty and sitting idle.
- Why this is superb: Because the loop is free, so you can add your code here to read the analog pins for over-voltage feedback, check current limits or display battery status on an LCD screen without affecting or distorting your AC output wave at all!
Need Help? Please Leave a Comment! We value your input—Kindly keep it relevant to the above topic!