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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
// 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 (1078 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
// 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 (1078 downloads )  into the same directory as the sketch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
// 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
// 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