This a POV (Persistence of vision) Display based on an Arduino Pro Mini clone.
The POV is fully interrupt driven making use of the Timer 1 input capture interrupt and the Timer 1 Output compare match interrupt for stable operation.
Parts
When you click on links to various merchants on this site and make a purchase, this can result in this site earning a commission. Affiliate programs and affiliations include, but are not limited to, the eBay Partner Network.
Ebay affiliate links:
(16Mhz pro mini for the best performance)
FT232RL usb serial
150mah lipo battery
Neodymium magnets 1/12″x1/8″ 2mmx3mm
0.01uf or 10nf ceramic capacitor
10k resistor 1/8w
Ribbon cable – 9 way
Fidget spinner
M8x20mm bolts
M8x25mm bolt
M8 nuts
M8 washers
M8 dome nut
Test Leds sketch
// Pov display - test leds
// Turn on each led in turn
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
void setup(){
DDRD = 0b11111111; // Set all pins in PORTD to outputs
PORTD = 0; // Turn off the leds
}
void loop() {
PORTD = 0; // Turn off the leds
delay(100);
byte dispval = 1;
for (int i=0; i <= 7; i++){ // Turn on each led in turn
PORTD=dispval;
delay(100);
dispval=dispval<<1;
}
}
Test hall effect sensor sketch
// Pov display - test hall effect sensor
// Leds reflect the output of the hall effect sensor
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
const int hallEffectPin = 8;
int hallEffectState = 0;
void setup(){
DDRD = 0b11111111; // All pins in PORTD are outputs
PORTD = 0; // Turn off the leds
// initialize the hall effect pin as an input:
pinMode(hallEffectPin, INPUT);
}
void loop() {
hallEffectState = digitalRead(hallEffectPin);
if (hallEffectState == HIGH) {
// turn LED on:
PORTD=255;
} else {
// turn LED off:
PORTD=0;
}
}
Sketch 1 – Basic display driver
// POV project
// Basic POV Sketch - Prints static text
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
const byte inputCapturePin = 8; // Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0; // The count width of a column
volatile byte timer1CountOverflows = 0; // Timer 1 counter overflows
volatile unsigned int povDisplayColumn = 0; // The next pov display column
// Data to be displayed on the pov display
// initialised with static text
// Look at the pattern of 1s sideways
//
volatile byte povDisplayData[64] ={
B00000001, // T
B00011111,
B00000001,
B00000000,
B00011111, // E
B00010101,
B00010101,
B00000000,
B00011111, // C
B00010001,
B00010001,
B00000000,
B00011111, // H
B00000100,
B00011111,
B00000000,
B00010111, // y
B00010100,
B00011111,
B00000000,
B00011111, // D
B00010001,
B00001110,
B00000000,
B00011111, // I
B00000000,
B00010111, // Y
B00010100,
B00011111
};
void setup()
{
noInterrupts(); // Turn off interrupts
DDRD = 0b11111111; // Set all PORTD pins to outputs
PORTD = 0; // Turn the leds off
pinMode(inputCapturePin, INPUT_PULLUP); // Hall effect sensor connected to this pin
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
TCCR1A = 0; // Normal wave generation mode
TCCR1B = 0;
TCNT1 = 0; // Reset timer 1 counter
OCR1A = 0xFFFE; // Initialise timer 1 output compare match
// Timer counter contol register
// ICNC1 - Input capture noise canceller
// ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
//
// TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10); // /1024 prescale
// TCCR1B = bit(ICNC1) | bit(CS12) ; // /256 prescale
// TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10); // /64 prescale
//TCCR1B = bit(ICNC1) | bit(CS11); // /8 prescale
TCCR1B = bit(ICNC1) | bit(CS10); // /1 prescale
// Timer interrupt mask register
// Input capture interrupt enable
// Output compare A match interrupt enable
// Timer overflow interrupt enable
TIMSK1 = bit(ICIE1) | bit(OCIE1A) | bit(TOIE1);
interrupts(); // enable interrupts
}
void loop() { // Empty main program loop
}
// Timer 1 - Input capture interrupt routine - digital input 8
ISR(TIMER1_CAPT_vect) {
povDisplayColumnWidthCount = ( ICR1 + timer1CountOverflows * 65535 ) / 64; // Calculate the time count for a column
TCNT1 = 0; // Reset timer 1 counter
povDisplayColumn=0; // Reset display to the first column
OCR1A = povDisplayColumnWidthCount-1; // Set timer 1 OCR1A interrupt count duration
timer1CountOverflows = 0; // Reset timer1 overflows
}
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
timer1CountOverflows++; // Increment the timer1 overflows counter
}
ISR(TIMER1_COMPA_vect){ // Timer 1 Output compare A match interrupt routine
OCR1A = TCNT1 + povDisplayColumnWidthCount-1; // Update timer1 with the next time interval
if (povDisplayColumn < 64){
PORTD = povDisplayData[povDisplayColumn]; // Output data to the leds
} else {
PORTD = 0; // Turn off the leds
}
povDisplayColumn++; // increment the display column
}
Sketch 2 – Print any text to the POV display with a frame buffer
Please copy the font.h file here: font.h (2452 downloads ) into the same directory as the sketch.
The sketch also makes use of a frame buffer, which allows you to modify the display data and then display it with povUpdateDisplay(); when you are ready.
// POV project
// Prints any text on the display
// This also has a frame buffer, so that the screen can be updated
// and then the results displayed when you are ready.
//
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
#include "font.h"; // defines array with font data
const byte ledpin = 13; // On board led output pin
const byte inputCapturePin = 8; // Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0; // The count width of a column
volatile byte timer1CountOverflows = 0; // Timer 1 ounter overflows
volatile unsigned int povDisplayColumn = 0; // The current pov display column
volatile byte povFrameBuffer[64]; // POV display working frame buffer
volatile boolean showpov = false; // Set to update the POV frame buffer
volatile boolean updateFrameBuffer = false; // Timer 1 uses this to tell itself to update the frame buffer
// Data to be displayed on the pov display
volatile byte povDisplayData[64];
byte printseg=0; // Next column to print on
void setup()
{
noInterrupts();
DDRD = 0b11111111; // Set all PORTD pins to outputs
PORTD = 255; // Turn the leds off
pinMode(ledpin, OUTPUT); // Onboard LED output
pinMode(inputCapturePin, INPUT_PULLUP); // Hall effect sensor connected to this pin
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
TCCR1A = 0; // Normal wave generation mode
TCCR1B = 0;
TCNT1 = 0; // Reset timer 1 counter
OCR1A = 0xFFFE; // Initialise timer 1 output compare match
// Timer counter contol register
// ICNC1 - Input capture noise canceller
// ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
//
// TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10); // /1024 prescale
// TCCR1B = bit(ICNC1) | bit(CS12) ; // /256 prescale
// TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10); // /64 prescale
//TCCR1B = bit(ICNC1) | bit(CS11); // /8 prescale
TCCR1B = bit(ICNC1) | bit(CS10); // /1 prescale
// Timer interrupt mask register
// Input capture interrupt enable
// Output compare A match interrupt enable
// Timer overflow interrupt enable
TIMSK1 = bit(ICIE1) | bit(OCIE1A) | bit(TOIE1);
interrupts(); // enable interrupts
}
void loop() { // Empty main program loop
printseg=0; // Reset print column to the start
povPrint("Techydiy"); // print text
povUpdateDisplay(); // Show the display data on screen
delay(500);
povClear(); // Clear the display data
printseg=0; // Reset print column to the start
povPrint("Projects"); // print text
povUpdateDisplay(); // Show the display data on screen
delay(500);
povClear(); // Clear the display data
}
// ---------------------------------
//
//
// Signal to update the frame buffer and then wait for it to be completed
void povUpdateDisplay(){;
showpov = true; // Show the display data on screen
do{ } while (showpov); // Wait for the display to be updated
}
void displayCharOnPov(char letter) {
unsigned int y = 0;
for (int i=0; i < 6; i++){
y=Font8x5[letter][i];
povDisplayData[printseg]= y;
printseg++; // update the next print segment pointer
}
}
void povPrint(String povtxt){
for (int z=0; z< povtxt.length(); z++){
displayCharOnPov( povtxt.charAt(z));
}
}
// Clear pov Display
void povClear(){
for (int zb=0; zb < 64; zb++){
povDisplayData[zb]=0;
}
}
//------------------------------------------------------------
//
// Timer 1 - Input capture interrupt routine - digital input 8
//
ISR(TIMER1_CAPT_vect) {
povDisplayColumnWidthCount = ( ICR1 + timer1CountOverflows * 65535 ) / 64; // Calculate the time count for a column
TCNT1 = 0; // Reset timer 1 counter
povDisplayColumn=0; // Reset display to the first column
OCR1A = povDisplayColumnWidthCount-1; // Set timer 1 OCR1A interrupt count duration
timer1CountOverflows = 0; // Reset timer1 overflows
if (showpov){
updateFrameBuffer = true; // Tell timer 1 OCR1A interrupt to update the frame buffer
}
}
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
timer1CountOverflows++; // Increment the timer1 overflows counter
digitalWrite(ledpin, !digitalRead(ledpin)); // Toggle onboard LED
}
ISR(TIMER1_COMPA_vect){ // Timer 1 Output compare A match interrupt routine
OCR1A = TCNT1 + povDisplayColumnWidthCount-1; // Update timer1 with the next time interval
if (updateFrameBuffer){ // Update the framebuffer only once
povFrameBuffer[povDisplayColumn]=povDisplayData[povDisplayColumn]; // Copy display data into the framebuffer
if (povDisplayColumn >= 63){
updateFrameBuffer = false; // framebuffer has been updated
showpov = false;
}
}
if (povDisplayColumn < 64){
PORTD = povFrameBuffer[povDisplayColumn]; // Output data to the leds from the frame buffer
} else {
//PORTD = 0; // If the spinning stops then turn off the leds
}
povDisplayColumn++; // increment the display column
}
Sketch 3 – Display the RPM (revolutions per minute) on the POV display
Please copy the font.h file here: font.h (2452 downloads ) into the same directory as the sketch.
// POV project
// Prints RPM (revolutions per minute) on the display
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
#include "font.h"; // defines array with font data.
const byte inputCapturePin = 8; // Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0; // The count width of a column
volatile unsigned long int revCount; // Count length of the last revolution
volatile byte timer1CountOverflows = 0; // Timer 1 ounter overflows
volatile unsigned int povDisplayColumn = 0; // The current pov display column
volatile byte povFrameBuffer[64]; // POV display working frame buffer
volatile boolean showpov = false; // Set to update the POV frame buffer
volatile boolean updateFrameBuffer = false; // Timer 1 uses this to tell itself to update the frame buffer
// Data to be displayed on the pov display
volatile byte povDisplayData[64];
byte printseg=0; // Next column to print on
void setup()
{
noInterrupts();
DDRD = 0b11111111; // Set all PORTD pins to outputs
PORTD = 255; // Turn the leds off
pinMode(inputCapturePin, INPUT_PULLUP); // Hall effect sensor connected to this pin
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
TCCR1A = 0; // Normal wave generation mode
TCCR1B = 0;
TCNT1 = 0; // Reset timer 1 counter
OCR1A = 0xFFFE; // Initialise timer 1 output compare match
// Timer counter contol register
// ICNC1 - Input capture noise canceller
// ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
//
// TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10); // /1024 prescale
// TCCR1B = bit(ICNC1) | bit(CS12) ; // /256 prescale
// TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10); // /64 prescale
//TCCR1B = bit(ICNC1) | bit(CS11); // /8 prescale
TCCR1B = bit(ICNC1) | bit(CS10); // /1 prescale
// Timer interrupt mask register
// Input capture interrupt enable
// Output compare A match interrupt enable
// Timer overflow interrupt enable
TIMSK1 = bit(ICIE1) | bit(OCIE1A) | bit(TOIE1);
interrupts(); // enable interrupts
}
void loop() { // Empty main program loop
int rpm = 0;
int oldrpm = 0;
printseg=0; // Reset print column to the start
povPrint("Techydiy"); // print text
povUpdateDisplay(); // Show the display data on screen
delay(1000);
povClear(); // Clear the display data
while(true){
// Calculate the rpm of the last revolution
rpm = 480000000 / revCount; // rpm = 60 / ( timer count x timer period )
// timer count = revcnt
// timer period = 125 x 10-9
// 8Mhz clock - no prescaler
if (rpm != oldrpm){ // If the RPM value has changed print it
printseg=0; // Reset print column to the start
povPrint(String(rpm)+" RPM "); // Print rpm value to the pov display
povUpdateDisplay(); // Show the display data on screen
oldrpm = rpm;
}
}
}
// ---------------------------------
//
//
// Signal to update the frame buffer and then wait for it to be completed
void povUpdateDisplay(){;
showpov = true; // Show the display data on screen
do{ } while (showpov); // Wait for the display to be updated
}
void displayCharOnPov(char letter) {
unsigned int y = 0;
for (int i=0; i < 6; i++){
y=Font8x5[letter][i];
povDisplayData[printseg]= y;
printseg++; // update the next print segment pointer
}
}
void povPrint(String povtxt){
for (int z=0; z< povtxt.length(); z++){
displayCharOnPov( povtxt.charAt(z));
}
}
// Clear pov Display
void povClear(){
for (int zb=0; zb < 64; zb++){
povDisplayData[zb]=0;
}
}
//------------------------------------------------------------
//
// Timer 1 - Input capture interrupt routine - digital input 8
//
ISR(TIMER1_CAPT_vect) {
revCount = ICR1 + timer1CountOverflows * 65535; // Count for the last revolution of the disc
povDisplayColumnWidthCount = revCount / 64; // Calculate the time count for a column
TCNT1 = 0; // Reset timer 1 counter
povDisplayColumn=0; // Reset display to the first column
OCR1A = povDisplayColumnWidthCount-1; // Set timer 1 OCR1A interrupt count duration
timer1CountOverflows = 0; // Reset timer1 overflows
if (showpov){
updateFrameBuffer = true; // Tell timer 1 OCR1A interrupt to update the frame buffer
}
}
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
timer1CountOverflows++; // Increment the timer1 overflows counter
}
ISR(TIMER1_COMPA_vect){ // Timer 1 Output compare A match interrupt routine
OCR1A = TCNT1 + povDisplayColumnWidthCount-1; // Update timer1 with the next time interval
if (updateFrameBuffer){ // Update the framebuffer only once
povFrameBuffer[povDisplayColumn]=povDisplayData[povDisplayColumn]; // Copy display data into the framebuffer
if (povDisplayColumn >= 63){
updateFrameBuffer = false; // framebuffer has been updated
showpov = false;
}
}
if (povDisplayColumn < 64){
PORTD = povFrameBuffer[povDisplayColumn]; // Output data to the leds from the frame buffer
} else {
PORTD = 0; // If the spinning stops then turn off the leds
}
povDisplayColumn++; // increment the display column
}
Sketch 4 – POV Animation Effect – Rotate Rows
This sketch uses multiple screens or frame buffers to create an animation effect. The frame buffers are initialised with display data which is rotated in each consecutive buffer.
The main program loop switches between the frame buffers to create the effect.
// POV project
//
// Animation effect - rotate rows
// This uses multiple screens or frame buffers to create an animation effect
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
const byte ledpin = 13; // On board led output pin
const byte inputCapturePin = 8; // Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0; // The measured count width of a column
volatile byte timer1CountOverflows = 0; // Timer 1 ounter overflows
volatile unsigned int povDisplayColumn = 0; // The current pov display column
volatile byte povCurrentDisplay =0 ; // The display currently being sent to screen
volatile byte povRequestDisplay =0 ; // The display to be sent to screen next
// Data to be displayed on the pov display
// initialised with static text
volatile byte povDisplayData[8][64] ={
B00000001, // T
B00011111,
B00000001,
B00000000,
B00011111, // E
B00010101,
B00010101,
B00000000,
B00011111, // C
B00010001,
B00010001,
B00000000,
B00011111, // H
B00000100,
B00011111,
B00000000,
B00010111, // y
B00010100,
B00011111,
B00000000,
B00011111, // D
B00010001,
B00001110,
B00000000,
B00011111, // I
B00000000,
B00010111, // Y
B00010100,
B00011111,
0,
0,
0,
B11111000, // Y
B00101000,
B11101000,
B00000000,
B11111000, // I
B00000000,
B01110000, // D
B10001000,
B11111000,
B00000000,
B11111000, // Y
B00101000,
B11101000,
B00000000,
B11111000, // H
B00100000,
B11111000,
B00000000,
B10001000, // C
B10001000,
B11111000,
B00000000,
B10101000, // E
B10101000,
B11111000,
B00000000,
B10000000,
B11111000, // T
B10000000,
0,
0,
0
};
void setup()
{
noInterrupts();
DDRD = 0b11111111; // Set all PORTD pins to outputs
PORTD = 255; // Turn the leds off
pinMode(ledpin, OUTPUT); // Onboard LED output
pinMode(inputCapturePin, INPUT_PULLUP); // Hall effect sensor connected to this pin
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
TCCR1A = 0; // Normal wave generation mode
TCCR1B = 0;
TCNT1 = 0; // Reset timer 1 counter
OCR1A = 0xFFFE; // Initialise timer 1 output compare match
// Timer counter contol register
// ICNC1 - Input capture noise canceller
// ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
//
// TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10); // /1024 prescale
// TCCR1B = bit(ICNC1) | bit(CS12) ; // /256 prescale
// TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10); // /64 prescale
//TCCR1B = bit(ICNC1) | bit(CS11); // /8 prescale
TCCR1B = bit(ICNC1) | bit(CS10); // /1 prescale
// Timer interrupt mask register
// Input capture interrupt enable
// Output compare A match interrupt enable
// Timer overflow interrupt enable
TIMSK1 = bit(ICIE1) | bit(OCIE1A) | bit(TOIE1);
interrupts(); // enable interrupts
// Load rotated copies of the data in the first display into the other displays
//
for (byte rotn=0; rotn <= 63; rotn++){ // columns 0 to 63
for (byte rown=1; rown <= 7; rown++){ // rows 1 to 7
povDisplayData[rown][rotn] = rotl( povDisplayData[rown-1][rotn] );
}
}
}
void loop() {
povRequestDisplay = 0;
delay(500);
for (byte rown=0; rown <= 7; rown++){ // Displays 0 to 7
povRequestDisplay = rown; // Change displays
do{} while(povRequestDisplay != povCurrentDisplay); // Wait for screen to update
delay(100);
}
}
unsigned char rotl(unsigned char c)
{
return (c << 1) | (c >> 7);
}
//------------------------------------------------------------
//
// Timer 1 - Input capture interrupt routine - digital input 8
ISR(TIMER1_CAPT_vect) {
povDisplayColumnWidthCount = ( ICR1 + timer1CountOverflows * 65535 ) / 64; // Calculate the time count for a column
TCNT1 = 0; // Reset timer 1 counter
povDisplayColumn=0; // Reset display to the first column
OCR1A = povDisplayColumnWidthCount-1; // Set timer 1 OCR1A interrupt count duration
timer1CountOverflows = 0; // Reset timer1 overflows
if (povRequestDisplay != povCurrentDisplay){ // Change the display
povCurrentDisplay = povRequestDisplay;
}
}
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
timer1CountOverflows++; // Increment the timer1 overflows counter
}
// Timer 1 Output compare A match interrupt routine
ISR(TIMER1_COMPA_vect){
OCR1A = TCNT1 + povDisplayColumnWidthCount-1; // Update timer1 with the next time interval
if (povDisplayColumn < 64){
PORTD = povDisplayData[povCurrentDisplay][povDisplayColumn]; // Output data to the leds from the frame buffer
} else {
PORTD = 0; // If the spinning stops then turn off the leds
}
povDisplayColumn++; // increment the display column
}


In the sketch 2, where to display ” povUpdateDisplay” in frame buffer section, please help