Sine Pulse LED PWM trick: the 8 bit way

In short

“QuadraticWaveLed” is a library that can pulsate LED using the PWM capabilities of attiny/atmega chips used by Arduinos. Download it from GitHub and install it in all your projects!

Effect obtained:

This tutorial is a follow up of the previous one. This is a pure 8 bit solution to what is inherently a floating point problem, and the code ends up being over 3 times faster. Extreme hacking is involved!

You can jump straight to the Arduino code if you don’t want to read about the theory behind the code. If you want to, carry on below!


Getting the most performance out of a 8 bit chip

In the previous version of this tutorial (click here), I described how to have a LED pulsate like a sine wave. It is a beautiful, mathematically accurate solution; and it would be implemented like this on a modern CPU with a floating point calculation unit.

The problem is: it’s really expensive on a 8 bit chip, and this for two reasons:

  1. The sine function. sin(x) is implemented in avr-libc in assembly and a look up table. There is no doubt it’s a brilliant piece of code, but a CPU is better designed to do polynomial multiplications.
  2. The elephant in the room: the usage of 32 bit floating points. These are incredibly slow. On a 8 bit chip doing math on 32 bit integers is already quite slow. So software emulated 32 bit floating point math? Ouch.

In this tutorial, we will address both of these points.


Getting rid of the sine function call

The idea here, is to replace the sin(f) call by a polynomial operation (that is to say: add and multiply). Some very smart people had already found out in the 18th century that you could replace any function by a sum of terms; and you might have heard of it, it’s the Taylor series.

Specifically, a sine function can be expressed this way:

While this works, it’s still too costly. In order to get a good precision on this Taylor series, we have to do quite a lot of math. x to the power of 7 can get rapidly expensive. We need something faster.

An alternative approach is to use quadratic functions (that is to say, terms that do not exceed the power of 2). is still fast to compute.

f(x) = x^2
f(x) = x²

… Well that doesn’t look like a sine wave. Or does it? Imagine we invert the parabol after 1, then invert it again. Wouldn’t that look like a sine wave? Let’s build it:

"Sine wave" made of three quadratic functions
“Sine wave” made of three quadratic functions

We will call the function above the “quadratic wave” from now on. This function is so close to a sine wave the difference between the real thing and this poor man’s version never exceeds 0.06.

The two curves are so close you can barely see the difference!
The two curves are so close you can barely see the difference!

Side note: a function made of several function is called a “piecewise-defined function“. Our quadratic wave can be written as such:



Getting rid of floating points: the fixed point approach

Fixed point is a very simple approach to floating point problems. We just arbitrarily decide that part of a number represent the decimal values.

For example, on a 16 bit number, you could decide that the first 8 bit represent the integer part, and the 8 other represent the decimal part. For instance, a number like 0x4ab2 can be read as:

  • 0x4a: 74
  • 0xb2: 178, 1/256 * 178 = 0.6953125

Therefore: 0x4ab2 = 74.6953125

Another way to see these numbers is that they behave like integers, but the minimal increment is the least significant bit on the decimal side of the number. With 8 bit decimal precision, the minimal increment to the number is 1/256=0.00390625. Writing i++ would increment this number by 0.00390625.
With such number, on a 16 bit scale, you can therefore write numbers from 0 to (1/256)*65535 = 255.99609375. In hexadecimal, this number would be 0xffff.

This tutorial is not dedicated to fixed point math, so feel free to dig deeper if you wish to. The main take away here is that using fixed point math, you can do addition and multiplications using traditional integer arithmetic. For all intent and purposes, your fixed point numbers are seen as 16 bit or 32 bit integers by the CPU.

In the code below, the chosen fixed point format is:

  • 1 sign bit
  • 6 bit integer (range from 0 to 64)
  • 9 bit decimal (precision of 1/512 = 0.001953125)



The Arduino code

The Arduino code below is a direct translation of the quadratic wave function explained above, applied with fixed point math. It is neatly packaged in a class called “QuadraticWaveLed” so that you can easily transport it to your projects.

Compared to the previous project, this code ran slightly over 3.5 times faster than the floating point version.

How to use this library?

  1. Download QuadraticWaveLed.cpp and QuadraticWaveLed.h from GitHub
  2. Paste these file your Arduino project main folder. Alternatively, you can download the sample Arduino sketch using the library at GitHub
  3. Add the library through #include “QuadraticWaveLed.h”
  4. Create an object QuadraticWaveLed, and call the “setup” function. First argument is the pin number to be used, 2nd in the lenght of the wave, in milliseconds.
  5. In your program loop, feed the “update” method with current time elapsed.

Sample program, with a 2s wave and using pin 11 of the Arduino Uno

#include "QuadraticWaveLed.h"

QuadraticWaveLed quadraticWaveLed;
const int LED_PIN = 11;     //11 is a PWM pin on the Arduino Uno. Change to whichever pin you wish to use.
const unsigned long WAVE_LEN = 2000UL;  //in milliseconds. This wave will last 2 seconds.

unsigned long milli = 0UL;

void setup() {
  quadraticWaveLed.setup(LED_PIN, WAVE_LEN);

void loop() {
  milli = millis();



“update” contains the meat of the code. This code:

  • Convert the argument given to the [0 .. wave duration] using the modulus operation
  • Normalize this value to the [0 .. 4] range –which is the operating range of our quadratic function.
  • Apply our quadratic wave function
  • Convert the value obtained to the [0 .. 255] range for PWM output
  • analogWrite this value to the led pin.
void QuadraticWaveLed::update(const unsigned long milli){
	unsigned long mod = milli % this->waveLen;

	//transform to 0--1 range
	this->value = muluf16p16(inttouf16p16(mod), this->inverseWaveLen);

	//convert to 0--4 range: multiply by 4 by shifting left twice
	this->value = this->value << 2; 

	//apply wave
	this->value = quadraticWavef7p9(this->value);

	//convert to 0-255 range
	uint8_t pwm_value = ((int32_t)this->value * floattof23p9(127.5f)) >> 18;

	//write to LED
	analogWrite(this->pinNo, pwm_value);

Final words

There are still some additional speed hacks that could be performed, I can think about using an accumulator and sending to the program the time elapsed instead of the full millis() call, or there could be even bigger hacks.

For instance the multiplication at the end of the code by 127.5 could be changed by a 7 bit left shift: that would multiply it by 128 would be close enough. All of these are possible but they would make the code a lot less usable and readable.

Right now, I feel like this is a good balance between speed and ease of use.

And to conclude, this code is licensed under CC-BY 4.0 so feel free to use it in any of your projects, personal or not!