So I started messing around with different colour formats and way's to fade LEDs gracefully yesterday. I've not set up the timer yet so I just used a loop with a big counter in it for now.
Be warned, this post is about code, if it's too dry for you just skip to the video.Each LED wants to hear three bytes of 8 bits, one for red, one green, and the last blue.
Up till now I've been storing each colour as an "unsigned long", this is a four byte type, thus I waste a whole byte each time I store a colour. Believe me you feel this on a device with 128B (yes, bytes) of RAM and 2KB of program words. Incidentally these simple test programs use 0 bytes of RAM, the C compiler is being clever and optimising it to be all instructions.
I had a play with structures and custom types like:
- Code: Select all
typdef struct {
unsigned char r;
unsigned char g;
unsigned char b;
} rgb;
Note that a character is also a very small integer.
This is great because (in theory) it only uses three bytes of memory, it's easy to pass into and return from functions. It's different when you're defining a colour (constant rather than #define). Sadly this used exactly as many program words as the unsigned long did, DOH!
So some people at work suggested I use less resolution for the colour, technically I've been using 24-bit "True Colour" so far, how about 16-bit "High Colour". This is a bit strange, from MSB to LSB it uses; 5 bits for red, 6 bits for green then 5 bits for blue. This is because our eyes are more sensitive to green light. I did try 15-bit colour leaving 1 bit unused, I could get substantially less close to the colours I wanted than I could with 16-bit. Now this format will fit into an "unsigned int" only two bytes, and it actually led to a substantial saving of program words.
I'm not exactly using High Colour as I map the 16-bit value to the 14-bit value the LEDs want like this:
- Code: Select all
16-bit 15 7 0
MSB [ R R R R R G G G G G G B B B B B ] LSB
| | \ \ \ \
| | \ \ \ \
| | \ \ \ \
| | \ \ \ \
| | \ \ \ \
| | \ \ \ \
| | | | \ \
| | | | \ \
| | | | \ \
| | | | \ \
24-bit | | 0 0 0 | | 0 0 | | 0 0 0
MSB [ R R R R R R R R G G G G G G G G B B B B B B B B ] LSB
23 15 7 0
This may seem foolish, but there is a reason:
Dividing or multiplying with floating point values is strictly off limits! Do it only once and you immediately crash past the 2,000 program words limit.
This limitation brings me onto the next topic: Fading.
I need to take these defined colours and make them dimmer. I immediately found that there is not enough resolution to do this in 16-bit land, so I worked out a way to dim the 24-bit value before transmission. You can't just subtract 1 from each sub-pixel, this changes the colour as it fades unless you are fading pure red, green or blue. Eventually I found that you could define another number which is, or is close to the lowest common denominator for that 24-bit colour (eg: 0xFF0000 needs 0x010000, 0xFF8000 is 0x020100). Yet still this is not good for anything other than primary colours.

What about timing:
* I know I want 25FPS, I do not want flicker.
* I know I might want to do something in the sequences every 1 second, maybe 2, maybe 0.25?
* I know I might want 100 LEDs in a string.
* I know I'm currently clocking the LED string and uC at 1Mhz, and I could move one or both up to 8MhZ, or even 16Mhz with an external crystal.
* I know the LEDs need 300us (microseconds) of silence for them to commit their colour to the LED via the PWM drivers.
So I need to have a timer interrupt every 0.04 seconds, this calls a service routine that every 25 times can call "do_sequence_actions" that can do things like change from fading down to fading up. And on every pass it will call "do_frame_actions" which can move an LED to the next faded value then cause a frame to be displayed. Easy, I sill have a spare timer too, we may be able to have a watchdog (I'll explain what that is in a later post).
Now, can we actually display a frame in less than 1/25th of a second (0.04 seconds)? 100 LEDs needs 2,400 bits of data to be sent. At the current 1Mbaud this takes 0.0024 seconds, plus the 300us delay after transmission is 0.0027 seconds. We have ample room, and there is no need to worry about how long the code takes to execute because the transmission will be handled in hardware while we work out what to draw in the next frame (there is a small interrupt driven bit of code, but it's very small).
Here's a video of fading down red, and fading up and down RGB:
[youtube]s2x8GbdKIq0[/youtube]
http://www.youtube.com/watch?v=s2x8GbdKIq0It does not work well at all for non-primary colours. You'll also notice that the fading is not linear, this is the LEDs, they are more sensitive at lower duty cycles, I need to have something where I skip several values at high duty cycles and go one by one at low duty cycles. So all the code I wrote last night will be going in the bin, I need to attack this from a different angle.
I noticed something cool about my colours back when they were 24-bit values:
- Code: Select all
#define RED 0xFF0000
#define ORANGE 0xFF2000
#define YELLOW 0xFF7000
#define GREEN 0x00FF00
#define BLUE 0x0000FF
#define INDIGO 0x5000FF
#define WHITE 0xFFFFFF
I only have 4 distinct sub-pixels!
This means I can solve the no division problem the way every uC developer eventually does, with a lookup table. I can have 4 100-element arrays of unsigned char, implemented as C constants these will eat 400 out of the 2,000 program words, I can live with this. Each array will run from 00 to the sub-pixel value logarithmically, this way I can sweep over the arrays to fade any colour I like in a liner fashion. It's perfect!