A Watch for Nerds: the Binary Watch

by NewsonsElectronics in Circuits > Arduino

44 Views, 0 Favorites, 0 Comments

A Watch for Nerds: the Binary Watch

Use English (2).png
A Watch for Nerds: The Binary Watch

Have you ever wanted to impress your friends? Now you can build your very own binary watch and read the time in binary code.


Link to the full build video : https://youtu.be/bifUcp3U8ec

Supplies

vlcsnap-2026-08-13-14h14m00s440.jpg

Here is a list of the main components for this PCB binary watch build.


Main Components

  1. 1 × ATMEGA328P-AU — U1
  2. 1 × DS3231MZ RTC — U2
  3. 1 × CH340C USB-to-Serial IC — U3
  4. 1 × TP4056-42-ESOP8 LiPo Charger IC — U4
  5. 1 × CSTCE16M0V53-R0 16 MHz Resonator — Y1

LEDs

  1. 18 × WS2812B-V6 Addressable RGB LEDs — D1–D18
  2. 1 × PMEG4010BEA Schottky Diode — D19
  3. 1 × KT-0805G Green LED — D20
  4. 1 × KT-0805R Red LED — D21

Resistors

  1. 1 × 1.2 kΩ Resistor — BAT1
  2. 4 × 4.7 kΩ Resistors — R2–R5
  3. 1 × 10 kΩ Resistor — R6
  4. 2 × 1 kΩ Resistors — R7–R8
  5. 1 × 2.7 kΩ Resistor — R9

Capacitors

  1. 4 × 0.1 µF (100 nF) Capacitors — C1–C4

Connectors & Controls

  1. 1 × USB-C 16-Pin Receptacle (USB 2.0) — J2
  2. 2 × EVQPUJ02K Tactile Push Buttons — SW1–SW2
  3. 1 × LiPo Battery — BT1

PCB Board Manufacturing

vlcsnap-2026-08-13-14h14m35s331.jpg
vlcsnap-2026-08-13-14h13m55s232.jpg

I had the PCB manufactured by JLCPCB, a company I have used both in Canada and China. Their website is very user-friendly, and they offer a wide range of services, including PCB manufacturing, SMT assembly, and PCB stencils.

I have uploaded all of the Gerber files needed to manufacture the PCB. I have also included the BOM (Bill of Materials) and component placement/position files if you would like JLCPCB to assemble the components for you. JLCPCB allows you to upload the BOM and placement files directly when ordering PCB assembly.

JLCPCB Website:

https://jlcpcb.com/

PCB Assembly

Schematic.jpg
vlcsnap-2026-08-13-14h12m35s747.jpg
vlcsnap-2026-08-13-14h12m44s604.jpg
vlcsnap-2026-08-13-14h14m00s440.jpg
vlcsnap-2026-08-13-14h14m11s640.jpg

First, follow the schematic to determine the correct component placements. I used liquid solder flux on the pads first, then carefully placed each component in the correct location using tweezers.

Note that the microchips have a white dot on the silkscreen indicating the location of Pin 1. Be sure to orient the chips correctly before soldering.

Once all the components were correctly positioned, I used a hot-air rework gun to heat the solder and solder the components into place.

Uploading the Code

vlcsnap-2026-08-13-14h14m28s171.jpg

Once all the components are soldered onto the board, we can program the watch using the Arduino IDE.

Important: Because I am repurposing the USB-C port for multiple functions, there is only one correct way to connect the USB-C cable to the board for programming.

First, make sure the CH340C USB-to-serial drivers are installed on your computer. You can download the official Windows driver here: CH340/CH341 Windows Driver — WCH

Once the drivers are installed, connect the USB-C cable in the correct orientation. Windows should play its connection sound, indicating that it has successfully detected the CH340C USB-to-serial (TTL) chip.

You can then open the Arduino IDE, select the appropriate board and COM port, and upload the following code to the watch.

Note!: Before uploading the code, install the following libraries through the Arduino IDE Library Manager:

  1. RTClib — by Adafruit
  2. Adafruit NeoPixel — by Adafruit


// BinaryWatch
// By Newson's Electronics
// August, 2026

#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_NeoPixel.h>
#include <avr/sleep.h>
#include <avr/power.h>

#define LED_PIN 4
#define NUM_LEDS 18
#define BUTTON_UP 2
#define BUTTON_NEXT 3

int brightness = 1;

Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
RTC_DS3231 rtc;

// ================= COLORS =================

uint32_t RED = 0xFF0000;
uint32_t GREEN = 0x00FF00;
uint32_t YELLOW = 0xFFFF00;
uint32_t BLUE = 0x0000FF; // low voltage

// ================= EDIT MODE =================

bool changeMode = false;

bool charge = false;

int editMode = 0;
// 0 = seconds
// 1 = minutes
// 2 = hours

int editSeconds;
int editMinutes;
int editHours;

// ================= BUTTON =================

unsigned long buttonTimer = 0;
bool buttonHeld = false;

// ================= FLASH =================

unsigned long flashTimer = 0;
bool flashState = true;

// ================= SLEEP =================

unsigned long lastActivity = 0;
const unsigned long sleepDelay = 60000; //ms
const unsigned long flashDelay = 100; //ms

unsigned long d2SleepTimer = 0;
bool d2SleepHeld = false;


// ================= WAKE FLAG =================

volatile bool wakeFlag = false;

void setup() {

Serial.begin(9600);


long voltage = readVcc();





pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_NEXT, INPUT_PULLUP);
//digitalWrite(BUTTON_UP, HIGH);
//digitalWrite(BUTTON_NEXT, HIGH);


strip.begin();
strip.setBrightness(brightness);
strip.show();

if (!rtc.begin()) {
Serial.println("RTC NOT FOUND");
}

// Set RTC from computer upload time
// COMMENT THIS AFTER FIRST UPLOAD

//rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

// D2 wakes from sleep

attachInterrupt(digitalPinToInterrupt(BUTTON_UP), wakeUp, FALLING);

lastActivity = millis();
}

void loop() {

// Enter sleep after 60 seconds idle

if (!changeMode && millis() - lastActivity > sleepDelay) {
enterSleep();
}


// Always check buttons

if (changeMode == false) {
checkBrightness();
checkEnterMode();
checkD2Sleep();
} else {
checkD2Increment();
checkD3Next();
checkD3Save();
}


// Update clock only once per second

static unsigned long clockTimer = 0;

if ((millis() - clockTimer >= 1000)||changeMode == true) {

clockTimer = millis();

DateTime now = rtc.now();




if (changeMode == false) {

displayClock(
now.hour(),
now.minute(),
now.second());


printTime(
now.hour(),
now.minute(),
now.second());

} else {

displayEditMode();


printTime(
editHours,
editMinutes,
editSeconds);
}
}

delay(10);
}


// ================= D2 HOLD TO SLEEP =================

void checkD2Sleep()
{
// Only work in normal mode
if (changeMode)
return;

bool d2 = digitalRead(BUTTON_UP);

// D2 is pressed
if (d2 == LOW)
{
// Start timing the hold
if (!d2SleepHeld)
{
d2SleepTimer = millis();
d2SleepHeld = true;
}

// Held for 1 second
if (millis() - d2SleepTimer >= 1000)
{
d2SleepHeld = false;

Serial.println("D2 HELD - GOING TO SLEEP");
strip.clear();
strip.show();
d2=0;
delay(2000);

// Reset activity timer
lastActivity = millis();

enterSleep();
}
}
else
{
// Button released
d2SleepHeld = false;
}
}

// ================= BATTERY VOLTAGE =================
// Reads LiPo voltage using ATmega328P internal 1.1V reference
// Returns voltage in millivolts

long readVcc()
{
// Save current ADC settings
uint8_t oldADMUX = ADMUX;
uint8_t oldADCSRA = ADCSRA;

// Enable ADC power
power_adc_enable();

// Enable ADC
ADCSRA |= _BV(ADEN);

// Select internal 1.1V reference
ADMUX = _BV(REFS0) |
_BV(MUX3) |
_BV(MUX2) |
_BV(MUX1);

// Allow internal reference to stabilize
delay(10);

// Clear ADC interrupt flag
ADCSRA |= _BV(ADIF);

// Start conversion
ADCSRA |= _BV(ADSC);

// Wait for conversion to finish
while (ADCSRA & _BV(ADSC))
{
}

// Read ADC result
uint8_t low = ADCL;
uint8_t high = ADCH;

uint16_t result = ((uint16_t)high << 8) | low;

// Disable ADC
ADCSRA &= ~_BV(ADEN);

// Disable ADC power
power_adc_disable();

// Restore previous ADC settings
ADMUX = oldADMUX;
ADCSRA = oldADCSRA;

// Calculate Vcc in millivolts
long vcc = 1125300L / result;

Serial.print("Battery Voltage: ");
Serial.print(vcc / 1000.0);
Serial.println(" V");


charge = (vcc < 3400); // low battery below 3.4V

//return vcc;
}

// ================= BRIGHTNESS ADJUST NORMAL MODE =================

void checkBrightness() {

static bool lastPressed = false;

bool d2 = digitalRead(BUTTON_UP);
bool d3 = digitalRead(BUTTON_NEXT);

// Both buttons pressed together

if (d2 == LOW && d3 == LOW) {

if (!lastPressed) {

brightness += 1;

if (brightness > 20)
brightness = 1;

strip.setBrightness(brightness);
strip.show();

Serial.print("Brightness: ");
Serial.println(brightness);

lastActivity = millis();
}

lastPressed = true;

} else {

lastPressed = false;
}
}

// ================= DISPLAY CLOCK =================

void displayClock(int h, int m, int s) {

strip.clear();

if (charge == true) { strip.setPixelColor(17, BLUE); }


// seconds

displayBinary(
s,
0,
GREEN);

// minutes

displayBinary(
m,
6,
YELLOW);

// hours

displayBinary(
h,
12,
RED);

strip.show();
}
// ================= ENTER EDIT MODE =================

void checkEnterMode() {

if (digitalRead(BUTTON_NEXT) == LOW) {

if (!buttonHeld) {

buttonTimer = millis();
buttonHeld = true;
}

if (millis() - buttonTimer > 1000) {

DateTime now = rtc.now();

editHours = now.hour();
editMinutes = now.minute();
editSeconds = now.second();

editMode = 0;

changeMode = true;

buttonHeld = false;

lastActivity = millis();

Serial.println("EDIT MODE");
}

} else {

buttonHeld = false;
}
}

// ================= EDIT DISPLAY =================

void displayEditMode() {

if (millis() - flashTimer > flashDelay) {

flashTimer = millis();

flashState = !flashState;
}

strip.clear();

// Display rows not being edited

if (editMode != 0)
displayBinary(editSeconds, 0, GREEN);

if (editMode != 1)
displayBinary(editMinutes, 6, YELLOW);

if (editMode != 2)
displayBinary(editHours, 12, RED);

// Flash selected row

if (flashState) {

if (editMode == 0)
displayBinary(editSeconds, 0, GREEN);

if (editMode == 1)
displayBinary(editMinutes, 6, YELLOW);

if (editMode == 2)
displayBinary(editHours, 12, RED);
}

strip.show();
}

// ================= D2 INCREMENT =================

void checkD2Increment() {

static bool lastState = HIGH;

bool current = digitalRead(BUTTON_UP);

if (lastState == HIGH && current == LOW) {

lastActivity = millis();

if (editMode == 0) {

editSeconds++;

if (editSeconds > 59)
editSeconds = 0;
}

if (editMode == 1) {

editMinutes++;

if (editMinutes > 59)
editMinutes = 0;
}

if (editMode == 2) {

editHours++;

if (editHours > 23)
editHours = 0;
}
}

lastState = current;
}

// ================= D3 NEXT =================

void checkD3Next() {

static bool lastState = HIGH;

bool current = digitalRead(BUTTON_NEXT);

if (lastState == HIGH && current == LOW) {

lastActivity = millis();

editMode++;

if (editMode > 2)
editMode = 0;

if (editMode == 0)
Serial.println("EDIT SECONDS");

if (editMode == 1)
Serial.println("EDIT MINUTES");

if (editMode == 2)
Serial.println("EDIT HOURS");
}

lastState = current;
}

// ================= D3 SAVE =================

void checkD3Save() {

if (digitalRead(BUTTON_NEXT) == LOW) {

if (!buttonHeld) {

buttonTimer = millis();

buttonHeld = true;
}

if (millis() - buttonTimer > 1000) {

DateTime now = rtc.now();

rtc.adjust(DateTime(
now.year(),
now.month(),
now.day(),
editHours,
editMinutes,
editSeconds));

changeMode = false;

buttonHeld = false;

lastActivity = millis();

Serial.println("TIME SAVED");
}

} else {

buttonHeld = false;
}
}

// ================= BINARY LED DISPLAY =================

void displayBinary(int value, int startLED, uint32_t color) {

for (int bit = 0; bit < 6; bit++) {

if (value & (1 << bit)) {

strip.setPixelColor(
startLED + bit,
color);
}
}
}

// ================= SERIAL TIME =================

void printTime(int h, int m, int s) {

Serial.print("Time: ");

if (h < 10)
Serial.print("0");

Serial.print(h);

Serial.print(":");

if (m < 10)
Serial.print("0");

Serial.print(m);

Serial.print(":");

if (s < 10)
Serial.print("0");

Serial.print(s);

// display time to sleep
unsigned long sleepCountdown = (sleepDelay - (millis() - lastActivity)) / 1000;

Serial.print(" Sleep in: ");
Serial.print(sleepCountdown);
Serial.println(" seconds");
}

// ================= DEEP SLEEP =================

void enterSleep()
{
// Make sure buttons are released before sleeping
if (digitalRead(BUTTON_UP) == LOW ||
digitalRead(BUTTON_NEXT) == LOW)
{
return;
}

Serial.println("GOING TO SLEEP");

strip.clear();
strip.show();

// Disable peripherals
power_adc_disable();
power_spi_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();

// Do NOT disable Timer0 because millis() and delay() need it
//power_timer0_disable();

set_sleep_mode(SLEEP_MODE_PWR_DOWN);

// Wait for button noise to settle
delay(50);

// Check again before sleeping
if (digitalRead(BUTTON_UP) == LOW ||
digitalRead(BUTTON_NEXT) == LOW)
{
power_all_enable();
return;
}

// Clear any pending interrupt
EIFR |= (1 << INTF0);

sleep_enable();

// Enter sleep
sleep_mode();

// Execution continues here after wake

sleep_disable();

// Clear interrupt again
EIFR |= (1 << INTF0);

// Restore peripherals
power_all_enable();

delay(10);

// Wait until wake button is released
while (digitalRead(BUTTON_UP) == LOW)
{
delay(10);
}

lastActivity = millis();

Serial.println("AWAKE");
readVcc();


}

// ================= WAKE INTERRUPT =================

void wakeUp() {

wakeFlag = true;
}



How the Watch Works

NO Charger!!! (1).png
vlcsnap-2026-08-13-14h13m17s072.jpg
vlcsnap-2026-08-13-14h13m37s092.jpg
vlcsnap-2026-08-13-14h13m26s723.jpg
vlcsnap-2026-08-13-14h13m32s144.jpg

The watch is controlled using the two push buttons on the right side.

Turning the Watch On and Off

The top button turns the watch on. To put the watch into sleep mode, press and hold the top button for approximately 1 second.

Setting the Time

To set the time, press and hold the bottom button for 1 second. The watch will begin flashing the row that is currently being edited:

  1. 1st row: Seconds
  2. 2nd row: Minutes
  3. 3rd row: Hours

Press the top button to increment the value for the selected row.

Once the correct time has been set, press and hold the bottom button for 1 second to save the time and return to normal watch mode.

Adjusting LED Brightness

While in normal watch mode, quickly press both buttons at the same time. Each press will increase the LED brightness. Once the maximum brightness is reached, the setting will loop back around to the dimmest level.

Low Battery Indicator

Each time the watch enters sleep mode, it checks the voltage of the LiPo battery.

If the battery voltage is detected to be too low, a blue LED in the top-left corner of the watch will turn on to indicate that the battery needs to be recharged.

How to Read the Time?

NO Charger!!! (1).png
binaryclock2.png

How to Read the Time

Each row of the watch represents one part of the time: seconds, minutes, and hours. Each row contains 6 LEDs, with each LED representing a binary value. Since the values are 1, 2, 4, 8, 16, and 32, the maximum value a row can display is 63.


To determine the number displayed in a row, add together the values of all the illuminated LEDs.

Example: If the LEDs showing 1 + 4 + 8 = 13, that row represents 13.


So, for example, if the watch displays 13 on the first row, 42 on the second row, and 18 on the third row, the time is 18:42:13.


Hope you enjoyed this project! I’m planning to develop a revised version of this clock design. It will utilise the internal 8 MHz oscillator and power MOSFETs to further cut quiescent current, extending battery life. If you have any ideas or feedback, feel free to send me a message.