Introduction: Using a Single RGB LED (or a LED Strip) to Show Information: Clock, Thermometer and a Weird Communicator

One of the first projects I did with an Arduino was a Christmas lights garland. In fact, it was made of a simple RGB LED strip cut in two, as Arduino has only 6 hardware PWM outputs. I didn’t want it to display trivial rainbows and random colors – after all, a Chinese controller that came with the strip was pretty capable of doing that – I wanted it to show something meaningful. Like time and outside temperature. As an RGB strip can show only one color at a time it is effectively a single RGB LED multiplied many times, so my task was basically to invent some way of showing information on a single RGB LED – hence the OnePixel working title.

I did it, and it worked, and in the year that followed I continued to hone the technique while building a lot of strange shining devices (that have nothing to do with Christmas). And I guess it is time to share this with the world – after all, a lot of people start their Arduino acquaintance with the Blink sketch and then try to improve it; and this is The Ultimate Blink experience.

Step 1: Before We Begin

What we will be doing here is making a single RGB LED display some meaningful information by showing a sequence of colors. First, we need to understand the possibilities and limitations of an RGB LED. I’ve explained some in my previous instructable dealing with the preference of the RGB model over the HSV with RGB LEDs, it may be useful for you to read it.

The following will be described in the next steps:

- an RGB LED clock in steps 2 and 4;

- an RGB LED thermometer in step 3;

- a weird RGB LED communicator in steps 5 and 6;

- some basics regarding RGB LED calibration in step 7.

To run the sketches provided in this instructable you’ll need:

- an Arduino

- an RGB LED (common anode preferred)

- three 220 Ohm resistors

- a pushbutton is recommended

- a clock module is highly recommended

- some wires and a breadboard.

Step 2: OnePixel Clock

Due to the nature of RGB LEDs you cannot produce any dark colors on them: there’s no such thing as a brown LED. You also will always want the richest, most saturated colors. So what we are left with here is the spectrum circle in uniform brightness, and we need to extract from it the maximum amount of unmistakably clear colors. These colors must be effortlessly and instantly told apart: what use will our clock be if it leaves room for doubt? I spent a lot of time experimenting, self-persuading and trying to squeeze more out of the LEDs than they can do, and finally had to concede: there are only six easily, absolutely and without doubt distinguishable RGB LED colors:

Red, Orange, Green, Cyan, Blue and Magenta.

Why orange and not yellow? Because yellow on a single LED looks too much like green and is not easily identifiable without reference. You may see it for yourself: write a simple sketch changing an RGB LED from red (255, 0, 0) to yellow (127, 127, 0) every second. You’ll see red and green colors.

While six is a good number for dealing with clocks, it was not reasonably sufficient for my needs. It is possible to squeeze something in the purple range, resulting in seven colors, but that won’t exactly help with a clock. So I decided to use an additional set of ten colors for the tasks where a slight error (confusing neighboring hues) is acceptable:

Red, Orange, Yellow, Green, Aquamarine, Skyblue, Blue, Purple, Fuchsia and White.

(the names are provisional, of course. Just nice words. In fact, it’s more like cyan-green, cyan-blue, blue-magenta, red-magenta, etc. See the table in the sketch below).

The white is on this list because it’s not definitive. As it is not a color, but, in fact, an absence of any hue, it leaves a lot to imagination and can be easily mistaken for something else (most notably, cyan). What’s more, as an RGB LED makes white out of combining three base colors, it in fact looks differently depending on a viewing angle: if you look at the LED from one point you’ll see more red, from another – more green etc.

The last important ‘color’ (or, more correctly, the lack thereof) is ‘black’. While it cannot convey any meaningful information (because it looks the same as a turned-off LED) it is very useful as a separator.

So I had two sets of colors. While initially I hoped to make an intuitive analog clock it was clear by that time that I’ll have to stick with digital. That is, with showing digits with colored codes. The two sets are, of course, ideal for showing minutes (or seconds): the six-color one can show tens, the ten-color one is good for ones (as it’s not a huge problem if you confuse the neighboring colors – you’ll just have a one-minute error). Just two blinks of an RGB LED.

What about hours? The same approach is not good here: firstly the 10-color set is not definitive enough, and secondly the display will be plain dull with the same tens colors repeating over and over. I was trying some solutions, but then looked at my wristwatch and it hit me: if the normal clock face can divide the number of hours in a day by two (showing 12 instead of 24) then why can’t I divide them further? After all, I’m making a garland, not an atomic clock. And, really, can one confuse 6 o’clock with midnight? So the convention was made: a ‘clock face’ of my OnePixel clock will be 6-hours, which means same colors for 12 and 6, 1 and 7, 2 and 8, etc. This convention proved to be very adequate: the only time I wasn’t able to read the exact time off the LED was on 1st of January and had more to do with the yesterdays party than anything (awoken, I wasn’t able to tell whether it’s dark still or already).

While it is possible to correlate digits and colors in any way, I prefer to do it according to the spectrum – no need to remember anything. See the charts above.

In the end the garland clock was showing time in dashes: long one for hours, two short ones for minutes. In the following year I filled my home with very different variations of the same device, most of them sporting more than one LED, but the principle remained (check the video). The only thing I’m not sure about nowadays is the white color for 9: I think I should make it a bit more pinkyish to avoid confusion with Cyan. It may have been better to insert white between two Cyan hues of the 10-color cycle, but now it’s too late for me, as I’m already pretty used to these clocks as they are.

Here’s a sketch that will allow you to see time on a single RGB LED in three different ways (modes switched by a button; the fourth mode will be explained in step 4). You’ll need an external clock module. If you don’t have one you may use the Time library to turn your Arduino into a time-keeping device (change the sketch accordingly). I would suggest you get the RTC module if you intend to dig into Arduino clocks – it’s really rather cheap and useful.

<p>#include <Wire.h><br>#define RED 3 // pins the RGB LED is connected to
#define GREEN 5
#define BLUE 6
#define BUTTON_PIN 7</p><p>int temperature;
byte second, minute, hour;
byte temperPos, prevPos, nextPos;
byte r, g, b;
byte mode = 0;</p><p>// sine wave array
const uint8_t lights[360]={
  0,   0,   0,   0,   0,   1,   1,   2, //8 
  2,   3,   4,   5,   6,   7,   8,   9, 
 11,  12,  13,  15,  17,  18,  20,  22, 
 24,  26,  28,  30,  32,  35,  37,  39, //32
 42,  44,  47,  49,  52,  55,  58,  60, 
 63,  66,  69,  72,  75,  78,  81,  85, //48
 88,  91,  94,  97, 101, 104, 107, 111, 
114, 117, 121, 124, 127, 131, 134, 137, //64
141, 144, 147, 150, 154, 157, 160, 163, 
167, 170, 173, 176, 179, 182, 185, 188, //96
191, 194, 197, 200, 202, 205, 208, 210, 
213, 215, 217, 220, 222, 224, 226, 229, 
231, 232, 234, 236, 238, 239, 241, 242, 
244, 245, 246, 248, 249, 250, 251, 251, 
252, 253, 253, 254, 254, 255, 255, 255, 
255, 255, 255, 255, 254, 254, 253, 253, 
252, 251, 251, 250, 249, 248, 246, 245, 
244, 242, 241, 239, 238, 236, 234, 232, 
231, 229, 226, 224, 222, 220, 217, 215, 
213, 210, 208, 205, 202, 200, 197, 194, 
191, 188, 185, 182, 179, 176, 173, 170, 
167, 163, 160, 157, 154, 150, 147, 144, 
141, 137, 134, 131, 127, 124, 121, 117, 
114, 111, 107, 104, 101,  97,  94,  91, 
 88,  85,  81,  78,  75,  72,  69,  66, 
 63,  60,  58,  55,  52,  49,  47,  44, 
 42,  39,  37,  35,  32,  30,  28,  26, 
 24,  22,  20,  18,  17,  15,  13,  12, 
 11,   9,   8,   7,   6,   5,   4,   3, 
  2,   2,   1,   1,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0, 
  0,   0,   0,   0,   0,   0,   0,   0};
  
// an array of pre-arranged RGB values
// for 10mm RGB LED. 
byte RGBready[13][3] ={
{255, 0, 0}, // hour=0, min=0
{229, 26, 0}, // min=1
{201, 54, 0}, // hour=1
{181, 74, 0}, // min=2
{0, 255, 0}, // hour=2, min=3
{0, 218, 36}, // min=4
{0, 174, 81}, // hour=3
{0, 99, 156}, // min=5
{0, 0, 255}, // hour=4, min=6
{74, 0, 181}, // min=7
{131, 0, 124}, // hour=5
{196, 0, 59}, // min=8
{95, 78, 81}, // min=9
};</p><p>// These arrays point to exact colors of hours and minutes in RGBready
// I use them because in my projects RGBready can hold more pre-arranged
// colors than just clock (like thermometer). And it's easier.
byte hourColor[6]={0,2,4,6,8,10};
byte minuteColor[10]={0,1,3,4,5,7,8,9,11,12};</p><p>void setup() {
Wire.begin();        
Serial.begin(9600);  
Serial.println("Starting");
pinMode(BUTTON_PIN, INPUT_PULLUP); // button connects to GND
}</p><p>void loop() {</p><p>// get and print time
getTime();
Serial.print("Time is ");
Serial.print(hour);
Serial.print(":");
Serial.print(minute);
Serial.print(":");
Serial.print(second);
Serial.println(".");
hour %= 6; // convert hours to 6-hour 'clock face'
//hour = minute%6;
//minute = second;</p><p>// to change modes you should press and hold the button
// (the reading happens between display cycles)
// I decided against ISR here to keep the sketch to the point
if (!digitalRead(BUTTON_PIN)) mode++;</p><p>mode %=4; // we have only three modes</p><p>switch (mode) {
  case 0: showTimeFades(); break;
  case 1: showTimeTransitions(); break;
  case 2: simpleMode(); break;
  case 3: analogClock(); break;
}
}</p><p>// the following two functions are used to get time from a DS3231 chip
// they were salvaged from Time library I think
// the Time library itself turns your Arduino into a clock, so if you
// don't have an RTC module, you should download it
// and change the getTime() function to work with it
void getTime ()
{
  // the DS3231 RTC chip address is 0x68
  Wire.beginTransmission(0x68); 
  Wire.write(byte(0));
  Wire.endTransmission();</p><p>  Wire.requestFrom(0x68, 3, 1); // request first three bytes, close connection
  second = bcdToDec (Wire.read());
  minute = bcdToDec (Wire.read());
  hour = bcdToDec(Wire.read());
  
// we don't need any further information like weekday, year, etc
// from the clock, so read only first 3 bytes. 
}</p><p>// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)  {
  return ( (val/16*10) + (val%16) );
}</p><p>// The OnePixel clock modes
// 1 - the mode I used on a garland
void showTimeFades()
{
  int delayVal = 4; // sets the overall display speed</p><p>  // fade-in fade-out hours color
  for (int k=0; k<241; k++)
  {
    r = (uint16_t)(RGBready[hourColor[hour]][0]*lights[k])>>8;
    g = (uint16_t)(RGBready[hourColor[hour]][1]*lights[k])>>8;
    b = (uint16_t)(RGBready[hourColor[hour]][2]*lights[k])>>8;
    setRGBpoint (0, r, g, b);
    delay(delayVal);
  }</p><p>  // fade-in fade-out minute-tens color
  for (int k=0; k<241; k++)
  {
    r = (uint16_t)(RGBready[hourColor[minute/10]][0]*lights[k])>>8;
    g = (uint16_t)(RGBready[hourColor[minute/10]][1]*lights[k])>>8;
    b = (uint16_t)(RGBready[hourColor[minute/10]][2]*lights[k])>>8;
    setRGBpoint (0, r, g, b);
    delay(delayVal>>1); // two times faster than hours
  }</p><p>  // fade-in fade-out minute-ones color
  for (int k=0; k<241; k++)
  {
    r = (uint16_t)(RGBready[minuteColor[minute%10]][0]*lights[k])>>8;
    g = (uint16_t)(RGBready[minuteColor[minute%10]][1]*lights[k])>>8;
    b = (uint16_t)(RGBready[minuteColor[minute%10]][2]*lights[k])>>8;
    setRGBpoint (0, r, g, b); 
    delay(delayVal>>1); // two times faster than hours
  }
}</p><p>// 2 - sine wave transitions between colors
void showTimeTransitions()
{
  int delayVal = 4; // set transitions speed
  
  // fade-in
  for (int k=0; k<121; k++)
  {
    r = (uint16_t)(RGBready[hourColor[hour]][0]*lights[k])>>8;
    g = (uint16_t)(RGBready[hourColor[hour]][1]*lights[k])>>8;
    b = (uint16_t)(RGBready[hourColor[hour]][2]*lights[k])>>8;
    setRGBpoint (0, r, g, b);
    delay(delayVal);
  }</p><p>  // transitions
  shiftColors(hourColor[hour], hourColor[minute/10], delayVal);
  
  delay(50); // briefly stop on the minute-tens value
  
  shiftColors(hourColor[minute/10], minuteColor[minute%10], delayVal);
  
  // fade-out
  for (int k=120; k<241; k++)
  {
    r = (uint16_t)(RGBready[minuteColor[minute%10]][0]*lights[k])>>8;
    g = (uint16_t)(RGBready[minuteColor[minute%10]][1]*lights[k])>>8;
    b = (uint16_t)(RGBready[minuteColor[minute%10]][2]*lights[k])>>8;
    setRGBpoint (0, r, g, b);
    delay(delayVal);
  }
}</p><p>// 3 - the simplest possible mode, may be useful as a start for something
void simpleMode()
{
  int delayVal = 333;
  r = RGBready[hourColor[hour]][0];
  g = RGBready[hourColor[hour]][1];
  b = RGBready[hourColor[hour]][2];
  setRGBpoint (0, r, g, b);
  delay(delayVal);</p><p>  r = RGBready[hourColor[minute/10]][0];
  g = RGBready[hourColor[minute/10]][1];
  b = RGBready[hourColor[minute/10]][2];
  setRGBpoint (0, r, g, b);
  delay(delayVal);</p><p>  r = RGBready[minuteColor[minute%10]][0];
  g = RGBready[minuteColor[minute%10]][1];
  b = RGBready[minuteColor[minute%10]][2];
  setRGBpoint (0, r, g, b);
  delay(delayVal);</p><p>  setRGBpoint (0, 0, 0, 0);
  delay(delayVal>>1);
}</p><p>void analogClock()
{
  int delayVal = 8;
  for (int k=0; k<52; k++)
  {
  r = (float)(RGBready[hourColor[hour]][0]*(60-minute) + RGBready[hourColor[(hour+1)%6]][0]*minute)/60;
  g = (float)(RGBready[hourColor[hour]][1]*(60-minute) + RGBready[hourColor[(hour+1)%6]][1]*minute)/60;
  b = (float)(RGBready[hourColor[hour]][2]*(60-minute) + RGBready[hourColor[(hour+1)%6]][2]*minute)/60;</p><p>  r = (uint16_t)(r*(lights[k]*2+30))>>8;
  g = (uint16_t)(g*(lights[k]*2+30))>>8;
  b = (uint16_t)(b*(lights[k]*2+30))>>8;
  setRGBpoint (0, r, g, b);
  delay(delayVal);
  }
  for (int k=51; k>0; k--)
  {
  r = (float)(RGBready[hourColor[hour]][0]*(60-minute) + RGBready[hourColor[(hour+1)%6]][0]*minute)/60;
  g = (float)(RGBready[hourColor[hour]][1]*(60-minute) + RGBready[hourColor[(hour+1)%6]][1]*minute)/60;
  b = (float)(RGBready[hourColor[hour]][2]*(60-minute) + RGBready[hourColor[(hour+1)%6]][2]*minute)/60;</p><p>  r = (uint16_t)(r*(lights[k]*2+30))>>8;
  g = (uint16_t)(g*(lights[k]*2+30))>>8;
  b = (uint16_t)(b*(lights[k]*2+30))>>8;
  setRGBpoint (0, r, g, b);
  delay(delayVal);
  }
  delay(delayVal*30);
}</p><p>// function for color shifting between
// positions in the RGBready array. This one uses the
// sine wave algorithm and lights[] pre-arranged array above
// delayVal sets the speed of transition
void shiftColors(byte cur, byte next, byte delayVal)
{
  uint16_t color[3], nextColor[3];
  long colorStep[3];
  
  showRGBcolor(cur); // start with clear first color
  // calculate steps in unsigned int format to avoid using floats
  for (byte k=0; k<3; k++) {
    color[k] = RGBready[cur][k]<<8;
    nextColor[k] = RGBready[next][k]<<8;
    colorStep[k]=((long)nextColor[k] - color[k])/255;
  }
  // set colors in 120 (360/3) steps, converting back to bytes
  for (byte k=0; k<120;k++)
    {
    setRGBpoint(0, (color[0]+colorStep[0]*lights[k])>>8,
                   (color[1]+colorStep[1]*lights[k])>>8,
                   (color[2]+colorStep[2]*lights[k])>>8);
    delay(delayVal);
    }
  showRGBcolor(next); // finish with clear second color
}</p><p>// two legacy functions to turn LEDs on.
// I use them here to maintain consistency between example sketches
  // both codes are for common anode LEDs. If you use common cathode ones,
  // remove the '255-' bits.
void setRGBpoint(uint8_t LED, uint8_t red, uint8_t green, uint8_t blue)
{
  analogWrite(RED, 255-red);
  analogWrite(GREEN, 255-green);
  analogWrite(BLUE, 255-blue);
}</p><p>void showRGBcolor(byte curLED)
{
  analogWrite(RED, 255-RGBready[curLED][0]);
  analogWrite(GREEN, 255-RGBready[curLED][1]);
  analogWrite(BLUE, 255-RGBready[curLED][2]);
}</p>

Notice the color table in the sketch? I’ll get back to it in the last step.

Step 3: OnePixel Thermometer

Note: I use Celsius temperatures in my projects. I’m not acquainted with the Fahrenheit system.

The second informative function of my garland was the thermometer. In fact, the thermometer itself was my first Arduino project, and it is still working nearby, showing outside temperature on a binary ‘display’. The garland was to request the data from it via I2C interface and display it in RGB.

It is possible to use the same approach as described above regarding minutes, that is, a digital one showing tens and ones. However, as the temperature can go in negative, this approach becomes not that intuitive. I also needed a different way to show temperature to avoid confusion, as both clock and thermometer were to work at the same time. So I used an ‘analog’ approach here.

The 'analog' approach means you don't have to convert seen colors into digits, you may just associate colors with how the weather feels. Like this (from cold to warm, for the winter):

Red: alarm, awfully cold, the car may not start today

Green: really cold, dress accordingly

Blue: cold, don’t forget gloves

Red: around freezing temp, expect slush, sleet possible

Green: winter mode off

And so forth. These base colors are tied to Celsius degrees (-30, -20, -10, 0, +10, see the chart) and change smoothly. So if you see, say, a cyan – it’s between green and blue, around -15°C; a purple hue means something around -5°C. The full spectrum range from red to red covers 30 degrees and then rolls over, that’s enough not to confuse readings (a 30°C overnight temperature drop or rise is pretty rare, and in any case you can look out of the window to get the basic idea of what’s going on).

While this system is pretty neat, when the wife asks for a temperature you have to reply in exact degrees C, not in colors. So I had to ‘rasterize’ the chart and assign specific colors to specific values. This resulted in a 2°C precision, which is OK for a casual project. See the second chart above. However, as discussed earlier, these colors are certainly not definitive and can be easily messed up, which in turn led to a ‘pendulum’ visualization system.

In ‘pendulum’ mode the RGB LED shows not only the current value, but it’s neighbors too, switching between the colors this way: current – previous – current – next – current – previous, etc. This provides the reference needed to nail down the exact current color as being, say, the red-orange because it’s neighbors are red and orange. It also makes a garland look better by providing some animation.

The sketch showing a ‘pendulum-analog’ method is below. Note: in this sketch I get the temperature from the thermometer Arduino device via I2C; change the getTemp() function according to your setup. The sine-wave color change algorithm is from my previous instructable.

#include <Wire.h>

#define RED 3 // pins the RGB LED is connected to #define GREEN 5 #define BLUE 6

int temperature; byte temperPos, prevPos, nextPos;

// sine wave array const uint8_t lights[360]={ 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 17, 18, 20, 22, 24, 26, 28, 30, 32, 35, 37, 39, 42, 44, 47, 49, 52, 55, 58, 60, 63, 66, 69, 72, 75, 78, 81, 85, 88, 91, 94, 97, 101, 104, 107, 111, 114, 117, 121, 124, 127, 131, 134, 137, 141, 144, 147, 150, 154, 157, 160, 163, 167, 170, 173, 176, 179, 182, 185, 188, 191, 194, 197, 200, 202, 205, 208, 210, 213, 215, 217, 220, 222, 224, 226, 229, 231, 232, 234, 236, 238, 239, 241, 242, 244, 245, 246, 248, 249, 250, 251, 251, 252, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 253, 253, 252, 251, 251, 250, 249, 248, 246, 245, 244, 242, 241, 239, 238, 236, 234, 232, 231, 229, 226, 224, 222, 220, 217, 215, 213, 210, 208, 205, 202, 200, 197, 194, 191, 188, 185, 182, 179, 176, 173, 170, 167, 163, 160, 157, 154, 150, 147, 144, 141, 137, 134, 131, 127, 124, 121, 117, 114, 111, 107, 104, 101, 97, 94, 91, 88, 85, 81, 78, 75, 72, 69, 66, 63, 60, 58, 55, 52, 49, 47, 44, 42, 39, 37, 35, 32, 30, 28, 26, 24, 22, 20, 18, 17, 15, 13, 12, 11, 9, 8, 7, 6, 5, 4, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // an array of pre-arranged RGB values // for 10mm RGB LED. byte RGBready[15][3] ={ {255, 0, 0}, // R0 {208, 47, 0}, // R1 {169, 86, 0}, // R2 {130, 125, 0}, // R3 {103, 151, 0}, // R4 {0, 255, 0}, // G0 {0, 248, 7}, // G1 {0, 217, 37}, // G2 {0, 171, 84}, // G3 {0, 67, 188}, // G4 {0, 0, 255}, // B0 {103, 0, 152}, // B1 {127, 0, 127}, // B2 {169, 0, 86}, // B3 {224, 0, 31}, // B4 };

void setup() { Wire.begin(); Serial.begin(9600); }

void loop(){ temperature = getReadings(); // request temperature // calculate the position of temperature meter // considering 2 degree accuracy temperPos = ((temperature + 60)/2)%15;

// set values for previous and next positions // just to make the code clearer prevPos = (temperPos+14)%15; nextPos = (temperPos+1)%15; // print out RGBready position - for debugging //Serial.println(temperPos);

// shift colors in the most obvious way shiftColors(temperPos, prevPos, 10); shiftColors(prevPos, temperPos, 10); shiftColors(temperPos, nextPos, 10); shiftColors(nextPos, temperPos, 10);

}

// request temperature: I use a second Arduino with a thermometer on it. // Change this function according to your setup (1-wire, I2C, whatever). // the result must end up in the form of an integer. int getReadings() { int temper; Wire.requestFrom(15, 1); // request one byte from device 15 while(Wire.available()) temper = Wire.read() - 60; // to avoid negatives the sending device Serial.println(temper); // adds 60 to its reading return temper; }

// function for color shifting between // positions in the RGBready array. This one uses the // sine wave algorithm and lights[] pre-arranged array above // delayVal sets the speed of transition void shiftColors(byte cur, byte next, byte delayVal) { uint16_t color[3], nextColor[3]; long colorStep[3]; showRGBcolor(cur); // start with clear first color // calculate steps in unsigned int format to avoid using floats for (byte k=0; k<3; k++) { color[k] = RGBready[cur][k]<<8; nextColor[k] = RGBready[next][k]<<8; colorStep[k]=((long)nextColor[k] - color[k])/255; } // set colors in 120 (360/3) steps, converting back to bytes for (byte k=0; k<120;k++) { setRGBpoint(0, (color[0]+colorStep[0]*lights[k])>>8, (color[1]+colorStep[1]*lights[k])>>8, (color[2]+colorStep[2]*lights[k])>>8); delay(delayVal); } showRGBcolor(next); // finish with clear second color }

// two legacy functions to turn LEDs on. // I use them here to maintain consistency between sketches // both codes are for common anode LEDs. If you use common cathode ones, // remove the '255-' bits. void setRGBpoint(uint8_t LED, uint8_t red, uint8_t green, uint8_t blue) { analogWrite(RED, 255-red); analogWrite(GREEN, 255-green); analogWrite(BLUE, 255-blue); }

void showRGBcolor(byte curLED) { analogWrite(RED, 255-RGBready[curLED][0]); analogWrite(GREEN, 255-RGBready[curLED][1]); analogWrite(BLUE, 255-RGBready[curLED][2]); }

Step 4: Analog OnePixel Clock

After the garland was made and used, I moved on with LED experimentation, acquainted myself with LED drivers, started experimenting with home-made PCBs. One of the first such PCBs included two LED drivers (DM631) and was able to host ten RGB LEDs. A bit later I found a small wooden coffer in one of the shops and decided to marry those two. In fact, I wanted to make some unusual stuff as a present for a friend of mine. In any case, the end result was a clumsily soldered home-made prototype of the CofferClock (the device I’m so proud of now that even am trying to sell the newer versions). As this was an early prototype, it required some real-life testing, so I took it with me on a holiday to use as a bedside clock. And there I found that it lacked an analog clock mode.

Example: you wake sometime at night. You don’t need to know the exact time, you just want to know whether it’s night or early morning and how much sleeping time you have left. With a regular OnePixel clock, especially when you’re more or less new to it, it takes some time to understand what it is showing (which color is hours, which is minutes). A single color showing approximate time would be better, so I needed an additional ‘nightlight’ mode.

The rest it easy: I took a basic spectrum circle from red to red, applied my 6-hour-clockface convention and dropped in some brightness-based animations for extra coolness. Colors change seamlessly over the hours, so very red-ish pink is something like 5:50, orange is closer to 7, cyan generally means three in the morning, etc.

Here’s the function from the sketch in step 2 – it’s the fourth mode there.

void analogClock()
{ int delayVal = 8; for (int k=0; k<52; k++) { r = (float)(RGBready[hourColor[hour]][0]*(60-minute) + RGBready[hourColor[(hour+1)%6]][0]*minute)/60; g = (float)(RGBready[hourColor[hour]][1]*(60-minute) + RGBready[hourColor[(hour+1)%6]][1]*minute)/60; b = (float)(RGBready[hourColor[hour]][2]*(60-minute) + RGBready[hourColor[(hour+1)%6]][2]*minute)/60;

r = (uint16_t)(r*(lights[k]*2+30))>>8; g = (uint16_t)(g*(lights[k]*2+30))>>8; b = (uint16_t)(b*(lights[k]*2+30))>>8; setRGBpoint (0, r, g, b); delay(delayVal); } for (int k=51; k>0; k--) { r = (float)(RGBready[hourColor[hour]][0]*(60-minute) + RGBready[hourColor[(hour+1)%6]][0]*minute)/60; g = (float)(RGBready[hourColor[hour]][1]*(60-minute) + RGBready[hourColor[(hour+1)%6]][1]*minute)/60; b = (float)(RGBready[hourColor[hour]][2]*(60-minute) + RGBready[hourColor[(hour+1)%6]][2]*minute)/60;

r = (uint16_t)(r*(lights[k]*2+30))>>8; g = (uint16_t)(g*(lights[k]*2+30))>>8; b = (uint16_t)(b*(lights[k]*2+30))>>8; setRGBpoint (0, r, g, b); delay(delayVal); } delay(delayVal*30); }

Step 5: Weird 6-bit Communicator

Someone, seeing my clock-garland for the first time, noted that it could be used as a secret communication device. Well, why not? This is less useful than the clock or the thermometer, but may happen to be a good and fun toy for someone with kids.

It is obvious that an RGB LED, physically consisting of three LEDs with separate cathodes (or anodes), is in fact a 3-bit device. By supplying its legs with 0 or 1 values you are giving it three bits of information and it is showing them in colored code. If we consider red as an LSB and blue as an MSB (or BGR = B000), we can actually assign visible colors to numbers 0 – 7: black for 0, red for 1 (B001), green for 2 (B010), orange (R+G) for 3 (B011), blue for 4 (B100), magenta (R+B) for 5 (B101), cyan (G+B) for 6 (B110) and white (R+G+B) for 7 (B111). The problem of this approach hides in the ‘black’ color (zero), as it becomes informative and cannot be used as a separator. With only three bits we won’t be able to send anything meaningful in just one color, and we’ll actually have a problem sending zero, as it will look exactly the same as a turned-off LED and no one will notice that we’re trying to communicate with them.

Most digital communication protocols use more than one channel just for that: to show the receiver that information is actually being transmitted, and it should consider voltage absence as a zero, not nothing. This takes form of a latch signal, a clock signal or some other carrier signal present on an additional wire. In our case we could have added a single color LED just for this, but as we’re using only one RGB LED by our OnePixel convention, we cannot. So the carrier signal must be transmitted by the RGB LED and we have to dedicate one of the channels to it, leaving us with only two bits of information per single color. So, if we use red as a carrier, we will have red for 0, orange for 1 (B01), magenta for 2 (B10) and white for 3 (B11).

Two bits are not enough, but we can stack them together in sequences. What’s more, we can use different carrier colors. For example, if we use red as a carrier for the first two bits, green for the middle two bits and blue for the last two bits we’ll get a nice three-color 6-bit sequence. We can use ‘black’ separators between such sequences to transmit the information clearly.

Technically we are not limited to 6 bits: we can add a fourth color, using red again as the carrier, and transmit full bytes. The problem here is that there are two colors in the middle of sequence, and if they are the same (like, say, in B00111100 – red, white, white, red or B00011000 – red, cyan, cyan, red), it can be hard to decode them. Thankfully, 6 bits are pretty enough for transmitting meaningful messages.

Check the ASCII table. The first 32 codes are for control characters, we don’t need them. Then go punctuation marks, numbers and the alphabet in upper case, and all these symbols fit into 6 bits (64 different values). If we take the ASCII table from space (32) to _ (95) we get everything we need to communicate in 6 bits. As a side note, early personal computers also ditched lower-case characters to reduce costs on character generation chips.

Ok, here’s the Secret Communication Sketch. It takes anything you type in the Serial Monitor window (changing lower-case symbols to upper case) and shows it on a single RGB LED. Note that Serial Monitor must be set up to 115200 baud. It also prints the character table at startup for easier decoding (you may also check the cheat sheet above, it shows the first color in vertical arrow, the second one in horizontal fill and the third one as the dot).

#define RED 3 // pins the RGB LED is connected to
#define GREEN 5 #define BLUE 6

// the table of colors. This one was calibrated for a 10mm RGB LED uint8_t RGBready[13][3] ={ {255, 0, 0}, // 00xxxx {128, 127, 0}, // 01xxxx {206, 0, 49}, // 10xxxx //{110, 90, 54}, // 11xxxx white {26, 155, 74}, // 11xxxx cyan {0, 255, 0}, // xx00xx {179, 75, 0}, // xx01xx {0, 168, 87}, // xx10xx //{90, 110, 54}, // xx11xx white {158, 10, 87}, // xx11xx pink {0, 0, 165}, // xxxx00 {206, 0, 49}, // xxxx01 {0, 212, 42}, // xxxx10 //{90, 90, 74}, // xxxx11 white {139, 105, 10}, // xxxx11 yellow {0,0,0} // 'black' };

uint8_t message[100], count; // char array and counter

// below are durations for colors and the 'black' pause between, in ms uint16_t signalDuration=400, pauseBetween=100;

void setup() { Serial.begin(115200);

// print a character table Serial.println("Character table"); for (uint8_t k=0; k<16; k++) { if (k<10) Serial.print("0"); Serial.print(k); Serial.print(": "); Serial.write(k+32); Serial.print(" "); Serial.print(k+16); Serial.print(": "); Serial.write(k+16+32); Serial.print(" "); Serial.print(k+32); Serial.print(": "); Serial.write(k+32+32); Serial.print(" "); Serial.print(k+48); Serial.print(": "); Serial.write(k+48+32); Serial.println(); } }

void loop() {

count=0; // drop the character counter

while (Serial.available()) // read string if available, convert it to our table { uint8_t incomingChar = Serial.read (); // normal characters if (incomingChar>31 && incomingChar<96) {message[count] = incomingChar; count++;} // lower case characters must be converted to upper case else if (incomingChar>96 && incomingChar<123) {message[count] = incomingChar - 32; count++;} }

// if there is incoming string, show it on the LED if (count>0){ for (uint8_t k=0; k

// the function to color-code and show info on the LED void sendRGBmessage(uint8_t letter) { letter = letter - 32; // convert from ASCII to our format showRGBcolor(letter>>4); // upper 2 bits delay(signalDuration); showRGBcolor(4+((letter>>2)&3)); // middle 2 bits delay(signalDuration); showRGBcolor(8+(letter&3)); // lower 2 bits delay(signalDuration); showRGBcolor(12); // pause between characters delay(pauseBetween); }

// the function to do the actual LED turning on // for common anode LED; in case of common cathode remove '255-' chunks void showRGBcolor(byte curLED) { analogWrite(RED, 255-RGBready[curLED][0]); analogWrite(GREEN, 255-RGBready[curLED][1]); analogWrite(BLUE, 255-RGBready[curLED][2]); }

Step 6: Improving the Weird Communicator

If you play around with the Secret Communicator for some time you’ll note some patterns. For example, all numbers start with yellow color and all letters start with either magenta or white. It may look like a good idea to correct the character table a bit to make these patterns even more intuitive. This is what I did – see the new table above. I even decided to dedicate all the yellow codes to numbers, making it possible to transmit HEX digits. This turned out to be a bit harder than I initially thought, as the ‘letter-digits’ of the ABCDEF range are repeated as letters, so I had to make a special communication mode for them – the HEX numbers are transmitted with a long yellow dash followed by two-color digits without the first two bits for speed. If you type HEXes starting with ‘0x’ in the Serial Monitor they will be interpreted as HEXes and transmitted accordingly.

I won’t go into further details regarding this sketch as it’s not the point of this instructable, hopefully you will be able to use and edit the sketch according to your needs. Here it is:

#define RED 3 // pins the RGB LED is connected to
#define GREEN 5 #define BLUE 6

// the table of colors. This one was calibrated for a 10mm RGB LED uint8_t RGBready[13][3] ={ {255, 0, 0}, // 00xxxx {128, 127, 0}, // 01xxxx {206, 0, 49}, // 10xxxx {110, 90, 54}, // 11xxxx white //{26, 155, 74}, // 11xxxx cyan {0, 255, 0}, // xx00xx {179, 75, 0}, // xx01xx {0, 168, 87}, // xx10xx {90, 110, 54}, // xx11xx white //{158, 10, 87}, // xx11xx pink {0, 0, 165}, // xxxx00 {206, 0, 49}, // xxxx01 {0, 212, 42}, // xxxx10 {90, 90, 74}, // xxxx11 white //{139, 105, 10}, // xxxx11 yellow {0,0,0} // 'black' };

// the Secret Code table, all values are ASCII codes // this one is not strictly necessary, as it is used only // to fill substitution table during setup, but it is // easier to edit and understand uint8_t secretCode[64]={ 32, 33, 63, 46, 44, 59, 58, 45, // !?.,;:- 40, 41, 34, 39, 43, 42, 61, 47, // ()"'+*=/ 48, 49, 50, 51, 52, 53, 54, 55, // 01234567 56, 57, 65, 66, 67, 68, 69, 70, // 89ABCDEF (HEX digits) 64, 65, 66, 67, 68, 69, 70, 71, // @ABCDEFG 72, 73, 74, 75, 76, 77, 78, 79, // HIJKLMNO 80, 81, 82, 83, 84, 85, 86, 87, // PQRSTUVW 88, 89, 90, 35, 36, 37, 38, 95 // XYZ#$%&_ };

uint8_t substitutionTable[64], HEXTable[16];

uint8_t message[64], count; // char array (64 bytes max) and counter

// below are durations for colors and the 'black' pause between, in ms uint16_t signalDuration=400, pauseBetween=100;

void setup() { Serial.begin(115200); // speed must be high, see the Serial.available comment below

// fill substitution table for (uint8_t k=0; k<64; k++) { substitutionTable[secretCode[k]-32] = k; }

// fill HEX codes because they are also present in letters for (uint8_t k=16; k<32; k++) { HEXTable[secretCode[k]-48] = k; }

// print a character table Serial.println("Character table"); for (uint8_t k=0; k<16; k++) { if (k<10) Serial.print("0"); Serial.print(k); Serial.print(": "); Serial.write(secretCode[k]); Serial.print(" "); Serial.print(k+16); Serial.print(": "); Serial.write(secretCode[k+16]); Serial.print(" "); Serial.print(k+32); Serial.print(": "); Serial.write(secretCode[k+32]); Serial.print(" "); Serial.print(k+48); Serial.print(": "); Serial.write(secretCode[k+48]); Serial.println(); } }

void loop() {

count=0; // drop the character counter

// the weird stuff below is the solution to a thing with Serial.available: // it needs time to count all the bytes it got, but it starts reporting results // before all of them are in. It's ok in the previous sketch, // but here it can botch the HEX recognition routine. // The call to S.a and the delay help it collect its wits // (the port speed change is important too) while (!Serial.available()); delay(10);

while (Serial.available()) // read string if available, convert it to our table { int incomingChar = Serial.read (); // normal characters if (incomingChar>31 && incomingChar<96) {message[count] = incomingChar; count++;} // lower case characters must be converted to upper case else if (incomingChar>96 && incomingChar<123) {message[count] = incomingChar - 32; count++;} }

// if there is incoming string, show it on the LED if (count>0){ // check if we've got a HEX number if (message[0] == 48 && message[1] == 88) sendHEXmessage(); // else send normally else for (uint8_t k=0; k

// the function to color-code and show info on the LED void sendRGBmessage(uint8_t letter) { letter = substitutionTable[letter-32]; // convert from ASCII to our format showRGBcolor(letter>>4); // upper 2 bits delay(signalDuration); showRGBcolor(4+((letter>>2)&3)); // middle 2 bits delay(signalDuration); showRGBcolor(8+(letter&3)); // lower 2 bits delay(signalDuration); showRGBcolor(12); // pause between characters delay(pauseBetween); }

// the function to send HEX numbers void sendHEXmessage() { showRGBcolor(1); // show yellow to signify the start of a HEX sequence delay(signalDuration<<1); // double pause showRGBcolor(12); // pause between characters delay(pauseBetween); for (uint8_t k=2; k>2)&3)); // middle 2 bits delay(signalDuration); showRGBcolor(8+(letter&3)); // lower 2 bits delay(signalDuration); showRGBcolor(12); // pause between characters delay(pauseBetween); } }

// the function to do the actual LED turning on // for common anode LED; in case of common cathode remove '255-' chunks void showRGBcolor(byte curLED) { analogWrite(RED, 255-RGBready[curLED][0]); analogWrite(GREEN, 255-RGBready[curLED][1]); analogWrite(BLUE, 255-RGBready[curLED][2]); }

Step 7: Calibrate Your LEDs!

You may have noticed the RGBready[][] arrays in my sketches; they hold all the preset values for the colors I use in clocks and other stuff. Why not a simple equation like yellow = 1/2 red + 1/2 green (or 127, 127, 0 to be precise)? Because RGB LEDs are not color-calibrated in any way and if you want them to display exact colors you have to hand-pick the values.

The video above shows five LEDs of different sizes being fed exactly the same values. Even on video you can see the difference in their behavior; in real life it’s much more pronounced – it seems like the big 10mm LEDs are actually acting on a different time scale than their smaller cousins. They are not, they just display blue totally differently which leads to different transitions between the colors, most notably between blue and red.

Blue, in fact, is the most problematic color on RGB LEDs: sometimes it’s deep and dark, sometimes it looks almost like cyan. Green can also change noticeably from yellowish warmer hue on one LED to cool bluish one on another. Green is also almost always insanely bright and tends to ‘swallow’ neighboring colors – so I usually tone it down. Red is the most reliable LED of an RGB LED: again, usually. It also is the weakest, and not because of its different Amps consumption, but due to the way our eyes perceive red color.

In any case, the only way to make color scheme as close to ideal as possible is adjusting the color values by hand. I keep a bunch of these RGBready[][] arrays for different LEDs, although I usually still have to make some adjustments for individual projects.

I actually intended to include a sketch for a simple LED-calibrating device here, but it turned out to be more complex than I thought: three potentiometers and an Arduino are not enough. So I’ll keep it for another instructable that I will publish soon. Until then – thanks for your time, comments and suggestions welcome!