Thursday, June 25, 2020

How a POV propeller display works

In part 1 on How to make a POV display we talked about the hardware and schematic. In this part 2 we will dive in how the code works and why the POV (Persistence Of Vision) display is so cool and fun to play with.

How a POV display works

First we need to measure the time it takes for the PC fan to make a full rotation. For this we will use a Hall effect sensor and a magnet.
Say a PC fan has 1360 RPM divided by 60 = 22.66 rotations per second.
Now 1 / 22.66 = 0.044 seconds or 44 milliseconds for one rotation.

Interrupt on each full rotation triggered by the Hall effect sensor

When the hall sensor passes over a magnet the microcontroller will trigger an interrupt INT0_ISR.
Here Timer 0 is used for measuring one full rotation time.

In this interrupt INT0_ISR the following happens:
  • clear the flag
  • stop Timer 0 and Timer 2
  • store the value of Timer 0 (16 bit) in a local 16 bit variable
one_rotation_time = Timer 0
  • reset Timer 0
On each rotation thus ISR trigger, Timer 0 starts from 0 so this is the time in cycles for one full rotation. It is not necessary to know the time in seconds although this can be calculated.
  • divide one_rotation_time by the number of columns to find out after how much time the next column can be displayed
time_per_column = one_rotation_time / number_of_columns
time_per_column = 44ms / 240 = 183 microseconds
time_per_column = 44ms / 120 = 366 microseconds
time_per_column = 44ms / 60 = 733 microseconds

Notice how increasing the number of columns decreases the time for one column. More columns gives a better resolution but the microcontroller must be fast enough to finish the code in that time.
  • set Timer 2 to trigger an ISR after the calculated time time_per_column and start Timer 2
  • start Timer 0
  • reset active column count to 0
Then the main routine continues until time_per_column passes and Timer 2 triggers an interrupt TIMER2_ISR.

Interrupt for every column

Here the leds corresponding to current column are lit. If all leds would be on a single port this would be achieved simply like this:

PORT0 = povDisplayData[active_column];

But when the leds are located on multiple pin ports the code becomes a bit tricky. I have used some bitwise operations and bitmasks for this. Switch case takes 10 times longer and if/else conditions even longer.
After that the active column is incremented.

povDisplayData is an 16 bit array with the size equal to the number of columns and it holds the data to be displayed on every frame/rotation. This array can be changed in the main loop to display text and animations.

Connecting the app to POV display device

On first use the device will create an wireless access point with SSID: POV Display and KEY: ESP826608. With a wireless device connect to this access point then using the Android app access Router Credentials page under the menu. Here you need to pass the credentials of your router for the ESP8266 to connect to the router. This step is only necessary once and after this the credentials are saved inside ESP8266 flash.

* a flashing blue light indicates the device is waiting for router credentials


This zip file contains main code and dependency libraries:
  • POV Display - EFM8BB31F32_main.c
  • uart.h
  • ESP8266.h
  • Flash.h
  • globals.h
  • delay.h
If you want to know how to program an EFM8 microcontroller visit this link

However you don't need Simplicity Studio for that. For uploading the code I have also included:
  • POV Display - EFM8BB31F32.efm8
This is the compiled file that needs to be uploaded on the microcontroller using an executable provided by the Silicon Labs
  • efm8load.exe
And a bat file for uploading on COM3. This can be edited.
  • 2 - Bootload Record Downloader.bat
The bat file contains:
@echo Downloading bootload record using COM3
@efm8load.exe -p COM3 -t "POV Display - EFM8BB31F32.efm8"


  1. Hello, sorry what do you mean when you said "set Timer 2 to trigger an ISR", what is an ISR?

    1. Hi. An ISR means Interrupt Service Routine and is like a function that interrupts the main code when an event occurs. In this case is a timer event. It can be an overflow or when the timer counts to a certain number then an interrupt is generated and the code inside the interrupt is executed. This is the ISR for timer 2:

      // code goes here