POV And POV Image Encoder
One of my recent projects was to build a POV display device. There are already many microcontroller based POV devices out there, but most of those I have seen use around eight LEDs and have fixed font types. So I thought of developing something that is larger (e.g. using more LEDs) and more flexible (e.g. can display both text and images).
Without using any shift registers, a single ATmega328p chip can handle a around 20 I/O ports. I decided to use 16 LEDs, twice the number of LEDs as the ones you commonly see. This gives me at least four free pins for other purposes. You can probably build a POV with around 20 LEDs, but If you need more than that, you will have to use shift registers to expand the available IO pins or change to a chip that has more built-in IO pins (i.e. ATMega1280). But as you shall see later, the image generation process will be considerably more complex and the required memory storage will be much larger as well.
POV Display and Its Synchronization
In order to achieve a stable image output, the image frames generated by the POV device must be synchronized. A POV display can operate as a hand device (e.g. a wand) or be driven by a motor. When the POV device is built for hand use, it usually has a motion detector (from simple spring-in-cylinder switch to the more exotic accelerometer) so that the device can start displaying when the hand is waved. I decided to go with a motor driven design so that I can have more precise control over the output image. In this approach, image frames must be synchronized for each full rotation. A simple way to detect the motor rotation is to use a IR transmitter and receiver.
The following is the full design of my POV display (the left side shows the optical triggering mechanism mentioned above):
And here is the finished circuit mounted on a motorized rotating platform:
Here is a picture of the side view, you can see how the IR transmitter and receiver circuits are mounted. Whenever the transmitter and the receiver line up (it happens every rotation), the transistor would output a LOW signal to the ATMega328p (pin 28, Arduino analog pin 5) which indicates the beginning of a new frame. To reduce the light interference from the environment, the receiver is mounted on the rotor so it is facing downwards.
Image Encoder
Building the circuit is only half of the game. In order to provide the most flexible and the richest displays, I decided to build a image encoder which can be used to automatically translates a binary image into the appropriate code that is needed for the POV display. For simple POV displays, the display patterns can be mapped directly into a binary two dimensional array, with 1 represents LED on and 0 represents LED off. But this approach is not practical when displaying larger images. For an image the size of 16×100, 1600 bytes would be required to store the image information. This might not sound like much but that is almost all of ATMega328’s usable SRAM. Of course, you can always store the data structure off in Flash memory using PROGMEM, but it is not the most efficient way to do so. A better approach would be to pack these pixels into bytes so that each byte can represent 8 pixel values.
This code is shown as below. This C# code works with both Microsoft’s .Net platform or Mono.Net and thus it can be run on either Windows or Linux:
using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; namespace convertImg { class Program { static void Main (string[] args) { if (args.Length != 1) return; Image img = Image.FromFile (args[0]); Bitmap bm = new Bitmap (img); BitmapData bmd = bm.LockBits (new Rectangle (0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); byte[,] ay = new byte[img.Width, img.Height]; byte[] compactedAy = new byte[2 * img.Width]; for (int y = 0; y < img.Height; y++) { for (int x = 0; x < img.Width; x++) { byte b = Marshal.ReadByte (bmd.Scan0, y * bmd.Stride + x * 4); if (b == 0) ay[x, y] = 1; else ay[x, y] = 0; Console.Write (ay[x, y]); } Console.WriteLine (); } Console.Write ("byte dispArray[] = {"); for (int x = 0; x < img.Width; x++) { if (x % 8 == 0) { Console.WriteLine (); Console.Write ("\t"); } compactedAy[2 * x] = (byte)(ay[x, 0] << 7 | ay[x, 1] << 6 | ay[x, 2] << 5 | ay[x, 3] << 4 | ay[x, 4] << 3 | ay[x, 5] << 2 | ay[x, 6] << 1 | ay[x, 7]); compactedAy[2 * x + 1] = (byte)(ay[x, 8] << 7 | ay[x, 9] << 6 | ay[x, 10] << 5 | ay[x, 11] << 4 | ay[x, 12] << 3 | ay[x, 13] << 2 | ay[x, 14] << 1 | ay[x, 15]); if (x < img.Width - 1) { Console.Write ("{0,3} ", compactedAy[2 * x]); Console.Write (", "); Console.Write ("{0,3} ", compactedAy[2 * x + 1]); Console.Write (", "); } else { Console.Write ("{0,3} ", compactedAy[2 * x]); Console.Write (", "); Console.Write ("{0,3} ", compactedAy[2 * x + 1]); } } Console.WriteLine (); Console.WriteLine ("};"); } } }
The code above converts a binary image into an array of packed bytes. Every vertical line consists of 16 pixels (since we are using 16 LEDs) which is translated into two consecutive bytes in the resulting array. In general, the more LEDs you have, the more space it would take for each encoded line. As you can see, making the number of LEDs divisible by eight enables us to use full bytes for each encoded line. It is possible to use number of LEDs that do not fall into this pattern, but the resulting code would be either more difficult if full byte utilization is desired or leave some unused space between each line if lines need to start on byte boundaries.
Here is our test image.
Using the image above (in PNG format) as the input, the encoded image is outputted to the terminal (formatted as a C++ array)
byte dispArray[] = { 0, 2, 0, 30, 0, 252, 7, 240, 31, 48, 252, 48, 224, 48, 252, 48, 31, 48, 7, 240, 0, 252, 0, 30, 0, 2, 0, 0, 0, 0, 0, 0, 15, 254, 15, 254, 6, 0, 12, 0, 12, 0, 12, 0, 0, 0, 1, 240, 7, 252, 14, 14, 12, 6, 12, 6, 12, 6, 12, 6, 6, 12, 255, 254, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 15, 248, 15, 252, 0, 14, 0, 6, 0, 6, 0, 6, 0, 12, 15, 254, 15, 254, 0, 0, 0, 0, 0, 0, 0, 0, 207, 254, 207, 254, 0, 0, 0, 0, 0, 0, 0, 0, 15, 254, 15, 254, 6, 0, 12, 0, 12, 0, 12, 0, 14, 0, 7, 254, 3, 254, 0, 0, 0, 0, 0, 0, 1, 240, 7, 252, 6, 12, 12, 6, 12, 6, 12, 6, 12, 6, 6, 12, 7, 252, 1, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 192, 12, 48, 48, 12, 32, 4, 76, 2, 76, 66, 128, 33, 128, 17, 128, 17, 128, 17, 76, 34, 76, 66, 32, 4, 48, 12, 12, 48, 3, 192, 0, 0 };
and is ready to be consumed by the Arduino side of the code.
Arduino Code
And here is the code on the Arduino side (I used Netbeans as my IDE, and you may need to change a couple of the includes if you are using the IDE bundled with Arduino. For more information, please see my previous article on this.)
As you can see, the dispArray is plugged in directly from the output of the encoder shown above. Note that here I assumed the image size is 16×100, if your image widths are different, then you will need to adjust the range of curPos accordingly.
The analogRead() statement in the main loop detects whether the IR transmitter and receiver are aligned. If so, the analog pin output would become lower as the transistor on the receiver side becomes saturated and when a pre-defined threshold is crossed, the decoding process begins. Depending on how fast the display spins, colDelay may need to be adjusted accordingly.
#include <binary.h> #include <HardwareSerial.h> #include <pins_arduino.h> #include <WConstants.h> #include <wiring.h> #include <wiring_private.h> #include <WProgram.h> #include <EEPROM/EEPROM.h> const int p1 = 0; //2; const int p2 = 1; //3; const int p3 = 2; //4; const int p4 = 3; //5; const int p5 = 4; //6; const int p6 = 5; //11; const int p7 = 6; //12; const int p8 = 7; //13; const int p9 = 8; //14; const int p10 = 9; //15; const int p11 = 10; //16; const int p12 = 14; //23; const int p13 = 15; //24; const int p14 = 16; //25; const int p15 = 17; //26; const int p16 = 18; //27; const int triggerPin = 5; //28; const int colDelay = 8; int ledPins[] = {p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16}; int curPos = 0; /** Image generator output */ byte dispArray[] = { 0, 2, 0, 30, 0, 252, 7, 240, 31, 48, 252, 48, 224, 48, 252, 48, 31, 48, 7, 240, 0, 252, 0, 30, 0, 2, 0, 0, 0, 0, 0, 0, 15, 254, 15, 254, 6, 0, 12, 0, 12, 0, 12, 0, 0, 0, 1, 240, 7, 252, 14, 14, 12, 6, 12, 6, 12, 6, 12, 6, 6, 12, 255, 254, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 15, 248, 15, 252, 0, 14, 0, 6, 0, 6, 0, 6, 0, 12, 15, 254, 15, 254, 0, 0, 0, 0, 0, 0, 0, 0, 207, 254, 207, 254, 0, 0, 0, 0, 0, 0, 0, 0, 15, 254, 15, 254, 6, 0, 12, 0, 12, 0, 12, 0, 14, 0, 7, 254, 3, 254, 0, 0, 0, 0, 0, 0, 1, 240, 7, 252, 6, 12, 12, 6, 12, 6, 12, 6, 12, 6, 6, 12, 7, 252, 1, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 192, 12, 48, 48, 12, 32, 4, 76, 2, 76, 66, 128, 33, 128, 17, 128, 17, 128, 17, 76, 34, 76, 66, 32, 4 of, 48, 12, 12, 48, 3, 192, 0, 0 }; /** * delay ms * 0.1 milliseconds */ void delayMilliseconds(int ms) { for (int i = 0; i < ms; i++) { delayMicroseconds(100); } } void setup() { for (int i = 0; i < 16; i++) { pinMode(ledPins[i], OUTPUT); digitalWrite(ledPins[i], LOW); } } void loop() { if (analogRead(triggerPin) < 800) { for (int curPos = 99; curPos >= 0; curPos--) { byte b1 = dispArray[curPos * 2]; byte b2 = dispArray[curPos * 2 + 1]; for (int i = 0; i < 8; i++) { digitalWrite(ledPins[i + 8], (b1 & (1 << i)) >> i); digitalWrite(ledPins[i], (b2 & (1 << i)) >> i); } delayMilliseconds(colDelay); } for (int i = 0; i < 16; i++) { digitalWrite(ledPins[i], LOW); } } }
POV Display in Action
Here’s the POV display in action. Using the programs in this article what you can show on this POV display is not just limited to characters, any binary image design that fits the image dimension (16×100 in this example) can be used. If you use different images for different frames you can even create your own POV animation.
about the IRtransmeter how we are going to connect to the main circuit…
also the specification all all the components that we are going to used. all the components
Im beginner with arduino, and found your Page by searching for pov and arduino…
The image encoder looks very interesting, but i don’t understand how to use the code “This C# code works with both Microsoft’s .Net…” and in which Program…
Thanks for helping an absolute beginner…
Hi Detlef,
The C# code is used to generate the image data array (dispArray) used in the Arduino code later. You can compile the code using Microsoft Visual Studio .Net (any version will do). Or if you are using Linux, you can compile it using Mono Develop.
Thanks for your reply, so i will try to get it work…
Hi
Thanks for sharing.
After two hours reading and testing about mono and C#, the program is fonctionnal under linux (fedora) with the following commands :
mcs -v /reference:System.Drawing.dll test_imagetobite.cs
mono test_imagetobite.exe arduino.png
hope to be useful
Just an another thing :
http://www.go-mono.com/mono-beginning/x145.html
Can I use hall sensor instead of infrared?
I don’t see why not. Hall sensor should work as well, the only drawback of a hall sensor is that the effective range is not as long as IR sensors and thus mounting the sensor could be a bit tricky.
Thanks for your reply, I have done it with hall sensor :). One more question how did you generate the png file? I mean how can I have my own bitmap?
Great. You should be able to use the code I wrote to generate what you want to display on the POV.
Hello,
I tried to compile the C# code, so I downloaded Microsoft Visual C# 2010, I created the project under “console application”, at the time of running the code, I got only a cmd prompt window for like 1/2 second, totally in black :( I have no experience with C# and Microsoft Visual C# 2010, What is wrong?
Thank you for helping, I am really frustrated because is the only thing I have to do to complete this great project, I am a beginner :)
Regards.
Hi Kim,
The app takes one argument (which is the full path of the input image file) and when the argument is not supplied it exits immediately. So to invoke the application you will need to supply the image file name to be converted.
Hello,
Thank you for your comment, with that information I could have the app working; I have no experience on programming in C#,due to my proyect has only 8 leds, how can I change the app code to have only 8 bits instead on 16?
Thank you very much,
Kim
Hello, is this correct?
static void Main(string[] args)
{
if (args.Length != 1) return;
args[0] = “D:\aaa.png”;
Image img = Image.FromFile(args[0]);
—My file is the png file displayed in your post and I pasted it on my D hard disk.
I do not really know where to put the path of my image.
Looks correct to me. You could just run the program in command line and pass the path to the file as the parameter though.
HEY KERRY (lol, delete my first comment, sorry, I just got excited. sorry! )! GREAT/SUPERB tutorial you got here, really. I was effing trying hard to search for a website that has a complete tut about this POV.
Luckily I found yours.
Thanks for your effort.
I just need to ask you this: will the display be the same if I design the LEDs into a globe shape? You know, the GLOBE POV which displays the earth map.THANK YOU SO MUCH AGAIN FOR THIS ARTICLE!!!
glenn.
PS: Ill update you KDWong when I finish my project. ;)
Hi Glenn,
Yeah, everything should be pretty much the same if you place the LEDs vertically on a globe. Good luck!
Hello again KerryWong ^__^. I’m glad that you have replied to my comment so soon.
Thanks for the information, I hope you can make a part two of this which can display a picture in 360 degrees, like a globe.
Firstly, I would like to thank you for sharing such a brilliant work of yours with us.
Secondly, I was wondering if I could get the details of the part of the circuit where power is transmitted. Thanks.
i have extracted binary information of 16*100 pixel image using MATLAB, can i use this binary array to display on this POV circuit ?
Why not? It should work.
I should pack them vertically or horizontally?
If you are using the code I had you should pack them starting from top left and line by line till the bottom right (raster scan).
I am still unable to pack binary array in byte using MATLAB. please help me. because I have no idea of .net and I am trying to make encoder using MATLAB
Hi,I cant get the generator to WORK! i am using Ubuntu Mono and all i get is ‘Press a key to continue’ And i am new to C#
Regards,
MohammadMubin
I Got it Working but how can i make animations
You will have to generate the bitmap for every rotation (or every nth rotation depending on your desired animation speed).
Sorry For a another Reply but i see 5 LEDS (when its spinning) The Top part of letter Shapes the first 5 ‘pixel’ Shift to the right for every character
Hello dear admin!
How make picturebox image to image array result in texbox winform?
please help me, console convert to winform
Hello Admin
How use shift register 74hc595?
I make 40 leds pov display. Please arduino code