Built-in project · ESP32-C3-DevKitM-1

Binary clock on an ESP32-C3

An ESP32-C3 writes the hours, minutes and seconds as three rows of binary lamps on a 128x64 OLED. The whole build, an ESP32-C3 and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

IntermediateRuns in your browser. Free, and no account needed.

Binary clock · ESP32-C3-DevKitM-1live0.000 s 0.00x
Click to open it in the editor
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32-C3-DevKitM-1
// Binary clock: the time in ones and zeros on a 128x64 OLED.
//
// Wiring, module to board:
//   SCL -> GPIO 5      SDA -> GPIO 4      VCC -> 3V3      GND -> GND
//
// Three rows of lamps: five bits of hours, six of minutes, six of seconds,
// most significant on the left. A filled circle is a one. Read a row as a
// binary number and that is the number under it, which the sketch also prints
// in decimal so the reading can be checked.
//
// The time comes from millis() and nothing else. There is no clock chip here,
// so the board starts at the time set below and counts from there; a real one
// would read a DS3231 over the same two wires this display is on.
//
// A whole second is a long time for a board, so the loop sleeps in 50 ms steps
// and only repaints when the second changes. The driver sends the
// eight-column blocks that changed rather than the whole kilobyte, so a tick
// that moves three lamps costs a few dozen bytes on the bus.

#include "mokxi_i2c.h"
#include "mokxi_ssd1306.h"

const uint8_t SCL_PIN = 5;
const uint8_t SDA_PIN = 4;

SoftI2c bus(SCL_PIN, SDA_PIN, 400);
Ssd1306 oled(bus, SSD1306_ADDRESS);

// Where the clock starts, since nothing here keeps time across a reset.
const uint32_t START_HOUR = 9;
const uint32_t START_MINUTE = 41;
const uint32_t START_SECOND = 0;

const int16_t ROW_Y[3] = {22, 38, 54};
const uint8_t ROW_BITS[3] = {5, 6, 6};
const char *const ROW_NAME[3] = {"H", "M", "S"};

const int16_t DOT_R = 5;
const int16_t DOT_STEP = 16;
const int16_t DOT_RIGHT = 118;

uint32_t shown = 0xffffffffu;

static void furniture() {
  oled.clear();
  oled.text(0, 2, "MOKXI", 1);
  oled.hLine(0, 12, 128);
  for (int r = 0; r < 3; r++) {
    oled.text(0, (int16_t)(ROW_Y[r] - 3), ROW_NAME[r], 1);
  }
}

// One row of lamps, most significant bit on the left.
static void row(int r, uint32_t value) {
  uint8_t bits = ROW_BITS[r];
  for (uint8_t i = 0; i < bits; i++) {
    int16_t cx = (int16_t)(DOT_RIGHT - i * DOT_STEP);
    bool on = ((value >> i) & 1u) != 0u;
    oled.fillCircle(cx, ROW_Y[r], DOT_R, false);
    if (on) {
      oled.fillCircle(cx, ROW_Y[r], DOT_R, true);
    } else {
      oled.circle(cx, ROW_Y[r], DOT_R, true);
    }
  }
}

static void twoDigits(int16_t x, int16_t y, uint32_t value) {
  oled.number(x, y, (long)(value / 10u), 1, true, true);
  oled.number((int16_t)(x + 6), y, (long)(value % 10u), 1, true, true);
}

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi ESP32-C3: binary clock on an SSD1306");
  if (!oled.begin()) {
    Serial.println("no ACK from the display: check SCL, SDA and the supply");
    return;
  }
  furniture();
  oled.display();
}

void loop() {
  uint32_t elapsed = millis() / 1000u;
  uint32_t total =
      (START_HOUR * 3600u + START_MINUTE * 60u + START_SECOND + elapsed) % 86400u;
  if (total == shown) {
    delay(50);
    return;
  }
  shown = total;

  uint32_t hours = total / 3600u;
  uint32_t minutes = (total / 60u) % 60u;
  uint32_t seconds = total % 60u;

  row(0, hours);
  row(1, minutes);
  row(2, seconds);

  twoDigits(72, 2, hours);
  oled.text(84, 2, ":", 1, true, true);
  twoDigits(90, 2, minutes);
  oled.text(102, 2, ":", 1, true, true);
  twoDigits(108, 2, seconds);
  oled.display();

  if (seconds == 0u) {
    Serial.print("tick ");
    Serial.print(hours);
    Serial.print(":");
    Serial.println(minutes);
  }
}

Parts list

3 parts, plus the jumper wires. Every one is in the editor's parts bin.

How it is wired

4 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • ESP32-C3-DevKitM-1 pin 3V3; OLED display, 128x64 pin VCC
  • Ground: ESP32-C3-DevKitM-1 pin GND; OLED display, 128x64 pin GND
  • ESP32-C3-DevKitM-1 pin 4; OLED display, 128x64 pin SDA
  • ESP32-C3-DevKitM-1 pin 5; OLED display, 128x64 pin SCL

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.