Welcome to the Rainbow Clock Guide

Hello! Welcome to the Rainbow Clock guide!

The Rainbow Clock is simply a digital clock with rainbow digits, but it’s also more than that: it is a tool for learning how to program LEDs. This website guides you through uploading your own code to the clock’s microcontroller to light up the grid of pixels with your own patterns.

What’s in this guide

This guide walks you through step-by-step writing your own LED code, including:

  • How to set up the necessary software onto your computer
  • How to upload code to the clock’s microcontroller
  • How to code several basic pixel patterns and animations

This guide also contains a breakdown of all the code that runs the clock functionality, including:

  • How the time is tracked
  • How a button press is captured
  • How the time is set using the button
  • How the pixels light up in a rainbow

Where to buy a clock

The clock is available to buy on either my online store or my Etsy shop.

Where to begin

The sidebar lists all the pages in this guide. If you want to understand more about how the clock is built, check out the pages in the “About the Rainbow Clock” section. If you’re ready to start coding, dive into Setup environment.

Link to original

About the rainbow clock

How to set the time

Locate the button on the back of the clock.

Long press the button for 2 seconds. Let go when the hours digit starts flashing.

Click the button to change the digit.

Long press the button for 2 seconds to move to the next digit. (The minutes digits are set separately.)

Click the button to change the digit.

Repeat for the last digit.

Long press the button again to return to the regular clock mode.

Unplug vs Turn off switch

If you unplug the clock, it will stop tracking time, but the time it was unplugged is stored and loaded next time you plug it in. So if you unplug it and replug 5 minutes later, it will be 5 minutes behind.

If you turn off the on/off switch, only the LEDs turn off, not the whole system. If you turn the switch off and leave the USB cable plugged in, the controller will still be powered, and it will still keep accurate time.

Link to original

Hardware breakdown

The clock itself is 3D printed, has an on/off switch on the back, and also has a button on the back for manually setting the time. The back panel of the clock hinges open to reveal a programmable ESP32 microcontroller and a 5x16 grid of LEDs. A data-sync capable microUSB cable is included.

clock_hardware

Materials

Wiring Diagram

schematic

ESP32ButtonLED Grid
VinPower (via on/off switch)
GNDGND
GNDpin 1
D13Data
D15pin 2

NOTE

The on/off switch only turns off power to the LEDs, not the whole system. So if you turn the switch off and leave the USB cable plugged in, the ESP32 board will still be powered, and it will still keep accurate time.

Link to original

LED grid layout

Sub-grid variations

There are two different hardware configurations your clock could have. These two variations are accounted for in the code. This page explains these variations.

The 5x16 grid in the clock was originally a 16x16 pixel matrix that was cut into three 5x16 pixel sub-grids. Because the wiring embedded in these grids runs back and forth in a snake pattern, one of these three sub-grids has a different starting pixel position, and the arrangement of LEDs is flipped.

Original 16x16 GridLED Arrangement Diagram

Which sub-grid variation do you have?

Your grid’s starting pixel should be on either the bottom left or the top left (looking at the front of the clock). You can hinge open the back panel of your clock to access your sub-grid. Then you can compare yours to the photos below to figure out which starting pixel you have.

If for some reason your starting pixel is on the right, rotate your grid 180 degrees so that it is on the left. The code only accounts for starting pixels on the bottom left or top left.

Sub-grids with BOTTOM_LEFT start pixel

subgrid_type2_diagram

Some grids have the wires soldered directly to the flat part on the back (see photo below). These have the BOTTOM_LEFT start pixel.

subgrid_type1_diagram_back

Sub-grids with TOP_LEFT start pixel

subgrid_type3_diagram

Set your starting pixel in the code

In the main file rainbow-clock.ino, there is a variable START_PIXEL. Change this variable to be set to LEDGrid::BOTTOM_LEFT or LEDGrid::TOP_LEFT according to your sub-grid’s start pixel.

#define START_PIXEL LEDGrid::BOTTOM_LEFT  // or LEDGrid::TOP_LEFT
 
LEDGrid leds(WIDTH, HEIGHT, START_PIXEL);
Link to original

Write your own LED code

Setup environment

In order to upload your own LED code to the Rainbow Clock, you’ll need to install the Arduino IDE. This page will walk you through setting up the Arduino IDE to be ready to upload code to the rainbow clock.

1. Download and install the Arduino IDE

There are two versions of the Arduino IDE: the newer Arduino IDE 2 and the Legacy IDE (1.8.X). Arduino IDE 2 is recommended but has specific operating system requirements, so the Legacy IDE is a good alternative for older systems.

Once you have the Arduino IDE installed, launch it, and it should look like this:

You now have the Arduino IDE installed!

2. Add the ESP32 board within the Arduino IDE

Now that you have the Arduino IDE, you’ll need to install the ESP32 board add-on within the IDE. (An ESP32 is a microprocessor just like an Arduino. The IDE comes ready to upload code to Arduino boards, but the ESP32 boards are special and need to be added separately.)

(Disclaimer: The steps and screenshots below are based directly on this guide: Installing ESP32 Board in Arduino IDE 2, with all screenshots freshly taken for this content.)

(If you have Legacy IDE 1.8.X, the steps are the exact same, but the screenshots won’t quite match. Here’s a guide specifically for Legacy IDE: Installing ESP32 Board in Legacy Arduino IDE 1)

To add ESP32 to Arduino IDE:

2a. Open Preferences

Go to File > Preferences for Windows users. For Mac users: Arduino IDE > Preferences.

Copy and paste the following line to the Additional Boards Manager URLs field. Press “OK” to confirm.

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Note: if you already have the ESP8266 boards URL, you can separate the URLs with a comma, as follows:

http://arduino.esp8266.com/stable/package_esp8266com_index.json, https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

2c. Open the Boards Manager

On the left side, click the Boards Manager icon. (Or you can go to Tools > Board > Boards Manager...)

2d. Search ESP32, Install

Type “ESP32” in the search field. Locate esp32 by Espressif Systems. Click Install. (This screenshot has a “Remove” button because it is already installed in this case.)

You now have the ESP32 board added!

3. Install LED and Time libraries from the Arduino libraries manager

The rainbow clock code uses two libraries:

  • FastLED: For controlling addressable LEDs.
  • ESP32Time: For setting and retrieving internal RTC (Real Time Clock) time on ESP32 boards.

Conveniently, you can install these libraries using the Arduino IDE!

3a. Open Arduino library manager

On the left side, click the Library Manager icon. (Or you can go to Tools > Manage Libraries.)

3b. Search for “FastLED”. Find FastLED by Daniel Garcia. Click Install.

3c. Search for “ESP32Time”. Find ESP32Time by fbiego. Click Install.

4. Test uploading code to the Rainbow Clock

To make sure you have everything set up correctly, we’ll upload some test code to the Rainbow Clock’s ESP32 board. This test code prints the words “hello world!” every 1 second to the Serial monitor.

(Again, these steps and screenshots are copied from this guide, except with different test code.)

4a. Paste this test code

In the main text area, delete all the pre-filled code, and paste the following.

void setup() {
  Serial.begin(9600);
  delay(500);
}
 
void loop() {
  Serial.println("hello world!");
  delay(1000);
}

4b. Select your board

Make sure your Rainbow Clock is plugged into your computer. On the top drop-down menu, click the “Select Board” dropdown. Click Select other board and port…

A new window will open:

Under “BOARDS” on the left, type “esp32 dev module” to filter, and then select ESP32 Dev Module.

Under “PORTS” on the right, select your board. It may have a different name other than COM, like /dev/cu.SLAB_USBtoUART, or /dev/cu.usbserial-0001. If you’re not sure which one to select, unplug the Rainbow Clock from your computer, then plug it back in, and see which option appears.

Click “OK” to confim.

4c. Click the upload button

At this step, your computer might prompt you to download and install python3. You’ll need to allow it, as python is needed to upload to ESP32 boards. If you’re getting stuck on this step, this guide has more detailed steps regarding python.

4d. Open the serial monitor to see the output

Verify that the code is running on the Rainbow Clock’s ESP32 by opening the Serial Monitor. Tools > Serial Monitor

The serial monitor shows up at the bottom below the code editor:

If you see “hello world!” printing every 1 second in your Serial monitor, it’s working! YOU DID IT! :) It might not be obvious, but this “hello world” code is running on your Rainbow Clock’s ESP32.

(If you see text but it’s random characters/jargon, make sure you select “9600 baud” on the right side of the serial monitor. 9600 should be the default.)

Now that you know how to upload code to the Rainbow Clock, you can start writing your more fun LED code!

Go to the next section! Code simple LED grid patterns

Link to original

Code simple LED grid patterns

This page will walk you through how to code some basic LED patterns, starting with turning on a single LED. Each pattern adds a layer of complexity onto the last, while calling out essential utility functions like map() and delay().

Prerequisites

  • Setup environment steps completed (ie. you can successfully upload code to an ESP32)
    • Arduino IDE installed
    • ESP32 board added to Arduino IDE
    • FastLED library installed
  • Starter code (see next section)

Starter code

To get the starter code, go to: rainbow-clock/starter-code.zip and click the download icon on the far right:

starter-code-download

This will download a zip which contains two files. Once unzipped, open the folder. There should be two files in the folder: LEDGrid.h and starter-code.ino. Open the .ino file in the Arduino IDE.

starter-code-ide

The rest of the examples on this page will assume that all of this surrounding starter code is present. When you paste code from each example below, paste below the comment:
// ... code here ... (highlighted in the above screenshot)
Don’t delete any of the other surrounding code.

(You don’t have to understand all this surrounding code to start coding some LED patterns!)

Loop structure

The loop() function runs over and over forever. This loop function we’re using here follows this flow:

  1. clear all the LEDs
  2. run code to compute what the LEDs look like on one iteration
  3. tell FastLED to send that data to the physical LEDs
void loop() {
  FastLED.clear(); // Set all LEDs to black
 
  // ... code here ...
 
  FastLED.show(); // Tell FastLED to show your data
}

Assume this loop structure in all the following examples, unless otherwise specified.

Static patterns

Turn on one LED

turn-on-one-led
leds(0, 0) = CRGB::Red;

Paste the above code into the Arduino IDE editor where you see the comment // ... code here .... (It doesn’t matter if the tabs/spacing is messed up, but if you want it to look pretty, you can press ctrl-t on Windows or cmd-t on Mac to auto-format.) Then click the upload icon!

This code sets a single pixel at the (x, y) coordinate (0, 0) to Red. CRGB::Red is one of many pre-defined colors in the FastLED library. See the full list of FastLED predefined colors. Try changing CRGB::Red to CRGB::Blue or CRGB::Green!

NOTE

If the pixel that turned on is not at the bottom left, you probably need to change the START_PIXEL variable towards the top of the file.

#define START_PIXEL LEDGrid::TOP_LEFT // change to LEDGrid::BOTTOM_LEFT, or vice versa

See LED grid layout for more details.

Challenge!

challenge-pattern
???

Now you know how to turn on a single LED at an (x, y) coordinate. Can you make the pattern in the image above? Solution here.

Turn on a row of LEDs

turn-on-row-of-leds
for (int x = 0; x < WIDTH; x++) {
  leds(x, 2) = CRGB::Red;
}

This code loops through every value of x from 0 to WIDTH (which is 16) and sets the LED in row 2 at that x-coordinate to Red. (Detailed explanation of a for loop here.)

TIP

It doesn’t matter if the tabs/spacing gets messed up, but if you want it to look pretty, you can press ctrl-t on Windows or cmd-t on Mac to auto-format, or go to Edit > Auto Format.

Turn on every LED

turn-on-every-led
for (int x = 0; x < WIDTH; x++) {
  for (int y = 0; y < HEIGHT; y++) {
    leds(x, y) = CRGB::Red;
  }
}

A double for loop! This one still loops through every value of x, and for each value of x, loop through every value of y from 0 to HEIGHT (which is 5). You can also think of x as column and y as row.

Make up your own colors

In addition to predefined colors like CRGB::Red, FastLED also allows you to set colors using two color models: RGB, and HSV (aka HSL). You can define these with the respective functions CRGB(red, green, blue) (CRGB documentation) and CHSV(hue, saturation, value) (CHSV documentation).

colorpicker.me is a useful and fun tool for understanding how colors are defined and for discovering new colors! Also keep in mind that not all colors map perfectly to LEDs. For instance you might find a nice light purple on colorpicker.me, try to send its RGB values to the LEDs, and get a boring dim white-ish color.

RGB

Here we define three variables red, green, and blue and set their values between 0-255. The following code sets all LEDs to the RGB color (9, 105, 218) (a rich blue color)

int red = 9;
int green = 105;
int blue = 218;
for (int x = 0; x < WIDTH; x++) {
  for (int y = 0; y < HEIGHT; y++) {
    leds(x, y) = CRGB(red, green, blue);
  }
}

HSV

hue, saturation, and value are all values between 0-255 in FastLED, but a typical HSV color has different ranges. Hue is usually a value 0-360 (as in 360 degrees on a color wheel), and saturation and value are usually percentages 0-100. To get the same blue color we got above as an RGB, we take the hue from colorpicker.me, which is 212 and we convert it from the 0-360 range to the 0-255 range: 212 * 255 / 360. Similarly we take the saturation and value 92% and 85% and convert them to the 0-255 range: 92 * 255 / 100 and 85 * 255 / 100.

int hue = 212 * 255 / 360;
int saturation = 92 * 255 / 100;
int value = 85 * 255 / 100;
for (int x = 0; x < WIDTH; x++) {
  for (int y = 0; y < HEIGHT; y++) {
    leds(x, y) = CHSV(hue, saturation, value);
  }
}

(Note that these RGB and HSV blues might be slightly different. The RGB <> HSV conversion is not perfect. You can see this for yourself in colorpicker.me if you try increasing or decreasing the “G” or “B” values by one, sometimes the HSV stays the same. So there could be multiple RGBs for a single HSV.)

Make a horizontal rainbow gradient

horizontal-rainbow-gradient
for (int x = 0; x < WIDTH; x++) {
  for (int y = 0; y < HEIGHT; y++) {
    int hue = map(x, 0, WIDTH, 0, 255);
    leds(x, y) = CHSV(hue, 255, 255);
  }
}

How it works

This one sets each pixel to a custom CHSV color instead of a pre-defined color like Red. The HSV color model takes the arguments (hue, saturation, value). saturation and value are both at their max of 255. We calculate the hue as a function of x. We do this with the map() function.

map() function

map(value, fromLow, fromHigh, toLow, toHigh)

The Arduino map() function is a handy built-in Arduino function that lets you convert a value from a starting range to a destination range.

int hue = map(x, 0, WIDTH, 0, 255);

Here, we’re mapping the x value from it’s range 0-WIDTH (which is 16) to the range of a hue 0-255. For example, when x is 2, hue will calculate to 32.

Dynamic patterns

make-one-led-blink
leds(0, 0) = CRGB::Red;   // Set the pixel to Red
FastLED.show();           // Tell FastLED to show your data
delay(1000);              // Wait 1 second (1000 milliseconds)
 
leds(0, 0) = CRGB::Black; // Turn the pixel off
FastLED.show();           // Tell FastLED to show your data
delay(1000);              // Wait 1 second (1000 milliseconds)

NOTE When you paste this into the starter code under // ...code here... , you’ll notice FastLED.show() gets called again after the pasted code. You can just leave that extra call in there. In this example, it doesn’t need to be called again after the last delay(1000), but it doesn’t hurt, and it’s easier to keep the same loop structure in place.

This code sets a single pixel to Red, tells FastLED to show that pixel, then uses the delay() function to pause for one second, then turns that LED to Black (off), show, pause.

delay() function

delay(milliseconds)

The Arduino built-in delay function pauses the program for a specified number of milliseconds before running the next line of code.

Make a scrolling rainbow

make-scrolling-rainbow
int sinBeat = beatsin8(30, 0, 255);   // 30 beats per minute, range 0-255
 
for (int x = 0; x < WIDTH; x++) {
  for (int y = 0; y < HEIGHT; y++) {
    int hue = map(x, 0, WIDTH, 0, 255);  // map the current x from 0-WIDTH to a hue in the range 0-255
    leds(x, y) = CHSV(sinBeat + hue, 255, 255);  // add a sine wave to hue to make it oscillate
  }                                                                                         
}   

How it works

This one creates a sine wave using beatsin8() to get a value that oscillates between a given range over “time” (ie. as the loop gets called over and over). It uses that sine wave to offset the hue, so that the hue at a given column is changing, creating the illusion that the whole picture is moving back and forth.

beatsin8() function

int sinBeat = beatsin8(30, 0, 255);   // 30 beats per minute, range 0-255 

The beatsin8 function is a FastLED beat generator which returns a sine wave in a specified Beats Per Minute, and with a specified low and high range for the output. The 8 stands for an 8-bit value, meaning the number can range from as low as 0 to as high as 255. The range of a hue is also 0-255 so we will utilize that whole range.

8-bit integers

CHSV(sinBeat + hue, 255, 255);

Let’s talk about why this sinBeat + hue part works.

  • The sinBeat variable we’ve already defined as a number oscillating in the range 0-255
  • The hue variable is calculated as a function of x and converted to a number in the range 0-255. So when x is 2, hue is 32 (red-ish-orange)
  • The sum sinBeat + hue is passed into the first argument of the CHSV function. According to the documentation, the hue variable is a type uint8_t - an 8-bit number from 0-255. (Unlike an int variable which can be anywhere in the range -2,147,483,648 to 2,147,483,647) So what happens if we pass in 256? It will wrap around to the beginning and turn into 0. This will always happen when you set any uint8_t variable to 256. Similarly 257 will be 1, 258 will be 2, etc.
  • Example: if we look at only the 3rd column of the grid, where x = 2, hue becomes 32 (red-ish-orange), then we add sinBeat which oscillates between 0-255, making the sum oscillate between 32-287, but 287 wraps around to 32. So the color at that columns is oscillating from red-ish-orange, to the end of the rainbow, wraps to the beginning, then to 32, then reverses all the way back.

What’s next

I believe the best way to learn programming is to try changing the code, running it, and seeing what happens. Get curious about what would happen if you change this variable to that, move this line of code there, etc. And see what happens! Sometimes it looks terrible! Sometimes it turns out different than expected, and sometimes that even leads to a new idea. :) What designs can you create? What colors did you make up? What’s the RGB code for your favorite color? Can you make some animations?

I would LOVE to see your creations. Post them on Instagram and tag @mickymakes.art!

Troubleshooting & FAQ

A few issues have been compiled on the Troubleshooting & FAQ page, which will be updated over time as feedback is collected on any additional issues users encounter.

How to re-upload the original clock code

If you’ve uploaded your own code to the Rainbow Clock and you want to go back to using it as a clock, this page will walk you through how to upload the original clock code: How to re-upload the original clock code

Link to original

How to re-upload the original clock code

If you’ve uploaded your own code to the Rainbow Clock and you want to go back to its original function as a clock, this page will walk you through how to upload the original clock code.

Steps

  1. Go to https://github.com/michellesh/rainbow-clock

  2. Click the green “Code” button -> Download ZIP

  3. Unzip and open the folder called rainbow-clock-main

  4. Open the file rainbow-clock/rainbow-clock.ino in the Arduino IDE. If you don’t have the Arduino IDE installed, you’ll need to follow at least the first three steps on the Setup environment page to install the Arduino IDE, add the ESP32 board within the IDE, and install the libraries FastLED and ESP32Time.

  5. Make sure you set the right START_PIXEL set according to if your grid’s starting pixel is on the bottom left or top left. You can open your clock to check this, or you can just try one and see if the numbers are displayed upside down!

    In the file rainbow-clock/rainbow-clock.ino:

    #define START_PIXEL LEDGrid::TOP_LEFT  // or LEDGrid::BOTTOM_LEFT
  6. Connect the Arduino IDE to your clock’s ESP32. Make sure your Rainbow Clock is plugged into your computer. On the top drop-down menu, click the “Select Board” dropdown. Click Select other board and port…

    A new window will open:

    Under “BOARDS” on the left, type “esp32 dev module” to filter, and then select ESP32 Dev Module.

    Under “PORTS” on the right, select your board. It may have a different name other than COM, like /dev/cu.SLAB_USBtoUART, or /dev/cu.usbserial-0001. If you’re not sure which one to select, unplug the Rainbow Clock from your computer, then plug it back in, and see which option appears.

    Click “OK” to confim.

  7. Click the upload button in the Arduino IDE.

    At this step, your computer might prompt you to download and install python3. You’ll need to allow it, as python is needed to upload to ESP32 boards. If you’re getting stuck on this step, this guide has more detailed steps regarding python.

Link to original

Clock code breakdown

Basic code and file structure

This page briefly explains the basic setup and loop structure used for Arduino programming, and contains an overview of the files in the Rainbow Clock code.

Basic code structure

When you open a blank file in the Arduino IDE, you get the following template. Arduino programs always need a setup() and a loop() function.

void setup() {
  // put your setup code here, to run once:
 
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

setup()

The setup() function runs once when you press reset or power the board. Some examples of things you might put in here are:

  • initialize a data pin with an LED
  • initialize a data pin with a button
  • initialize an array of LEDs with the FastLED library

loop()

The loop() function runs over and over again forever. Some examples of things you might put in here are:

  • read the state of a button
  • set basic LEDs to on or off
  • set addressable LED pixels to a color

Example

Here’s an example from the Arduino IDE. You can access this example in your own IDE by going to File -> Examples -> Basics -> Blink

/*
  Blink
 
  Turns an LED on for one second, then off for one second, repeatedly.
 
  http://www.arduino.cc/en/Tutorial/Blink
 
*/
 
// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}
 
// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Rainbow Clock file structure

The rainbow clock code is made up of .ino files and .h files.

.ino files

.ino files are arduino files. When you compile your Arduino program, all .ino files are automatically concatenated together into one big file. They are a way to organize large programs into separate files.

.ino fileDescription
rainbow-clock.inoThe main arduino file containing the setup() and loop() functions
button.inoButton related functions. See the code breakdown on the How the button code works page.
eeprom.inoEEPROM related functions. See the code breakdown on the How the clock code works page.
leds.inoLED related functions. See the code breakdown on the How the LED code works page.

.h files

.h files are header files. In Arduino programming, header files contain definitions for a library, which contain .cpp and .h files. (See more about library file structure here.) The Rainbow Clock header files break this rule! They are used to store class definitions and cohesive standalone chunks of code instead of following the traditional library structure.

.h fileDescription
LEDGrid.hLEDGrid class. See the code breakdown on the How the LED code works page.
Clock.hClock class. See the code breakdown on the How the clock code works page.
Button.hButton class. See the code breakdown on the How the button code works page.
Digit.hDigit struct and definitions of digits 0-9. See the code breakdown on the How the LED code works page.
Timer.hTimer struct. See the code breakdown on the How the button code works page.
Link to original

How the clock code works

This page will walk through all the parts of the code that have to do with the clock: tracking the hours, minutes and seconds, editing the time, and flashing the digits. The rest of the code, ie. code related to the LEDs or button, will be skipped on this page but covered on the other pages in this section.

Clock code structure

Here’s all the clock-related code in the main rainbow-clock.ino file.

First include other files in this repository. Clock.h contains a clock class that’s used to store the hour, minute, and second, and contains the functionality for editing the time.

#include "Clock.h"

Note: .ino files don’t need to be included. All .ino files are automatically concatenated together into one big file when you compile.

Create the clock object.

Clock c;

In the main loop, there are three clock related functions: updateTimeFromRTC(), which collects the hour and minute from the RTC library and stores it in the Clock class. updateDigitVisibility(), which controls flashing the digits while editing the time, and updateColonVisibility(), which controls flashing the colon.

void loop() {
  // ...
  
  // Update the hour/minute variables via the RTC module
  c.updateTimeFromRTC();
 
  // Flash digits when setting the clock
  c.updateDigitVisibility();
 
  // Flash the colon every second when showing the time
  c.updateColonVisibility();
 
  // ...
}

Clock class

This class stores the hour, minute, and second values, converts them into individual digits, and contains the functionality for editing the time and flashing the digits.

Define global variables

Start by defining global variables. These define all the modes, which track if we’re editing the clock, and which digit we’re editing.

// Modes
#define NUM_MODES 4           // 4 modes
#define SHOW_TIME 0           // Mode 0: show the current time
#define EDIT_HOUR 1           // Mode 1: set the hour digit
#define EDIT_MINUTE_DIGIT_1 2 // Mode 2: set the left minute digit
#define EDIT_MINUTE_DIGIT_2 3 // Mode 3: set the right minite digit

Here we define a day, month, and year, but these are actually irrelevant because they don’t show up on the clock. The RTC library requires a day/month/year so this is just a random one.

// (The RTC library requires both time and date. Since the clock only tracks
// time, these values don't do anything.)
#define DAY 11
#define MONTH 8
#define YEAR 2023

These variables define how fast the colon flashes, and how fast the digits flash when editing them.

#define EDIT_TIME_FLASH_DURATION 300 // millseconds per flash when setting time
#define COLON_FLASH_DURATION 1000    // milliseconds per colon flash

Define private and public clock class variables

Private variables cannot be accessed outside of this class. We store things here that either don’t need to be called elsewhere, or that we specifically don’t want to be called elsewhere so we can control what they get set to. Private variables often have a _ prefix, which serves as a reminder that they’re private variables while writing code.

// This class stores all clock-related data, and functions to access the
// individual digits and whether they are visible.
class Clock {
 
private:
  ESP32Time _rtc;
 
  // When setting the clock, the digit you're currently setting flashes on and
  // off. `_hideDigit` tracks whether the digit being edited is currently hidden
  bool _hideDigit;
 
  // The colon always flashes every second, unless you're setting the time, it
  // flashes along with the digit being edited
  bool _hideColon;

The ESP32Time _rtc creates an instance of the ESP32Time library we installed. This is how we get the accurate time from the RTC module on the ESP32. The _rtc object is stored directly on the clock object here because there is nowhere else in the code that needs to access the RTC functions. It’s a private variable to ensure that no other functions outside the Clock class will modify the RTC object, eg. change the time.

_hideDigit and _hideColon are booleans that indicate when to show and hide the digits when they are flashing.

Public variables can be accessed outside of the class.

public:
  int mode; // the current mode
 
  int hour;   // the current hour in 24-hour time
  int minute; // the current minute
  int second; // the current second

Why does it track seconds if it doesn’t show seconds on the clock? It doesn’t have to, but it does for one small reason: when you unplug the clock, the hour, minute and second are stored in the EEPROM memory. So when you unplug the clock and plug it back in, it will load the exact same time, including seconds. If seconds were omitted, it would start over at seconds=0 when plugged back in.

Convert 24-hour time to 12-hour time

The _get12hour() function converts 24 hour time to 12 hour time.

// Convert 24-hour time to 12-hour time
int _get12hour(int hour24) {
  int hour12 = hour24 % 12;
  return hour12 == 0 ? 12 : hour12;
}

Why not just store 12-hour time and not worry about 24-hour time? The code felt cleaner to store 24 hour time behind the scenes, and then convert to 12-hour time right before displaying digits on the clock. The main reason for this is if someone wanted to change their clock to show 24-hour time, they would have to figure out a few different places in code where 12-hour time is assumed across a couple different files. This way, the only thing they’d have to change is remove wherever _get12hour is called, which is more straightforward to figure out.

Get and set time values

This function updateTimeFromRTC() is called in the main loop() function. It collects the hour minute and second from the RTC module and stores them on this Clock class. It only does this in SHOW_TIME mode, so that the minute doesn’t change while you’re editing the time.

void updateTimeFromRTC() {
  // Update the accurate hour and minute from the RTC module
  // in SHOW_TIME mode only (don't update while setting the clock)
  if (mode == SHOW_TIME) {
    hour = _rtc.getHour(true);
    minute = _rtc.getMinute();
    second = _rtc.getSecond();
  }
}

Why do we need to store the hour/minute/second in a Clock class when we can get the hour minute and second directly from the RTC library? The short answer is because of edit mode. If we just displayed the RTC values directly on the clock, then there’s a chance that while you are editing the time, the number you are editing would change while you are editing it. Then you’d have to go back and edit again! Boring! So we store separate hour and minute variables, and when we’re editing, we’re changing those separate variables, and when we’re not editing, we keep them in sync with the RTC values.

setNewTime() is how we tell the RTC library what time to start with so it can track accurate time from there. It’s called every time we click the button to change a number while in edit mode. It’s also called when we load the time from the EEPROM memory when we power the clock.

void setNewTime(int newHour, int newMinute, int newSecond = 0) {
  // When finished setting the clock, this function gets called to tell the
  // RTC library the newly set time
  _rtc.setTime(newSecond, newMinute, newHour, DAY, MONTH, YEAR);
}

Get individual digits

These “get digit” functions are called by the LED code when it’s time to display an actual digit on the clock. They convert the time values into individual digits. For example at 12:34 these functions return 1, 2, 3, and 4 respectively.

// Separate the hours and minutes into left and right digits
int getHourDigit1() { return _get12hour(hour) / 10; }
 
int getHourDigit2() { return _get12hour(hour) % 10; }
 
int getMinuteDigit1() { return minute / 10; }
 
int getMinuteDigit2() { return minute % 10; }

Update visibility of digits and colon

These “update visibility” functions are called from the main loop() function. They use a nifty function EVERY_N_MILLISECONDS provided by the FastLED library. Every 300 milliseconds, toggle the _hideDigit boolean from true -> false -> true etc. _hideDigit is toggled even when we’re not in edit mode and the digit isn’t flashing. But the digit doesn’t flash all the time because as you’ll see in the next functions, both _hideDigit AND edit mode are checked before actually hiding the digit.

void updateDigitVisibility() {
  // If editing the time, flash the digit being edited every 300ms
  EVERY_N_MILLISECONDS(EDIT_TIME_FLASH_DURATION) { _hideDigit = !_hideDigit; }
}
 
void updateColonVisibility() {
  // Flash the colon every 1 second
  EVERY_N_MILLISECONDS(COLON_FLASH_DURATION) { _hideColon = !_hideColon; }
}

Get current visibility of digits and colon

The rest of these “is visible” functions return whether or not each digit and the colon are currently visible or hidden.

bool isHourDigit1Visible() {
  // Also hide the left hour digit if it's zero, ie. show 5:00 instead of
  // 05:00
  return getHourDigit1() != 0 && (mode != EDIT_HOUR || !_hideDigit);
}
 
// This is the same as saying: if we're not editing the hour, return true.
// If we are, and `_hideDigit` is false, also return true. Otherwise,
// return false.
bool isHourDigit2Visible() { return mode != EDIT_HOUR || !_hideDigit; }
 
bool isMinuteDigit1Visible() {
  return mode != EDIT_MINUTE_DIGIT_1 || !_hideDigit;
}
 
bool isMinuteDigit2Visible() {
  return mode != EDIT_MINUTE_DIGIT_2 || !_hideDigit;
}
 
bool isColonVisible() {
  // The colon flashes at different speeds depending on the mode. When editing
  // the clock, the colon flash aligns with the digit flash. Otherwise, it
  // flashes slower.
  return mode == SHOW_TIME ? !_hideColon : !_hideDigit;
}

EEPROM

The EEPROM is internal memory on the ESP32 microcontroller that doesn’t get lost when you unplug the ESP32 from power. We use it to store the time (hour, minute, second) so that if you unplug the clock and plug it back in, the last stored time will be loaded. (However, when you plug the clock back in, it doesn’t know what time it is now, only what time it was when you unplugged it. So if you unplug the clock for 20 minutes and plug it back in, it will be 20 minutes behind.)

EEPROM code in main loop structure

When the board starts up, we collect the time values from EEPROM, and in the main loop, we update the EEPROM with the new time values.

#include <EEPROM.h>    // Built in
 
// ...
 
void setup() {
  // ...
 
  // Read initial hour/minute from EEPROM
  readEEPROM();
}
 
void loop() {
  // ...
  
  // Store the hour/minute variables in memory
  updateEEPROM();
 
  // ...
}

Read and update EEPROM

Each EEPROM variable needs an address to write to, here we define those as 0 1 and 2.

// EEPROM variables
#define EEPROM_SIZE 3   // 3 bytes stored in EEPROM
#define EEPROM_HOUR 0   // Variable 0: hour
#define EEPROM_MINUTE 1 // Variable 1: minute
#define EEPROM_SECOND 2 // Variable 2: second

The EEPROM library provides the functions EEPROM.begin to set the number of bytes we want to use, and EEPROM.read to read the byte from EEPROM. Then we pass those values to the Clock object c to set the time.

void readEEPROM() {
  EEPROM.begin(EEPROM_SIZE);
  int hour = EEPROM.read(EEPROM_HOUR);
  int minute = EEPROM.read(EEPROM_MINUTE);
  int second = EEPROM.read(EEPROM_SECOND);
  c.setNewTime(hour, minute, second);
}

Every 1 second, write the new time values with EEPROM.write, and call EEPROM.commit to save changes.

void updateEEPROM() {
  EVERY_N_SECONDS(1) {
    EEPROM.write(EEPROM_HOUR, c.hour);
    EEPROM.write(EEPROM_MINUTE, c.minute);
    EEPROM.write(EEPROM_SECOND, c.second);
    EEPROM.commit();
  }
}

EEPROM References: Arduino EEPROM library and ESP differences from the standard EEPROM class.

Link to original

How the LED code works

This page will walk through all the parts of the code that have to do with LEDs. The rest of the code related to the clock and button functionality will be skipped on this page but covered on the other pages in this section.

Include libraries and define global variables

Include libraries and other files in this repository. FastLED is an external library that lets us interact with LEDs and set them to different colors. Digit.h and LEDGrid.h are internal files that contain more globals and standalone class definitions.

#include <FastLED.h>   // Arduino libraries manager
 
#include "Digit.h" // Pixel arrangements for the numbers 0-9
#include "LEDGrid.h"

Note: .ino files don’t need to be included. All .ino files are automatically concatenated together into one big file when you compile.

Define the global variables

// LED variables
#define LED_PIN 13                // pin D13 on the ESP32
#define BRIGHTNESS 200            // 0-255
#define WIDTH 16                  // number of pixels across
#define HEIGHT 5                  // number of pixels high
#define NUM_LEDS (WIDTH * HEIGHT) // total number of pixels
 
#define START_PIXEL LEDGrid::TOP_LEFT // or LEDGrid::BOTTOM_LEFT

LED code structure

In the setup() function, tell the FastLED library which pin to read from, to use the leds array, and how many LEDs there are.

void setup() {
  // ...
  
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds[0], NUM_LEDS);
 
  // ...
}

In the main loop function, set all LEDs to black to start with a clean slate, then call to showClockLEDs to figure out which pixels to turn on to display the clock, then tell FastLED to show those pixels.

void loop() {
  // Set all LEDs to black
  FastLED.clear();
 
  // ...
 
  // Set the LED pixels to rainbow colored digits
  showClockLEDs();
 
  // Set the two pixels to show the colon
  showColonLEDs();
 
  // Tell the FastLED library to show your data
  FastLED.show();
}

leds.ino

The leds.ino file contains functions related to setting the right pixels on the grid to different colors.

Why are these functions not on the LEDGrid class? These functions are defined outside the LEDGrid class to keep it free of any clock-related dependencies, making it reusable for other projects. The functions in leds.ino mix LED and clock-specific functionality, including references to clock-related items like isHourDigit1Visible and Digit. If these functions were incorporated into the LEDGrid class, it would restrict the class to clock-based applications. By keeping LEDGrid independent of clock functionality, the Rainbow Clock can be reprogrammed to perform entirely different tasks—-like running a ‘snake’ game—-while still using LEDGrid as-is, without any modifications.

showClockLEDs

At this point, we can access the four individual digits of the clock with the c object. (The way those variables are set is covered in How the clock code works.) This function showClockLEDs(), goes through each of the four digits, checks if the digit is hidden (ie. if it’s hidden while flashing in edit mode), and if it’s not hidden, calls the next function in this file showDigitLEDs to show that individual digit, specifying one of the four digit positions.

void showClockLEDs() {
  // Set the LEDs for each individual digit, if it's not hidden (ie. if it's
  // not currently flashing in edit mode)
  if (c.isHourDigit1Visible()) {
    showDigitLEDs(c.getHourDigit1(), DIGIT_1_COLUMN);
  }
  if (c.isHourDigit2Visible()) {
    showDigitLEDs(c.getHourDigit2(), DIGIT_2_COLUMN);
  }
  if (c.isMinuteDigit1Visible()) {
    showDigitLEDs(c.getMinuteDigit1(), DIGIT_3_COLUMN);
  }
  if (c.isMinuteDigit2Visible()) {
    showDigitLEDs(c.getMinuteDigit2(), DIGIT_4_COLUMN);
  }
}

showDigitLEDs

Each digit takes up a 3x5 grid of pixels. These 3x5 grids are defined in Digit.h. This function loops through each pixel in a digit and assigns it to it’s corresponding pixel in the leds grid.

void showDigitLEDs(int digit, int startColumn) {
  // Get a 3x5 mapping of which pixels to display for this digit
  Digit d = digits[digit];
 
  // Loop through this 3x5 array pixels and set the LEDs
  for (int x = 0; x < DIGIT_WIDTH; x++) {
    for (int y = 0; y < DIGIT_HEIGHT; y++) {
      // The pixels array tells us to show this LED or set it to Black
      if (d.showPixel(x, y)) {
        leds(startColumn + x, y) = getColor(startColumn + x);
      } else {
        leds(startColumn + x, y) = CRGB::Black;
      }
    }
  }
}

Digit.h

Each digit is a two-dimensional array of 0s and 1s. This screenshot shows the 1s highlighted. We can loop through these 2d arrays of “pixels” and check if the corresponding LED should be lit.

image

We store this 2d array of pixels in the Digit struct.

struct Digit {
  bool pixels[DIGIT_HEIGHT][DIGIT_WIDTH];
  bool showPixel(int x, int y) {
    return pixels[DIGIT_HEIGHT - y - 1][x];
  }
};

The function showPixel returns whether a pixel in the 3x5 digit grid should be lit. It swaps the (x, y) syntax to [y][x] because the pixel arrays are organized row-then-column. It also flips the y value because the pixels are stored top-to-bottom, and the clock pixels are rendered bottom-to-top.

Then we take all the Digit objects 0-9 and make a global array digits.

Digit digits[] = {zero, one, two, three, four, five, six, seven, eight, nine};

getColor

Get a color of the rainbow according to which column we’re on. Map the current column 0 -> 16 to a hue 0 -> 255

CHSV getColor(int column) {                                      
  int hue = map(column, 0, WIDTH, 255, 0);
  return CHSV(hue, 255, BRIGHTNESS);                                                        
}

First, use the map() function to convert the column to a hue. The Arduino map() function is a handy Arduino built-in function that lets you convert a value from a starting range to a destination range. Here, we’re mapping the column from it’s range 0-WIDTH (which is 16) to the range of a hue 0-255. For example, when column is 2, hue becomes 32.

Then we pass the hue to a FastLED function CHSV() to create an HSV color (CHSV documentation). In the FastLED library, hue, saturation, and value are all values between 0-255 but a typical HSV color has different ranges. Hue is usually a value 0-360 (as in 360 degrees on a color wheel), and saturation and value are usually a percentage 0-100. Here we set the saturation to it’s max of 255 and brightness to the global BRIGHTNESS variable we defined at the very beginning of the rainbow clock program.

showColonLEDs

If the colon is flashed on, set those 2 LEDs to white. In the main file, we defined the COLON_COLUMN to be column 8. This function sets the pixels in that column on rows 1 and 3.

void showColonLEDs() {
  // If the colon is flashed on, set the 2 LEDs to white in the middle column,
  // rows 1 and 3
  if (c.isColonVisible()) {
    CHSV white = CHSV(0, 0, BRIGHTNESS);
    leds(COLON_COLUMN, 1) = white;
    leds(COLON_COLUMN, 3) = white;
  }
}

LEDGrid.h class

This class lets us reference the LEDs conveniently with syntax like leds(x, y) = color. The main part of the code is the function xyCoordsToIndex which converts x, y coordinates to an index on the one-dimensional array that snakes back and forth on the grid, which is how FastLED stores LED data.

The LEDGrid object is defined like this in the main rainbow-clock.ino file:

LEDGrid leds(WIDTH, HEIGHT, START_PIXEL);

Private and public variables

Define private variables. These cannot be accessed outside of this class.

class LEDGrid {
 
private:
  int _width;                    // number of pixels across
  int _height;                   // number of pixels high
  int _startPixel = BOTTOM_LEFT; // the first pixel on the subgrid, either
                                 // BOTTOM_LEFT or TOP_LEFT
  CRGB *_leds;                   // pointer to the LED array

The FastLED leds array is usually defined with this syntax: CRGB leds[NUM_LEDS]. That syntax will allocate the amount of memory needed for the array. It requires NUM_LEDS to be a static variable so it knows how much memory to allocate. But since this class is flexible enough to accept different grid dimensions, we want our _leds size to be _width * _height, which are both dynamic variables whose values aren’t known at first. Instead, we can define a pointer with the syntax *_leds, which doesn’t allocate any memory, but instead points to memory. Then we allocate the memory later when we know the value of _width and _height. This happens in the constructor, using the new operator (see next section).

The public variables in this class are BOTTOM_LEFT and TOP_LEFT, which are the two different positions of the starting pixel on the grid.

public:
  static constexpr int BOTTOM_LEFT = 0;
  static constexpr int TOP_LEFT = 1;

Constructor

The class constructor sets the width, height, and start pixel. It also allocates the size of the LED array in memory that the pointer _leds points to.

LEDGrid(int width, int height, int startPixel) {
  _width = width;
  _height = height;
  _startPixel = startPixel;
  _leds = new CRGB[_width * _height];
}

[] and () operators

The [] operator lets us reference the LEDGrid object with the syntax leds[i]. This is used in the setup() function where FastLED.addLEDs is called, where we tell FastLED our LED array variable.

struct CRGB *operator[](int i) {
  return (&_leds[i]);
}

The () operator lets us reference the LEDGrid object with the syntax leds(x, y). It coverts the x and y values to the corresponding index on the one-dimensional FastLED _leds array.

struct CRGB &operator()(int x, int y) {
  return _leds[_xyCoordsToIndex(x, y)];
}

Convert x y coordinates to an index

The LEDs are wired as one long strand that snakes back and forth. In order to reference this strand with x, y coordinates, we need to calculate the index. The equation varies slightly depending on the row and the start pixel.

The core equation to convert x, y to one long strand is y * width + x. But, we need to flip the x horizontally for odd rows because the strand snakes back and forth. And also, if the starting pixel is at the top, we need to flip all the y values vertically.

int _xyCoordsToIndex(int x, int y) {
  // For odd rows, flip the x value horizontally
  if (y % 2 == 1) {
    x = _flipHorizontal(x);
  }
 
  // If the start pixel is at the top, flip the y value vertically
  if (_startPixel == TOP_LEFT) {
    y = _flipVertical(y);
  }
 
  return y * _width + x;
}
 
// Given a column, returns the opposite column horizontally
int _flipHorizontal(int x) { return _width - x - 1; }
 
// Given a row, returns the opposite row vertically
int _flipVertical(int y) { return _height - y - 1; }
Link to original

How the button code works

This page will walk through all the parts of the code that have to do with reading and handling the button state. The rest of the code related to the clock and LED functionality will be skipped on this page but covered on the other pages in this section.

Button code structure

Include the Button.h class file in this repository.

#include "Button.h"

Note: .ino files don’t need to be included. All .ino files are automatically concatenated together into one big file when you compile.

Define the pin number the button is soldered to on the ESP32.

// Button variables
#define BUTTON_PIN 15 // pin D15 on the ESP32

Create an instance of the button class named button, tell it the pin number we defined.

Button button(BUTTON_PIN);

The setupButton function configures the specified pin to behave as an input.

void setup() {
  // Tell the button object which pin to read from
  button.setupButton();
 
  // ...
}

On every iteration of the loop, the handleButtonState function reads the current state of the button and checks if a click or a long press has occurred, and if one has, it handles it by calling the necessary functions in the clock code. The code within the function is explained further below.

void loop() {
  // ...
  
  // Read the button state from the pin and check for a click or a long press
  handleButtonState();
 
  // ...
}

button.ino

handleButtonState()

Here’s the whole function. We’ll go through each piece one at a time next.

void handleButtonState() {
  // Read the value from the data pin
  button.update();
 
  if (button.longPressed) {
    // Go to next mode when button is long pressed
    c.mode = (c.mode + 1) % NUM_MODES;
  }
 
  if (button.clicked) {
    // Increment the digit according to which mode we're in
    if (c.mode == EDIT_HOUR) {
      c.hour++;
      c.hour %= 24;
    } else if (c.mode == EDIT_MINUTE_DIGIT_1) {
      c.minute += 10;
      c.minute %= 60;
    } else if (c.mode == EDIT_MINUTE_DIGIT_2) {
      c.minute++;
      if (c.minute % 10 == 0) {
        // If the minute wraps from 9 -> 10, it increments the left minute
        // digit, which was already set in the last edit mode, so subtract 10 to
        // keep the left minute digit the same
        c.minute -= 10;
      }
      c.minute %= 60;
    }
 
    // Tell the clock object that this is the new time
    if (c.mode != SHOW_TIME) {
      c.setNewTime(c.hour, c.minute);
    }
  }
}

On the first line, update is called, (a function defined on the Button class, and is explained further below) which reads the state of the button from the pin on the ESP32 and determines if a click or a long press occurred.

button.update();

Handle a long press

If a long press occurred, we go to the next mode. Long pressing over and over will continuously cycle through all 4 modes: SHOW_TIME, EDIT_HOUR, EDIT_MINUTE_DIGIT_1, EDIT_MINUTE_DIGIT_2. % NUM_MODES is used to wrap back to 0 every time we reach the last mode. 0 -> 1 -> 2 -> 3 -> 0 -> etc

if (button.longPressed) {
  // Go to next mode when button is long pressed
  c.mode = (c.mode + 1) % NUM_MODES;
}

Handle a click

If a click occurred, figure out which edit mode we’re in. (If we’re not in edit mode, it will run through all this code without anything happening.) In each if block, edit the digit: hour digit, left minute digit, etc. Then, in any edit mode, set that new time with c.setNewTime.

if (button.clicked) {
  if (c.mode == EDIT_HOUR) {
    // ...
  } else if (c.mode == EDIT_MINUTE_DIGIT_1) {
    // ...
  } else if (c.mode == EDIT_MINUTE_DIGIT_2) {
    // ...
  }
  if (c.mode != SHOW_TIME) {
    c.setNewTime(c.hour, c.minute);
  }
}

Jumping in to what happens within one of the if blocks, increment the hour digit, then use %= 24 to wrap back to 0 every time 24 is reached (24 % 24 = 0).

if (c.mode == EDIT_HOUR) {
  c.hour++;
  c.hour %= 24;
} ...

The right-hand-side minute digit has one extra step because when that right digit wraps from 9 -> 0, the minute value increments from 9 -> 10, which will also increase the left minute digit by 1. But we already set the left minute in the previous edit mode, so we want to keep it the same as it was. So we handle this special case by subtracting 10 every time the digit wraps from 9 -> 0, ie. every time the new incremented value is divisible by 10 (% 10).

c.minute++;
if (c.minute % 10 == 0) {
  c.minute -= 10;
}
c.minute %= 60;

Button.h class

The Button class includes the Timer library and one global variable:

#include "Timer.h"
 
#define LONG_PRESS_DURATION 2000 // Hold button for 2 seconds to long press

Timer.h

This is a simple struct we’ll use to time a long press. Here is the definition of the class, which is in the file Timer.h.

struct Timer {
  unsigned long totalCycleTime;
  unsigned long lastCycleTime;
  void reset() { lastCycleTime = millis(); };
  bool complete() { return (millis() - lastCycleTime) > totalCycleTime; };
};

Example Timer usage: (this is not rainbow clock code)

Timer myTimer = {10000}; // Set up a timer to last 10 seconds (10000 milliseconds)
 
myTimer.reset(); // Start the timer
 
if (myTimer.complete()) {
 
  // ... Do something here ...
 
  myTimer.reset(); // Start the timer again (if you want)
}

Button class variables and constructor

The timer we’ll use to time a long press is set up as the private variable _longPressTimer on the button class. Other private variables include pin number, the previous button state, and a flag to indicate if a long press was triggered.

class Button {
 
private:
  int _pin;                         // Data pin on the ESP32
  int _prevState = BUTTON_UP;       // The state of the button in the last loop
  bool _longPressTriggered = false; // Prevents a click from triggering when
                                    // lifting the button up after a long press
  Timer _longPressTimer = {LONG_PRESS_DURATION}; // For timing a long press

Public variables include the two button states LOW and HIGH which represent voltages. In this case, the button reads as LOW when it’s held down, and HIGH when it’s not held. clicked and longPressed are the two button statuses to be referenced outside this class to check if the button was just clicked or long pressed.

public:
  static constexpr int BUTTON_DOWN = LOW;
  static constexpr int BUTTON_UP = HIGH;
 
  bool clicked = false;     // Sets to true when button is clicked
  bool longPressed = false; // Sets to true when button is lock pressed

When the button is constructed, the pin number on the ESP32 is passed in. The setupButton is called from the main setup() function to orient the pin as an input pin.

Button(int pin) { _pin = pin; }
 
void setupButton() { pinMode(_pin, INPUT_PULLUP); }

Update the button status

The purpose of the update() function is to set the public variables clicked and longPressed so that somewhere else in code can reference them, eg. with if (button.clicked) {, and cause things to happen after button interactions.

The first thing that happens is we read the signal from the pin. This sets the state to whether the button is up or down right now.

void update() {
  int state = digitalRead(_pin); // Read the signal from the pin

Reset the click and long press status to false. If a click or a long press happened in the previous iteration of the loop, then one of these will be true. Reset them to false so we don’t trigger another click or long press.

  // Reset the click and long press status
  clicked = false;
  longPressed = false;

There are three button conditions being checked for:

  1. The button is down and was previously up: Could be a click or a long press. We won’t know until the button is released. Start the timer.

      if (state == BUTTON_DOWN && _prevState == BUTTON_UP) {
        _longPressTimer.reset();
  2. The button is down and it’s been down for 2 seconds: Trigger a long press. Also start the timer over. Also set the _longPressTriggered flag to true until the button is lifted. This flag prevents a click from triggering when the user releases the button from this long press.

      } else if (state == BUTTON_DOWN && _longPressTimer.complete()) {
        longPressed = true;
        _longPressTimer.reset();
        _longPressTriggered = true; // Set this to true until button is lifted
  3. The button is up and was previously down: Trigger a click. Unless its the user releasing the button after just long pressing. In that case, don’t trigger a click. Check if it was a long press by checking _longPressTriggered, and if so, reset it back to false to indicate the long press is complete. If it wasn’t a long press, proceed with triggering a click.

    } else if (state == BUTTON_UP && _prevState == BUTTON_DOWN) {
      if (_longPressTriggered) {
        _longPressTriggered = false;
      } else {
        clicked = true;
      }
    }

Next time around, this state will the previous state.

  _prevState = state;
}
Link to original

More

Other resources

Arduino examples

  • The Arduino IDE comes with loads of well explained examples. In the Arduino IDE go to File -> Examples. Most libraries you install like FastLED also include examples that show up in the same File -> Examples menu after you install the library.

LEDs

Inspiration / Other projects

Link to original

Troubleshooting & FAQ

Troubleshooting

Searching for specific problems

There are tons of articles, forum posts, and videos out there about Arduino code and ESP32s. If you’re running into a code problem try searching for the problem or error you’re getting with the word “Arduino”. (Even though we’re uploading code to an ESP32, we’re still writing in the “Arduino” programming language.)

Code wont upload

  • Try cycling power: Unplug and replug USB cable.
  • Check that you have the ESP32 board selected in the Tools -> Port menu. If it is not showing up in the menu, see the next section.

ESP32 not showing up in Port menu in Arduino IDE

  • Try cycling power: Unplug and replug USB cable.
  • Try a different USB cable. The cable that came with your clock should work, but strange things happen. Make sure the cable is not power-only and is data sync capable.
  • The LEDs might be using too much power. Try disconnecting the LEDs from the board by opening the clock and disconnecting the plastic LED connector. Then unplug the USB from your computer and plug it back in. If this fixes it you might have uploaded code that was resulting in the LEDs using too much power, for example turning all the LEDs on white at full brightness. Try uploading new code that uses less power (eg. lower brightness, fewer LEDs on, or a lower power-consuming color like Red)
  • Double check all the steps in this guide for installing the ESP32 on the Arduino IDE: Installing ESP32 board in Arduino IDE 1 and Installing ESP32 board in Arduino IDE 2
  • You may need to install ESP32 drivers.

Pixels are flipped upside down or backwards

  • If you are using the clock code in this repository, you might need to change the START_PIXEL variable from LEDGrid::TOP_LEFT to LEDGrid::BOTTOM_LEFT or vice versa. See LED grid layout for more details.
  • You may need to open the clock and physically rotate the LED Grid. The start pixel should be on the top left or bottom left of the clock as you are looking at the front of the grid.

FAQ

What programming language does Arduino use?

Arduino is its own programming language based on C and C++. The Arduino language has its own built-in functions that are specific to hardware coding like delay(), pinMode() (see a full list of built-in functions) and Arduino programs must follow a specific code structure with two functions: setup() (which runs once at the beginning) and loop() (which runs over and over forever). For more details about the code structure visit this page: Basic code and file structure

Link to original