With 20 LEDs in a row a persistence of vision display is something that just has to happen. But how to implement it most efficiently? It could be done from scratch but I will try to reuse as much open source stuff as possible to get a lean and mean implementation. I also will use „direct port manipulation“. Althoug this is a somewhat advanced technique it is very appropriate for this application. It will make the sketch much simpler.
So let’s see what a POV display actually has to do. It will display a bit pattern for some time and then replace it by a new pattern. Often the timing is synchronized to some movement of the display. In this case I will just use some fixed delays. This leaves the question how to store and output the bit patterns. Of course 20 bits will fit into 3 bytes. And of course rows of 3 bytes can be stored in some array.
But there are some issues. Extracting the bits and pushing them through digitalWrite is slow and somehow anoying. Especially since the 20 pins are mapped to exactly 3 ports. All 3 ports are accessible by writing just 1 byte. This technique is known as direct port manipulation. It may make the sketch incompatible to some Arduino clones that are not based on the Atmega 328 family. This can be fixed with some macros but I sacrifice compatibility for clarity.
Another issue is how avr-gcc handles arrays. By default arrays are copied from FLASH memory into RAM. Due to the tight RAM constraints of the controllers this is something that I definitely want to avoid. Fortunately there exist the pgmspace libraries which provide the PROGMEM macro. With this macro the compiler will NOT copy the array to RAM. It follows that the array’s contents can not be accessed as usual. Instead the content must be read with some helper function.
Enough said. Here is how such a sketch will look like.
//
// www.blinkenlight.net
//
// Copyright 2011 Udo Klein
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
#include <MsTimer2.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
uint8_t pov_pattern[] PROGMEM = {
0b000000, 0b000000, 0b00000000, // line 1: ....................
0b000000, 0b000000, 0b00000000, // line 2: ....................
0b000000, 0b000000, 0b00000000, // line 3: ....................
0b000000, 0b000000, 0b00000000, // line 4: ....................
0b000000, 0b000000, 0b00000000, // line 5: ....................
0b000000, 0b000000, 0b00000000, // line 6: ....................
0b000000, 0b011110, 0b00000000, // line 7: .......XXXX.........
0b000001, 0b111110, 0b00000000, // line 8: .....XXXXXX.........
0b000011, 0b111110, 0b00000000, // line 9: ....XXXXXXX.........
0b000111, 0b111100, 0b00000000, // line 10: ...XXXXXXX..........
0b001111, 0b110000, 0b00000000, // line 11: ..XXXXXX............
0b001111, 0b100000, 0b00000000, // line 12: ..XXXXX.............
0b011111, 0b000000, 0b00000000, // line 13: .XXXXX..............
0b011110, 0b000000, 0b00000000, // line 14: .XXXX...............
0b011110, 0b000000, 0b00111110, // line 15: .XXXX.........XXXXX.
0b011100, 0b000000, 0b01111110, // line 16: .XXX.........XXXXXX.
0b111100, 0b000000, 0b01111110, // line 17: XXXX.........XXXXXX.
0b111100, 0b000000, 0b01111110, // line 18: XXXX.........XXXXXX.
0b111100, 0b000000, 0b01111100, // line 19: XXXX.........XXXXX..
0b111000, 0b000000, 0b00000000, // line 20: XXX.................
0b111000, 0b000000, 0b00000000, // line 21: XXX.................
0b111000, 0b000000, 0b00000000, // line 22: XXX.................
0b111000, 0b000000, 0b00000000, // line 23: XXX.................
0b111100, 0b000000, 0b01111100, // line 24: XXXX.........XXXXX..
0b111100, 0b000000, 0b01111110, // line 25: XXXX.........XXXXXX.
0b111110, 0b000000, 0b01111110, // line 26: XXXXX........XXXXXX.
0b011111, 0b000000, 0b01111110, // line 27: .XXXXX.......XXXXXX.
0b001111, 0b000000, 0b00111100, // line 28: ..XXXX........XXXX..
0b001111, 0b100000, 0b00000000, // line 29: ..XXXXX.............
0b000111, 0b111000, 0b00000000, // line 30: ...XXXXXX...........
0b000011, 0b111100, 0b00000000, // line 31: ....XXXXXX..........
0b000001, 0b111100, 0b00000000, // line 32: .....XXXXX..........
0b000000, 0b111100, 0b00000000, // line 33: ......XXXX..........
0b000000, 0b000000, 0b00000000, // line 34: ....................
0b000000, 0b000000, 0b00000000, // line 35: ....................
0b000000, 0b000000, 0b00000000, // line 36: ....................
0b000000, 0b000000, 0b00000000, // line 37: ....................
0b000000, 0b000000, 0b00000000, // line 38: ....................
0b000000, 0b000000, 0b00000000, // line 39: ....................
0b000000, 0b000000, 0b00000000, // line 40: ....................
};
void blink() {
static uint16_t index = 0;
PORTC = pgm_read_byte(pov_pattern+(index++));
PORTB = pgm_read_byte(pov_pattern+(index++));
PORTD = pgm_read_byte(pov_pattern+(index++));
if (index >= sizeof(pov_pattern)) { index = 0; }
}
void setup() {
DDRD = 0b11111111; // set digital 0- 7 to output
DDRB = 0b00111111; // set digital 8-13 to output
DDRC = 0b00111111; // set digital 14-19 to output (coincidences with analog 0-5)
MsTimer2::set(2, blink);
MsTimer2::start();
}
void loop() { }
Notice that the sketch does not loop. Instead it uses the MsTimer2 library to trigger blinking. This library is not included by default with Arduino. You have to download it from the playground and install it. Installation means just unpacking it into the library directry. If the Arduino IDE is open during „installation“ it must be restarted to pick up the library.
It would be easily possible to get the same effect with delays. However I like this library a lot. It provides some rudimentary multitasking. Thus the main loop is empty. Although the animation is running the controller is „free“ for doing other computations. If no „other“ computations are required it would also be possible to sleep it most of the time to reduce power consumption.
So now we can have some nice light painting.
The picture above was taken with a digital SLR mounted on a tripod. The shutter was open for 10s. I was just waving the shield in front of the camera. This is very easy and needs very little tries to get great results.
Before you start to edit my sketch to create your own persistence of vision displays read on to learn how to generate pov sketches.
You may also want to learn about my new and improved version of the sketch above.

