- Either all colors of a single RGB-LED are off (meaning the whole LED is off), or at least one color is completely switched on;
- Each color has 7 different levels of activity;
- The time between color-changes is at least one second;
- We use the general purpose registers to store the color changes. This gives us roughly 2 * 80 = 160 bytes.
First, we need to encode the colors. 7 levels can be encoded in 3 bits. Unfortunately, 3 * 3 = 9, and 9 > 8 -- so we would need more than one byte to encode a single color. Not desirable.
However, one of the colors is always completely switched on. We have three colors, and 3 can be encoded in 2 bits.
Therefore, we choose the following encoding: the first two bits encode which of the three colors is completely switched on. If this value is 0 (both bits off), then the LED is switched off.
The next three bits encode the level of Red (or, if Red has been switched on, Green).
The next three bits encode the level of Green (or, if Green or Red have been switched on, Blue).
That means 2 + 3 + 3 = 8 bits per color -- exactly one byte!
OK, on to tackle the representation of the sequences. If we represent every color for every second, we have to allocate three bytes per second (one for each LED). This means that the maximum cycle we can do is 160/3 = 53 seconds. That's too short, and we don't want to lengthen the time between color switches.
So, how can we compress this? By not representing the colors for LEDs whose colors don't change. This means we have to add extra codes within the stream of color-encoding bytes. There's two ways we can compress the data.
The first way is if the pauses between changes of colors are more than 1 second. Instead of representing the same bytes, it would be beneficial if we could encode a pause of X seconds.
The second way is to encode which LEDs will change in the next second. Instead of using a sequence of three bytes for every second, we encode which LED will change, and only represent the colors for the LEDs that change.
Now, we have three LEDs, so we need three bits to encode which LEDs are going to change in the next second. If we take a nibble (half a byte, 4 bits) to encode for that, we have one bit left. Guess what: we can use that bit to switch between a pause or encoding changes!
So, the 'metadata'-byte will consist of 2 nibbles. Each nibble has a leading bit. If that bit is off, the next three bits specify the pause in seconds until the next change in colors. That means that a pause of maximally 7 seconds can be encoded -- if you need a longer pause, you can use several pause-'instructions' after one another.
If that bit is on, the next three bits specify which LEDs will change color in the next second. The following bytes will be the color-encodings of the LEDs that change.
So, one 'metadata'-byte will actually contain instructions for two color-changes (or two pauses, or one pause and one color-change).
This 'metadata' will add overhead to the 'instruction'-stream. In the worst-case scenario, each of the three LEDs will change color every second. This means three color-encoding bytes per second, plus the half-byte metadata overhead, thus 7 bytes per two seconds. That means a maximum of 160 / 7 * 2 = 44 seconds, 9 seconds less than the 'uncompressed' datastream.
In the best scenario, the colors never change, and the whole instruction-stream will consist of 7-second pauses. In that case, you have only the metadata bytes, and thus your cycle will be 160 * 2 * 7 = 2240 seconds. That's over 37 minutes!
Of course, real-life color cycles will be somewhere in between. Assume the color changes of 1 LED, 2 LEDs, 3 LEDs and pauses occur in a ratio of 4:3:2:1, and that pauses are (on average) 2 seconds. One 'unit' is 10 'instructions', which means 5 instruction-bytes. Color encodings take (4 * 1) + (3 * 2) + (2 * 3) = 16 bytes. This whole series takes 4 + 3 + 2 + 2 = 11 seconds, and needs 16 + 5 = 21 bytes of instruction stream.
That means that we can encode 160 / 21 = 7.6 cycles of this, for a total of 83 seconds. That's 30 seconds longer than the initial encoding!
Next question: does anyone know how to preload values in registers at startup, without having to type it all out in the program itself?