top of page

COLORCLOCK

What time is it?

Amber o'clock-ish (?)

I was cleaning up my code of magic numbers and added a few #define statements for the frequency values. But after uploading the program to the board I didn't understand why the the colors on the light were not changing at the correct rate. So I added a couple serial output debug prints saw that my frequency was not set properly.


Code

// Time constants
#define SECONDS_PER_MIN   60.0
#define MINUTES_PER_HOUR  60.0
#define SECONDS_PER_HOUR  SECONDS_PER_MIN * MINUTES_PER_HOUR 

// Cycle time for ColorClock object 0
// Should equal 0.00333____
#define CC0_CYCLE_TIME_HOURS 12.0 / SECONDS_PER_HOUR

//...

Serial.print("SECONDS_PER_HOUR:     ");
Serial.println(SECONDS_PER_HOUR);
Serial.print("CC0_CYCLE_TIME_HOURS: ");
Serial.println(CC0_CYCLE_TIME_HOURS);

Serial Output

I was expecting CC0_CYCLE_TIME_HOURS to print as 0.00333 or even 0.00. Why was it printing 12.00??


CC0_CYCLE_TIME_HOURS: 12.00
SECONDS_PER_HOUR:     3600.00

I figured out that it had something to do with my #define statement. I stared at the code for way too long before deciding to do away with the SECONDS_PER_HOUR constant and just hard code the values directly:


#define CC0_CYCLE_TIME_HOURS 12.0 / 3600.00

And this worked, so whatever. I just figured that the Arduino compiler is weird and doesn't treat preprocessor directives the same as gcc. I started writing this post to complain about the Arduino compiler, so to illustrate my point, I coded up the following simple program to compile on my desktop.


Code

#include <stdio.h>

#define SECONDS_PER_MIN   60.0
#define MINUTES_PER_HOUR  60.0
#define SECONDS_PER_HOUR  SECONDS_PER_MIN * MINUTES_PER_HOUR 

#define CC0_CYCLE_TIME_HOURS 12.0 / SECONDS_PER_HOUR

int main()
{
  printf("\nSECONDS_PER_HOUR:     %f", SECONDS_PER_HOUR);
  printf("\nCC0_CYCLE_TIME_HOURS: %f", CC0_CYCLE_TIME_HOURS);

  return 0;
}

But to my surprise, the console output displayed the same value as the Arduino serial out!


Console Output

SECONDS_PER_HOUR:     3600.000000
CC0_CYCLE_TIME_HOURS: 12.000000

The Problem

I stared at the code again and realized that the lack of parenthesis in the definition for SECONDS_PER_HOUR was the source of my problem. The #define macro is a simple find and replace, so what the compiler saw was:


#define SECONDS_PER_MIN   60.0
#define MINUTES_PER_HOUR  60.0

// The compiler sees:
// #define SECONDS_PER_HOUR  60.0 * 60.0
#define SECONDS_PER_HOUR  SECONDS_PER_MIN * MINUTES_PER_HOUR 

// The compiler sees:
// #define CC0_CYCLE_TIME_HOURS 12.0 / 60.0 * 60.0
#define CC0_CYCLE_TIME_HOURS 12.0 / SECONDS_PER_HOUR

The compiler was basically seeing 12 / 60  60 instead of 12 / (60  60). And because of order of operations, CC0_CYCLE_TIME_HOURS was indeed 12, not the 0.003333 that I was hoping for 🤦‍♀️


Lesson learned 😳

As a child, I could spend entire days engrossed in puzzles. My favorites were the Mind Benders series logic puzzles, where a set of clues were provided to determine matches with limited information, such as figuring out which child owned which pet. I found immense satisfaction in solving these problems and took comfort in the certainty that there was always one correct solution. My chosen career path reflects this passion for problem-solving. Today, the puzzles I tackle are far more complex, with numerous possible solutions, each varying in its degree of optimality.


The Circuit Board

My big puzzle for the week has been how to lay out the final circuit board. There wasn't enough time to have a board printed, so I'll be wiring and soldering everything manually. For the second version of ColorClock, I would like to design a printed circuit board (PCB) to simplify the soldering effort. All integrated component boards, e.g. level shifter, Arduino board, will be attached to the board with female headers, so if a component dies for some reason it can easily be swapped out without re-soldering.


I decided to go with a board that is connected much like a solderless prototyping breadboard, where each column of holes are electrically connected. This does limit the possible orientation of the modules, but it will make the final wiring a little easier given that it can be laid out nearly exactly the same as the solderless prototype.


This photo shows the underside of one of the ElectroCookie breadboards. The project will have two boards side-by-side to accommodate for all of the control electronics. The stand offs in the middle of the board are to raise it up off the bottom of the electronics enclosure.


🌈


This diagram illustrates the layout of the board, and is mostly to scale. The groups of wires extending beyond the breadboard's borders will be routed to external electronics, such as the participant buttons, power input, and light display. Although the wires are shown overlapping the modules in the diagram, they will actually be routed underneath the modules, as the modules will be elevated using female headers.


🌈


This diagram illustrates the soldering layout primarily for the wires and female headers.


The Box

The control panel enclosure will house the participant-facing buttons in addition to the control electronics for the installation, i.e. the breadboard discussed earlier in this post. I was planning on modeling and 3D printing the enclosure, but now I am contemplating making it out of wood and simply printing an enclosure for the breadboard that will sit inside the box. With regards to the enclosure as a whole, there are a few mechanical factors to consider, like how it will be mounted and accessed from the outside in case of malfunction.


The participant control panel will be located beneath the light display, which is mounted on a 45-degree wooden panel. The enclosure is designed as a diagonally cut rectangular prism to position the buttons vertically for easy access.


This is the initial 3D model I was planning on printing. The triangular holes in the front are for the arcade buttons that the participants can use to modify one of the LED strips. With this design, there is no way to access the enclosed electronics without unscrewing the cover from the wooden panel that it would be mounted to.


🌈


The little nubs at the bottom of the box are to stabilize the stand offs used to elevate the circuit board off the bottom of the box.


The Structure

My partner, Bo, has been working hard on the structure, experiencing a multitude of challenges, and has practically rebuilt the frame, piece by piece. Home Depot has become our second home 😅 I have such immense respect for his tenacity in constructing a sturdy frame to bring my vision to fruition.


We each spent most of the day working on our respective parts of ColorClock. I was mostly CAD-ing in the AC while he was sweating in the garage playing with power tools.


🌈


This is Bo's exasperated "I can't wait to burn this thing" look. I love him 😘


🌈


Me, for scale.


TODOs For This Week

  • Solder circuit board

  • Model circuit board enclosure

  • Modify software to output lower power at night


😬

It looks like using a multiplexer as a PWM expander is not going to work. As previously discussed, in order to wire up three RGB LED strips to change colors at different frequencies, there is need for 12 analog output pins, one each for red, green and blue on each of the RGB LED strips. For the Arduino that we have selected, this will completely max out the number of analog output pins I have.


A couple months ago I tried to use a constant current I/O expander to write to PWM pins, but my tests did not yield favorable results. I did some googling and found that some developers will use a multiplexer as a way to increase the number of I/O pins to write to (or read from). I found the CD74HC4067 part, which is a 16-1 mux (with 4 control signals) which would provide ample outputs for the number of analog signals required by ColorClock.


Back to Computer Engineering 101

To review some digital logic, a multiplexer is basically a switch that can select from many inputs. When I used to teach, I compared it to a vending machine. There are many inputs (e.g. chips, granola, gum, candy) and one output (your selection). We type in a code on the keypad to select what kind of snack we want.

The snacks are the selection of inputs and the dispensing slot is the output. The keypad acts as the set of control signals used to select which input should be connected to the output.


Mux for PWM Output? Wishful Thinking 🥺

We can flip this diagram 180 and instead of having many inputs to one output, we can have one input to many outputs. If we change the selection fast enough, we can write to all of the outputs (allegedly) virtually simultaneously.

The idea here is to have one common signal pin (with the PWM value) that can write to one of many outputs (RGB LED terminals). You can switch the control lines to change the output, i.e. channel, of the mux and do an analogWrite for each channel.


So we can write the PWM value for a red pin, change the channel, write to the green pin, change the channel, write to the blue output, etc. My thought was because our eye's persistence of vision, we wouldn't be able to detect any flickering.


Buuuuuuut... that wasn't the case 😑 My tests showed significant flicker (even only writing to two channels) and the colors just didn't display properly. The videos below show the results of my tests.


Test Results

In all of the tests described below, I am trying to display magenta on my RGB LED:

void setup()
{
    //____________________________________________
    // Common pin that all values are written to
    //____________________________________________
    pinMode(common_pin, OUTPUT);

    //____________________________________________
    // Display the color magenta,
    // i.e. max values for red and blue, no green
    //____________________________________________
    color = RgbColor(0xFF00FF);
    //____________________________________________
}

Test 1: Perform analogWrite and Channel Select Sequentially

void loop()
{
    //____________________________________________
    // NOTES:
    // 1) `Rgb::RED`, `Rgb::GRN`, `Rgb::BLU`
    //    are values from an enumeration
    // 2) muxxy is the multiplexer object
    //____________________________________________
    analogWrite(common_pin, color.rgb_[Rgb::RED]);
    muxxy.channel(0);
    analogWrite(common_pin, color.rgb_[Rgb::GRN]);
    muxxy.channel(1);
    analogWrite(common_pin, color.rgb_[Rgb::BLU]);
    muxxy.channel(2);
}

The light should be displaying a magenta color, i.e. equal voltage on the red and blue pins, and no voltage on the green pin. Instead, the light looks white, which equates to equal voltage on each of the red, green and blue pins.


Test 2: Set Channel Before analogWrite

    muxxy.channel(0);
    analogWrite(common_pin, color.rgb_[Rgb::RED]);
    muxxy.channel(1);
    analogWrite(common_pin, color.rgb_[Rgb::GRN]);
    muxxy.channel(2);
    analogWrite(common_pin, color.rgb_[Rgb::BLU]);

There is no significant different between selecting the channel before the analog write.


Test 3: Only Write to Red Channel

    muxxy.channel(0);
    analogWrite(common_pin, color.rgb_[Rgb::RED]);

Only writing to one channel displays the color properly. This test was performed just to ensure it was actually writing the value.


Test 4: Only Write to Red and Green Channels

    muxxy.channel(0);
    analogWrite(common_pin, color.rgb_[Rgb::RED]);
    muxxy.channel(1);
    analogWrite(common_pin, color.rgb_[Rgb::GRN]);

    Debug::print_labeled_int("green val: ", color.rgb_[Rgb::GRN]);

There is no green in magenta, so this test should only display red on the LED. Instead, we are seeing green flickering, and not any red at all. I also output a serial print for the value of green to ensure that green was indeed set to zero, and this was true.


Test 5: Reset Output to 0

    muxxy.channel(0);
    analogWrite(common_pin, color.rgb_[Rgb::RED]);
    analogWrite(common_pin, 0);
    muxxy.channel(1);
    analogWrite(common_pin, color.rgb_[Rgb::GRN]);
    analogWrite(common_pin, 0);

Resetting the output pin to zero before switching channels just makes it look more choppy. Green is still displayed, though it shouldn't be.


We Can't Have Everything

So... I'm sticking to the PWM (analog) out pins from the Arduino for now. I also wanted to have a variable output for the light-up arcade buttons on the participant control panel so the brightness of the buttons would change as a result of participant interaction. I won't be able to do this because I have no free analog output pins 😒


In addition to the outputs for the light display, I also need digital input pins from the participant and programmer control panels (the programmer control panel will be hidden from the audience and is used for time set control). So I will proceed to test the previous I/O expander I used (Adafruit AW9523) for digital input and hope it works for this purpose.


Hindsight is 20/20

In retrospect, I really should have just selected individually addressable LED strips for the light display. This would have eliminated all need for analog output because I would have been able to control the lights with a simple I2C line. But the code was already written assuming PWM outputs, and I didn't want to write another interfacing layer. But with all the extra code I've had to write to test these additional modules, an interface layer to I2C would have been trivial.


But... we're here now. Initially, I was only anticipating one LED strip, a color clock that would cycle over 24 hours, so I would have only needed three PWM pins for the light display. But scope creep happened (for good reason - this design is way more interesting) and now we have four LED strips, all cycling through the spectrum at different frequencies.


There's One Win...

In other good news, I wrote the code to modify colors based on participant input, so yay!

Alright... now I'm back to testing the original I/O expander, this time to take digital input 😵‍💫

bottom of page