It uses 16 RGB LEDs to display full color 24-bit bitmap files created by the MS Paint tool. What is especially cool about this project is that unlike many common POV systems, this one displays full color graphics, not just text. Above is a Youtube video showing the POV in action.
Initial Designs:
An early design with 8 RGB LEDs and 3 shift registers. The TV remote was used to change program settings while the device was spinning on the ceiling fan. |
Sample Images of this POV version:
16 Color Bitmaps:
I wanted to develop an easier method of adding color to my images so I started to research bitmaps. I first tried 16 color bit maps because they were small file size and stored each pixel as a color. This Arduino code was the first to utilize MS Paint bitmap files and was much simpler than my previous attempts at adding multiple colors to an image. My code would cycle through each byte in the bitmap, determine what color it represented, convert the 16 colors from the image to the limited available 8 color palette and then display it row by row. The code was a radical upgrade from previous code that used online POV generators to create simple graphics and fonts for text. However, the code was never utilized on any board. I could only combine solid colors such as BLUE and RED to make PURPLE or all the colors to make WHITE. I only had 8 colors available if you accept BLACK as a color. Very shortly after this code was completed I studied the other MSPaint compatible bitmap formats instead and discovered how 24-bit bitmaps work. This was the last POV that used 8 RGBs LEDs with shift registers. In this version, each pixel was 1 byte so a 16 by 16 pixel image would take use 256 bytes.
24-bit Bitmaps:
This Persistence of Vision project displays full color graphics, not just text. |
This is a sample of the graphical ability of my P.O.V. display. I mounted it to my ceiling fan. |
PROGMEM and Address Spaces:
The Arduino uses the Harvard Architecture which according to Wikipedia means "The Harvard architecture is a computer architecture with physically separate storage and signal pathways for instructions and data." (Wiki link is below.) Each variable is stored at a specific memory address and with Harvard Architecture systems there are two separate address spaces for SRAM and for FLASH storage. Normally, the program itself stays in FLASH and the active code and variables/string values reside in SRAM. When you are accessing a variable value, you really are just accessing a type of pointer to a memory address that contains the value for that variable which normally would be in SRAM. However, if you store that value in FLASH instead then the value actually resides in a completely different memory address space, even if it had the same numerical address. Therefore, variables in PROGMEM (FLASH) must be stored and retrieved in a specific way which is rather complicated for the typical user. Fortunately, Mikal Hart, who runs the Arduiniana website, created an easy to use library to facilitate the use of PROGMEM. Suddenly, it was very simple to store large arrays in FLASH. In fact, it was so easy that now I store each and every text string in flash as well for my programs which save me a considerable amount of SRAM. Running out of SRAM in Arduino is not fun at all and is very confusing because your program just crashes and you don't know why. Even the functions available on the Internet that determine how much SRAM is left are somewhat confusing as you really probably aren't running out of SRAM but actually no longer have enough heap/stack space. The heap and the stack are very low-level terms in machine language. Basically, you have to have enough SRAM left to handle the heap/stack but that value may very from program to program. Still, the function is very useful. It is found in the SD.h library which is included in the Arduino IDE 0022. The specific file in the library that has the function is "sdfatutil.h". I am not exactly sure how this function works as it goes very low level, but it has been a reliable indicator of SRAM.
The FreeRam() function is below. You can just copy and paste it to your program.
static int FreeRam(void) {
extern int __bss_end;
extern int* __brkval;
int free_memory;
if (reinterpret_cast<int>(__brkval) == 0) {
// if no heap use from end of bss section
free_memory = reinterpret_cast<int>(&free_memory)
- reinterpret_cast<int>(&__bss_end);
} else {
// use from top of stack to heap
free_memory = reinterpret_cast<int>(&free_memory)
- reinterpret_cast<int>(__brkval);
}
return free_memory;
}
Creating C "Prototypes"
Now that I had the flash library I encountered another problem very specific to the C language and how the Arduino software compiles code. In C, each and every function needs what is called a "prototype." This "prototype" defines the function name, what the datatype of each value passed to it will be, and what datatype the function returns if any. I wanted to pass the array for my images to a drawing function but it wouldn't work. It turns out that the Arduino software does not recognize the "objects" (or data types) defined by the flash library. Normally, the Arduino would create the prototypes for you for all of your functions but it could not create prototypes for functions that have "objects" that it does not recognize. The solution was simple. I just had to manually enter the prototypes for each function that was passed a value that was located in FLASH. This is definitely useful information to the beginner.
Here is the technical explanation from Mikal Hart:
" The FLASH_STRING macro creates and object of type _FLASH_STRING.
FLASH_ARRAY creates an object of type _FLASH_ARRAY.
FLASH_TABLE creates an object of type _FLASH_TABLE. "
Again, these objects are not recognized by the Arduino compiler so you'll need to add the appropriate prototypes manually to the top section of your code for any function that will use these new objects. Notice how in the following example that I just define the object types that will be passed to the particular function and the value that the function may return."FullDrawBitmap" accepts a "_FLASH_ARRAY," a "word", and a "boolean". "DrawWith16RGB" accepts a "_FLASH_ARRAY", and a "word" value. This is pretty much all that you need to do to handle this prototype problem.
C Prototype Example:
void FullDrawBitmap (_FLASH_ARRAY
void DrawWith16RGB (_FLASH_ARRAY
The code in this latest version reads through the bitmap data backwards due to the order that the data is stored in the bitmap. Each row of the image has 16 pixels or LEDs, and each LED/pixel has 1 byte for Red, Green, and Blue. Therefore, each row reads in 48 bytes of data per row. The TLC is a 12-bit PWM device with 16 channels on each chip. To handle the 48 lights I would need 48 channels or 3 chips. In total, this is 48 * 12 == 576 bits per column (or 72 bytes per column.) All in all, that is 48 bytes for each row of the image and another 72 bytes to actually send it for a grand total of 120 bytes (or 960 bits) for each and every row of the supplied image. Yikes! Plus, considering the RPM of my ceiling fan, I needed to keep the LEDs on for very short periods of time, between 200 - 800 micro seconds (not milli, micro.) Every 200 microseconds, the SPI bus that I use to control the TLC5940 chips must be clocked 576 times! That's pretty fast. So fast in fact that you actually can not do it fast enough with normal digitalWrite() commands. I was using a library (link below) to facilitate the communications to the TLC5940 chips and it used direct port manipulation. Researching on the internet, I found that digitalWrite actually is much slower and in my situation, without direct port manipulation I would have been unable to complete the project.
What's Next?
I like the design and believe that it is solid in its current form but there is definitely room for improvement.
- For starters, since the new design I have been unable to use my TV remote to control the project whilst in motion because the TLC5940 library uses both timer1 and timer2. The IR Remote library needs to have timer1 and when I put them both together they could not cooperate. I have been using a Bluetooth adapter connected to the Arduino's serial port but I believe that this is an overly complicated solution.
- Using the TLC5947 will solve the timer problem because it has its own built-in GSclock and operates much like a shift register and not requiring both of my timers to use it. The TLC5947 is a surface mount IC so I will need to create a PCB design and etch it. The TLC5947 is a 24 channel driver so I'll actually need one less chip.
- Since I will be using the physically smaller TLC5947 with 24 channels each I would like to use 32 surface mount RGB LEDs for greater resolution. Cost will be a concern so I'll need to be frugal.
References / Links:
POV Message Generator
Simplified Windows BMP Bitmap File Format Specification
Write your own 24-bit BMP
Texas Instruments TLC5940 - Arduino Playground
An Arduino Library for the TI TLC5940 16-Channel PWM Chip
Read, Set, Oscillate! The Fastest Way to Change Arduino Pins
http://www.billporter.info/ready-set-oscillate-the-fastest-way-to-change-arduino-pins/
PROGMEM - Arduino
http://www.arduino.cc/en/Reference/PROGMEM
Flash - Arduiniana - Arduino wisdom and gems by Mikal Hart
http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
Harvard Architecture - Wikipedia
http://en.wikipedia.org/wiki/Harvard_architecture
Propeller Clock Timing Theory
A FAT16/FAT32 Arduino library for SD/SDHC cards
http://code.google.com/p/sdfatlib/
--
Very nicely done. Thanks for sharing this with the world.
ReplyDelete