Desk clock on an Arduino Uno
A DS3231 over I2C and a TM1637 on two wires that are not I2C. BCD registers read in one transfer, a colon that blinks every second, and a logic analyzer on the bus decoding every byte. The whole build, an Arduino Uno and 3 more parts, 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.
Teaching? Assign it to your class in one click.
Intermediate About 25 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $35. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino DS3231 Clock With a TM1637 Display
What you'll learn
- I2C and the fixed DS3231 address 0x68
- Why TM1637 looks like I2C but is not
- Binary coded decimal in clock registers
- Updating a display only when the second changes
- Reading a status flag that says the time is a guess
- Difficulty: Intermediate
- Time: About 25 minutes
- Board: Arduino Uno R3
- Parts: 4
How it works
A DS3231 clock chip keeps the time. The Uno reads it over I2C on A4 (SDA) and A5 (SCL). Once per second change, the hours and minutes are sent to a four-digit TM1637 display on pins 4 (CLK) and 5 (DIO).
These look like the same kind of bus, but they are not. The DS3231 has an address, 0x68. The TM1637 has none, and it takes bits least significant first. That is why the two parts get separate pins.
The colon between the digits blinks on even seconds, so you can see the clock is running. On the first second of each minute, the sketch also prints the full date and time to the Serial Monitor.
Parts list
4 parts, plus the jumper wires and a bench instrument. The parts retail for about $35; on Mokxi they're free, and so are the instruments.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Real-time clock, DS3231In Christmas Lights Kit.
- 1 × Four-digit display, TM1637In Pinball Table Kit, Spaceship Panel Kit.
- 1 × Logic analyzer, four channels
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Four-digit display, TM1637 pin DIO | Arduino Uno R3 pin 5 |
| Four-digit display, TM1637 pin CLK | Arduino Uno R3 pin 4 |
| Real-time clock, DS3231 pin VCC | Arduino Uno R3 pin 5V |
| Four-digit display, TM1637 pin VCC | Arduino Uno R3 pin 5V |
| Real-time clock, DS3231 pin GND | Arduino Uno R3 pin GND |
| Four-digit display, TM1637 pin GND | Arduino Uno R3 pin GND |
| Logic analyzer, four channels pin GND | Arduino Uno R3 pin GND |
| Real-time clock, DS3231 pin SDA | Arduino Uno R3 pin A4 |
| Logic analyzer, four channels pin D1 | Arduino Uno R3 pin A4 |
| Real-time clock, DS3231 pin SCL | Arduino Uno R3 pin A5 |
| Logic analyzer, four channels pin D0 | Arduino Uno R3 pin A5 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Desk clock: a DS3231 over I2C, the time on a four-digit TM1637.
//
// A5 -> SCL A4 -> SDA the clock module carries its own pull-ups
// pin 4 -> CLK pin 5 -> DIO the display's two wires
//
// Two two-wire devices on one board, and they are two *different* two-wire
// buses, which is the thing worth taking away from this circuit.
//
// # The clock speaks I2C
//
// Address 0x68, fixed; there is no address pin on a DS3231. Seven registers
// hold the time and they are read in **one** transfer, because reading them one
// at a time lets the clock tick between the minutes and the seconds, and
// 10:00:59 comes out as 10:01:59 once an hour for ever.
//
// Every one of those registers is binary coded decimal: two decimal digits, one
// per nibble, so twenty-five seconds is 0x25 and not 25. That is the whole of
// the DS3231 for most sketches and it is the one thing everybody gets wrong
// once. The driver converts both ways so nothing above it sees a BCD byte.
//
// # The display does not
//
// The TM1637's bus looks like I2C (same START, same STOP, an acknowledge on a
// ninth clock), and it is not, in two ways that break any attempt to drive it
// with a Wire library:
//
// * there is no address, so the first byte is a command and the chip answers
// everything on the wire;
// * the bytes go least significant bit first, where I2C is most significant
// first. Get that wrong and it lights a plausible set of wrong segments.
//
// # The colon
//
// It is bit 7 of the *second* digit and nowhere else. That is this module's
// wiring, not the chip's, which is why every library has a special case for it.
// Here it blinks once a second, which is what tells you the clock is running
// rather than the sketch having stopped with a number on the screen.
//
// # The oscillator-stop flag
//
// Bit 7 of the status register comes up set after power, and it means "this
// time cannot be trusted". A real module keeps time on its coin cell and clears
// it when it is set; Mokxi's part has no battery, so it powers up at whatever
// the `start` property says with the flag raised. The sketch prints it once,
// because on a real bench that flag is how you find out somebody's cell is
// flat.
#include "mokxi_ds3231.h"
#include "mokxi_i2c.h"
#include "mokxi_tm1637.h"
const int SCL_PIN = A5;
const int SDA_PIN = A4;
const int TM_CLK = 4;
const int TM_DIO = 5;
SoftI2c bus(SCL_PIN, SDA_PIN);
Ds3231 rtc(bus);
Tm1637 panel(TM_CLK, TM_DIO);
unsigned char digits[4];
int lastSecond = -1;
// Two digits with the leading zero, which is what a clock wants everywhere.
void print2(int value) {
if (value < 10) {
Serial.print("0");
}
Serial.print(value);
}
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Uno: DS3231 desk clock on a TM1637");
bus.begin();
panel.begin();
if (!rtc.begin()) {
Serial.println("nothing answered at 0x68");
return;
}
unsigned char status = rtc.status();
Serial.print("status 0x");
Serial.print((long)status, HEX);
Serial.println((status & DS3231_OSF) ? " (OSF set: the time is a guess)" : " (time trusted)");
// Alarm 1 on the zeroth second of every minute, so the sketch has something
// to notice besides the digits changing.
rtc.setAlarm1Seconds(0);
rtc.writeControl(DS3231_INTCN | DS3231_A1IE);
rtc.alarmFired(DS3231_A1F);
}
void loop() {
Ds3231Time now;
if (!rtc.now(now)) {
Serial.println("read failed");
delay(500);
return;
}
if ((int)now.seconds != lastSecond) {
lastSecond = (int)now.seconds;
Tm1637::clock(now.hours, now.minutes, digits);
// The colon blinks on the even second, which is how you can see it running.
Tm1637::setColon(digits, (now.seconds % 2) == 0);
panel.show(digits, 5);
if (now.seconds == 0) {
Serial.print(now.year);
Serial.print("-");
print2((int)now.month);
Serial.print("-");
print2((int)now.date);
Serial.print(" ");
print2((int)now.hours);
Serial.print(":");
print2((int)now.minutes);
Serial.print(":");
print2((int)now.seconds);
Serial.print(" chip ");
Serial.print(rtc.temperatureQuarters() / 4);
Serial.print(" C");
Serial.println(rtc.alarmFired(DS3231_A1F) ? " [alarm 1]" : "");
}
}
delay(50);
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Starts the serial port, the I2C bus and the display. It then checks that something answers at 0x68 and prints the status register, noting whether the oscillator-stop flag is set. Last, it sets alarm 1 to fire at second zero of each minute, so the sketch has an event to report.
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Uno: DS3231 desk clock on a TM1637");
bus.begin();
panel.begin();
if (!rtc.begin()) {
Serial.println("nothing answered at 0x68");
return;
}
unsigned char status = rtc.status();
Serial.print("status 0x");
Serial.print((long)status, HEX);
Serial.println((status & DS3231_OSF) ? " (OSF set: the time is a guess)" : " (time trusted)");
// Alarm 1 on the zeroth second of every minute, so the sketch has something
// to notice besides the digits changing.
rtc.setAlarm1Seconds(0);
rtc.writeControl(DS3231_INTCN | DS3231_A1IE);
rtc.alarmFired(DS3231_A1F);
}loop sketch.ino
Reads the time. When the seconds value is different from last time, it builds the four digits from hours and minutes, sets the colon on for even seconds, and sends them to the display. On second zero it also prints the date, time, chip temperature and whether alarm 1 fired.
void loop() {
Ds3231Time now;
if (!rtc.now(now)) {
Serial.println("read failed");
delay(500);
return;
}
if ((int)now.seconds != lastSecond) {
lastSecond = (int)now.seconds;
Tm1637::clock(now.hours, now.minutes, digits);
// The colon blinks on the even second, which is how you can see it running.
Tm1637::setColon(digits, (now.seconds % 2) == 0);
panel.show(digits, 5);
if (now.seconds == 0) {
Serial.print(now.year);
Serial.print("-");
print2((int)now.month);
Serial.print("-");
print2((int)now.date);
Serial.print(" ");
print2((int)now.hours);
Serial.print(":");
print2((int)now.minutes);
Serial.print(":");
print2((int)now.seconds);
Serial.print(" chip ");
// ... 7 more lines in the editorprint2 sketch.ino
Prints a number with a leading zero when it is below 10, so 7 shows as 07. Clocks look wrong without it, and the date and time lines in the Serial Monitor use it.
// Two digits with the leading zero, which is what a clock wants everywhere.
void print2(int value) {
if (value < 10) {
Serial.print("0");
}
Serial.print(value);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- Serial Monitor says nothing answered at 0x68 This project
Check that SDA goes to A4 and SCL goes to A5, and that the clock has 5V and GND. - The display stays dark This project
CLK must go to pin 4 and DIO to pin 5, matchingTM_CLKandTM_DIO. - The Serial Monitor says the time is a guess This project
That flag is normal here. This part has no battery, so it starts with the flag set. - The time shows odd numbers, such as 0x23 printed as 35, when you read it. Real-time clock
All time registers are BCD, not plain binary. Convert each byte with (b >> 4) * 10 + (b & 0x0F) to read, and the reverse to write. A library such as RTClib does this for you. - The clock does not respond, or the I2C bus hangs. Real-time clock
The DS3231 address is 0x68. Wire SDA and SCL to the right pins with pull-ups, about 4.7k, to the supply. Many breakout boards already have them. Join the grounds. - The display does nothing. Four-digit display
Connect CLK and DIO to two digital pins, plus VCC and GND. It is not I2C, so no address and no Wire library. Use a TM1637 library and give it the same two pins. - Digits look wrong or in a strange order. Four-digit display
Check the library's segment mapping and digit order. The colon is bit 7 of the second digit. Brightness is set separately and can be 0 to 7. - The traces show random noise or nothing useful. Logic analyzer
Connect the analyzer's ground to the circuit's ground. Without a shared reference the probes float. Also check each channel is on the right wire and the logic level matches (3.3 V or 5 V).
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.