top of page

COLORCLOCK

What time is it?

Amber o'clock-ish (?)

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 😵‍💫

IO Expander Analog Write Fails Me

Although the code for the IO expander compiles successfully, it doesn't produce the expected variable output voltage. The analogWrite function accepts values from 0 to 255 (0xFF) and normally setting it to 128 would result in half the output voltage compared to setting it to 255. However, I discovered that the output voltage remained the same whether the pin was set to 128 or 255.


In a previous test, I connected an LED and cycled through output values from 0 to 255, observing the LED brightening. But the differences in output voltage didn't scale as anticipated; there might be a variation up to a certain point, beyond which the voltage levels off.


// pin_num0 and pin_num1 produce the same output voltage
analogWrite(pin_num0, 128);
analogWrite(pin_num1, 255);

The digitalWrite function still works but I haven't the digitalRead yet. If digitalRead works, we might use this IO expander for the user control panel input buttons. But my confidence level in this piece of hardware is low, so we'll see if I actually end up using it.


Now I'm back to using ALL the Arduino PWM output pins to wire up the four LED strips. I'm a bit apprehensive about maxing out the available analog output pins, so I've ordered a new multiplexer, the CD74HC4067, as a backup solution for expanding analog outputs. The new parts have arrived, so I'll be soldering and testing them this weekend... 🤞


The new multiplexor. I'll need to solder on the headers prior to testing.


12V Level Shifter Works!

In this project, an Arduino output pin provides up to 3.3V, insufficient for the 12V needed by the RGB LED strips. So we're using a level shifter to boost the voltage to 12V.


In recent testing, I connected the Arduino's analog output to the level shifter input and the RGB strip to its output, and... it was a success! 🥳 The light cycled through colors as programmed, though transitions were slightly choppy, a topic I'll address later in this post.


The level shifter input pins are connected to the Arduino analog output pins and the level shifter output pins are connected to the RGB LED strips.


Lesson Learned: Don't Use a Member Variable to Define a Global

After successfully testing the level shifter, I quickly integrated the three additional ColorClock objects into the code, assigning analog output pins for each new strip. However, upon connecting more LED strips and running the code, the board appeared to freeze. Subsequent attempts to upload new code failed as the computer no longer detected the Arduino's serial port. Resetting the board and reseating the USB cable eventually restored the serial port connection. Despite this, re-uploading the program continued to cause the board to freeze again 😵‍💫


I barely touched the code! I was worried there was something on the board shorting so it took me about a day of swapping out hardware, doing incremental testing, and meticulously comparing Git commits before I identified the problem.


In the last commit where everything broke, I made one tiny optimization in an effort to reduce the use of 'magic numbers'... In the definition of the time periods for each color clock, I used a static member variable, TimeCalcs::SEC_IN_HR.


float cc0_period =  6.0 / TimeCalcs::SEC_IN_HR;
float cc1_period = 12.0 / TimeCalcs::SEC_IN_HR;
float cc2_period = 30.0 / TimeCalcs::SEC_IN_HR;
float cc3_period = 60.0 / TimeCalcs::SEC_IN_HR;

I should note that the three cc#_period variables used are globals in the main driver file. It seemed like the driver file was not aware of the value of TimeCalcs::SEC_IN_HR by the time it ran that part of the code. When I removed this reference to TimeCalcs::SEC_IN_HR it worked again 😅


float cc0_period =  6.0 / 3600.0;
float cc1_period = 12.0 / 3600.0;
float cc2_period = 30.0 / 3600.0;
float cc3_period = 60.0 / 3600.0;

I'll probably create some #defines at the top of the file to keep it looking a little cleaner.


Smooth Transitions

At first, I thought updating the color output every second would be enough since our original design had a 24-hour cycle time. But once we added a user-controlled light that allows participants to set cycle times as short as six seconds, I realized that smoother color transitions were essential for seamless aesthetics.


I am using a real-time clock module to output the current time, which the program uses to determine the color and the module's smallest granularity is one second. However, Arduino has a built-in function that outputs the current number of milliseconds since the program started, so I added a function to output the current millisecond within each second.


int FluxClock::get_milli(byte crnt_sec)
{
  static byte prev_sec = 0;
  static long int prev_millis = 0;
         long int crnt_millis = millis();

  // Resent millisecond count if its a new second
  if (crnt_sec != prev_sec)
  {
    prev_sec = crnt_sec;
    prev_millis = crnt_millis;
  }

  return static_cast<int>(crnt_millis - prev_millis);
}

The two videos included below demonstate the differences when updating the color output every second versus every millisecond. The three RGB LED strips in the videos cycle through all hues in the color wheel at different rates: 6 seconds, 12 seconds, and 30 seconds.


🌈



In this implementation, the program is updating the color output every second. Notice how abrupt the color transitions appear.



Here, the colors are updated every millisecond. The transitions in this implementation are much smoother than before.


OS Reinstallation is Always an Option

In the period of time that I started writing this post, I experienced an issue where the Ubuntu partition on my desktop decided not to connect to the internet. I struggled for a few days with all sorts of network settings before I realized it wasn't worth the trouble and just reinstalled Ubuntu in its entirety. As I was going through my whole new environment setup process, I documented most of what I did to streamline the next time I have to start over 😅


User Interface Software Design Begins!

The next step in the ColorClock saga is to design the software implementation for the participant-facing user control panel. I wired up a little prototype for testing. The final design will use arcade buttons embedded in an enclosure that will house the electronics.


In this small prototype I'm using breadboard-sized push buttons and LEDs. The final control panel will use light up triangular arcade buttons for red, green, blue, and frequency modification. I'm using one current-limiting resistor for all the LEDs and 10K pull-down resistors for each push-button.


The soldering work is not beautiful but it gets the job done.

A Beautiful Breadboard

Behold! The first design of the of the fully populated solderless breadboard!

This to-scale-ish design contains all of the modules that will be present on the final circuit board for ColorClock. The user interface controls are not present on this board.


🌈


To streamline the final phase of hardware testing and preempt the challenges of electronic troubleshooting, I found it important to meticulously design the layout of the prototyping breadboard. I started this layout by looking at the datasheets, obtaining the dimensions of each module, and drawing each component to scale. Then I decided it would be actually be more functional if I were using units of breadboard holes (approximately 0.1" between holes) instead of true dimensions. To determine the size represented on the diagram I placed each module on the breadboard and noted how many holes they covered and the positioning of all the pins. The next step is to actually wire this thing up and get to testing with the real LED light display 🙌


Clock Crafting

And on the topic of the light display, it's just about finished! My partner bought a plunge base and circle jig in order to cut the circle and route out pockets for the LED strips to rest in. I'm very pleased with the results 😀 We are using a reflective metallic finish to maximize the light output, and a sheet of translucent acrylic that will sit on top to diffuse the light.


The final routed wood piece features outer rings designed to house RGB LED strips. Each ring will display colors corresponding to the current time in days, hours, and minutes. The center of the piece will have a flower-like shape of LEDs that will be controllable by participant interaction via user control panel.


The LEDs are glued to the inside of the pockets, and the colored light will be reflected off the metallic finish.


IO Expander Integration Trials

In my last post, I was working on integrating the Arduino Nano ESP32 into the project. I successfully tested this board with the new RTC module and the new alphanumeric display 🤗 But then... I was in the process of testing the IO expander and ended up messing with my board configuration so much that I was no longer able to upload any code to the board 😦 Thankfully, ptillisch came to the rescue and resolved my issue 🥳


Though I was able to talk to the Nano board, I was unable to include the library to interface with the IO expander 😭 I chased my tail for a couple of weeks before I decided to move in a different direction (see notes at the end of this post).


The good news was that I could interface the IO expander with the MKR 1000 that I had been testing on before I got the Nano board. Sooooo I ordered two more Arduinos, the MKR 1010 (they no longer make the MKR 1000) and decided to move forward.


Can I Just Get Some Variable Voltage Control??

The plan:


  • Use the IO expander for analog output (required to create the RGB color mixing)

  • Use the Arduino on-board GPIO for digital input from user interface


When the new boards came in, I promptly began testing the IO expander. I was able to get the digitalWrite functions to work with ease, but I struggled with the analogWrite function. After days of banging my head against the wall 😖 my new contingency plan was to swap the IO functionality of the on-board Arduino GPIO and the IO expander, i.e. the IO expander for the digital IO and Arduino PWM for the RGB LED control.


ColorClock has four light strips and three variable voltage lines per strip (one for each red, green and blue) so I need exactly 12 PWM pins. According to the product page for the MKR 1010, there are supposed to be 13 PWM pins on the board BUT one of those listed pins would be unusable for variable voltage output on ColorClock because it needed to be used for the I2C SCL required for communication to the various modules.


This PWM output count would leave me NO wiggle room if one of the pins didn't work as expected. My concern grew when I noticed that the datasheet had conflicting information from the product website on which pins were capable of PWM output (see the notes at the end of this post for more information).


So, to get some clarity, I manually tested each individual pin. I wrote a tiny test program and hooked up a simple circuit with an LED where I varied the output voltage. I observed the LED to see if the light got brighter as the value of the output increased. I discovered more discrepancies between my empirical testing and the documentation (see notes).


We spent the afternoon at a brewery where I performed my variable output voltage test while my partner played his Steam Deck killing zombies or something.


Wiring Matters

The inconsistency between my findings and the documentation left me uneasy, so I went back to testing the IO expander with a minimal example. I knew my code was solid, so I double-checked the circuit wiring and sure enough, there was a mistake. After fixing it, I was able to see a variable output voltage on a multimeter using the analogWrite function using the IO expander 🎉 So now I'm back to the original plan of using the IO expander to control the lights, and the on-board GPIO for the digital inputs.


The input values set by my program didn’t correlate as expected with the measured voltage with the multimeter, so it looks like I’ll need to write some calibration functions so I can programmatically compensate for hardware characteristics.


Moving Forward

So, next steps will be:


  • Wire up the breadboard so beautifully depicted above.

  • Characterize the RGB LED strip, so I can programmatically adjust for the non-linear relationship between the IO expander's output voltage and the visible color from the lights.


We've got 2 1/2 months and counting... 😅



 

Notes

IO Expander Adafruit AW9523

There have been others that have had issues integrating the Adafruit_AW9523 library with the Arduino Nano ESP32. Though the GitHub issues page states that this issue was resolved, I'm not so sure it was...


Arduino MKR 1010 PWM Pins

According to the MKR 1010 product website, the following pins are available for PWM out:

0 .. 8, 10, 12, A3, A4

According to the MKR 1010 datasheet the following pins are available for PWM out:

0 .. 9

According to my empirical testing, the following pins are available for variable voltage output:

0 .. 8, 10, A3, A4, A0* **

* I learned that the A0 pin provides a true analog output, not a PWM signal which is why it was listed on neither the website nor the datasheet as a PWM pin. This page describes the difference between analog and PWM output.


** Pins 9 and 12, as listed on the datasheet and website, respectively, did not produce variable output voltages in my experiment.



©2022 by Rebecca Jennifer  Rashkin 🐰

bottom of page