In our last lesson, we utilized the badass (P)ulse (W)idth (M)odulation tech to breathe life into a simple LED. Here, we're going to take this to new levels and apply it to multiple channels.
Unfortunately, the PWM hardware included with the Raspberry B+ is only capable of communicating over a single channel. Since we have 3 channels we need to talk to (for each of our three colors), we'll have to go soft and crank this up with software: softPwm.
First: How the RGB LED works
The RGB LED I'm working with has four pins. Three of these pins take input that control the brightness level for (R)ed, (G)reen, and (B)lue. This allows us to take advantage of the additive color system to create all kinds of colors by supplying different proportions of these three types of light (e.g. fully bright red and fully bright blue will appear purple).
Our Problem: How can we easily store color values in RGB format as a percentage of luminance?
Hexadecimal To The Rescue!
I know what you're thinking:
Don't lose me just yet. There's some hidden gold here! The magic of bitwise operations.
The binary 11111111
is equal to 255
. Luckily for us, this binary representation maps perfectly to Hexadecimal, as FF == 255
!
11111111 00000000 00000000
RED GREEN BLUE
What this means is that we can use Hexadecimal literals in Ruby, and as a result start to see some serious gains. First off, Hexadecimal numbers make a lot of sense to us as colors already. These are the Ruby equivalent of the CSS versions you might be familiar with:
Color | Hexadecimal Representation in Ruby |
---|---|
Red | 0xFF0000 |
Green | 0x00FF00 |
Blue | 0x0000FF |
Purple | 0xFF00FF |
The real value here comes with the calculations we can make. Using hexadecimal literals makes some things easier. We can extract each red, green, or blue value from any given hexadecimal color by AND'ing the bits of the maxed out version of our target.
Let's say we have the color 0xFF1133
. This has a binary form of:
11111111 00010001 00110011
We can extract the value of just the blue color 0x0000FF
by using the bitwise AND (&
) operator to cancel out all non-blue bits. So:
0xFF1133 & 0x0000FF == 0x000033
This is because all the 0
bits in our pure blue color 0x0000FF
override any bits set in any of the other color positions, and any 0
bits in our 0xFF0033
color override any 1
bits in our pure blue.
11111111 00010001 00110011
& 00000000 00000000 11111111
⇣
00000000 00000000 00110011
This works great for our blue value, but for the others we have to take this one step further. If we were to perform this same bitwise AND on the pure green color 0x00FF00
then we will get what we want, except with a lot of trailing zeros.
11111111 00010001 00110011
& 00000000 11111111 00000000
⇣
00000000 00010001 00000000
In order to target that middle section of the resulting bitwise operation, we need to shift the bits to the right to get rid of the extra zeros. To do this, we can use the bitwise right shift operator >>
and push them eight places to the right.
00000000 00010001 00000000 >> 8
⇣
00000000 00000000 00010001
Bingo! Unity. With this in mind, we can translate it to Ruby and apply these lightning fast calculations to extract percentages of each RGB color value from a hexadecimal numeric:
# Given the folowing color
color = 0x6495ED
percentage_blue = (color & 0x0000FF) * 100 / 255 # => 92%
percentage_green = ((color & 0x00FF00) >> 8) * 100 / 255 # => 58%
percentage_red = ((color & 0xFF0000) >> 16) * 100 / 255 # => 39%
All Together Now
Since the duty cycle value is essential the percentage that the signal will be ON, we can just extract the RGB color percentages from a hexadecimal color value and set these to the duty cycle of each RGB pin of the LED.
Ok, ok, all this binary mumbo jumbo and all we get is a bunch of flashing colors!? Let's turn the dial all the way up to eleven and add a simple twist: have a button trigger a random color.
BOOM! We have made a game! Guess what color the LED will be when you press the button?
Special thank you to Calle Erlandsson's fantastic write-up on bitwise operations in Ruby.
Top comments (0)