Arduino E-Paper Clock

Updated 23 January 2025

This project started with a desire to have a modern digital clock that would use the natural light of the room for illumination. This means the screen is always the right brightness, looking like paper rather than a traditional screen. By using an E-paper display, blue light is not emitted meaning that the clock does not disrupt your circadian rhythm before bed. It seamlessly updates every minute, and on the hour the screen does a full refresh.

Another useful function of the clock is that it uses a supercapacitor to ensure accurate time even when unplugged for extended periods. This means the time does not have to be set again after blackouts like most other clocks.

Assumed Knowledge

 Tools

Assembly Instructions

  1. Connect the jumper wires from the Arduino’s male headers to the female headers on the E-paper module according to the schematic diagram & wiring table below.
    Some connections may need soldering, like the D13-CLK-CLK connection, since multiple devices are connected to the same pin on the Arduino. The buttons all have to be soldered as well. Keep at least 8cm on each wire connected to the button so that there is enough slack for the buttons to be placed at the back of the case.
Arduino E-Paper Display RTC Unit Buttons
5V VSYS    
3V3   VCC  
GND GND GND All buttons - bottom right pin
A4   SDA  
A5   SCL  
D2   INT  
D3     Button 1 - bottom left pin (Down)
D4     Button 2 - bottom left pin (change function from changing hours to changing minutes to exit changing time mode)
D5     Button 3 -  bottom left pin (Turn on changing time mode) 
D6 DC    
D7 BUSY    
D8     Button 4 - bottom right pin (Up)
D9 RST    
D10 CS    
D11 DIN    
D13 CLK CLK  
  1. Optionally hot glue the jumper wire connections so that they can't lose contact over time. For a more permanent setup, all the connections can be soldered.
  2. Plug the Arduino Nano into your computer and adjust the Arduino sketch.
    1. Ensure that the ZIP libraries are all installed. They are all on the Github repository below under the E-paper_Clock folder. Install them at Sketch → Include Library → Add .ZIP Library. The libraries are Low Power, GxEPD2 and U8g2 for Adafruit GFX.
    2. Ensure that the time and date variables (int year =, int month =, etc) are set to the current date and time.
       
    3. Uncomment line 59 by removing the ‘//’ from the start.

      //rtc.setTime(sec, minute, hour, day, date, month, year); //USE THIS TO INITALLY SET TIME. Once set it needs to be commented out so that it doesn't get set to this time every restart”
                 
    4. Upload the sketch. Set the board to Arduino AVR Boards → Arduino Nano.
    5. Turn the line above back into a comment by adding the ‘//’ to the start.
    6. Upload the sketch again. The clock is now programmed.
  1. 3D print the 3 case parts. They can be printed individually or all together.
  2. The main case might need a brim depending on your printer since the contact area is quite small.

    Print settings:

     

    • Filament type: PLA

    • Nozzle Temperature: 210ºC

    • Bed Temperature: 60ºC

    • Infill: 15%

    • Layer Height: 0.2mm

    • Perimeters: 3

    • Solid Layers:

      • Top: 4

      • Bottom: 4

  1. Melt the heated inserts into the 6 holes in the 3d print using a soldering iron. Video demonstration.
  2. Remove the e-paper screen protector and position the screen in the 3d print with the screen opening. Hot glue the corners of the back of the screen to the 3d print.
  1. Hot glue each button to the back piece in the button holes. Order the buttons so that they make sense when using them to change the time, i.e. the down button is below the up button.
  2. Plug the USB cable into the Arduino and guide the cable through the hole at the bottom of the back piece.
  1. Push the back piece into the case of the clock. Once again it’s a friction fit.
  2. Screw the 3 grubscrews through the heated inserts at the bottom of the case. This will hold all the pieces of the case together.
  3. Plug the Arduino to a USB wall plug or computer port for power.

How To Adjust the Time

  1. Click the ‘Changing time mode button’
  2. Use the up and down button to change the hour. There might be a little delay since e-paper screens have a low refresh rate.
  3. Click the change function button so that the up and down buttons can be used to adjust the minutes.
  4. Click the change function button again to exit the change time mode. Once the screen does a full refresh, the new time is saved.

Code

#include  // RTC Library
#include  // Reduces power consumption https://github.com/rocketscream/Low-Power
#include  // https://github.com/olikraus/U8g2_for_Adafruit_GFX
#include  // including both doesn't use more code or ram
// select the display class and display driver class in the following file (new style):
#include "GxEPD2_display_selection.h"

//The below variables control what the date will be set to. Uncomment the line bellow in setup that sets the rtc to the time here. After compiling, the line needs to be commented out again so that the Arduino does not set the time to this time every time it loses power
int sec = 0;
int minute = 52;
int hour = 12;
int day = 5;
int date = 29;
int month = 12;
int year = 2023;

U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; // font constructor
RV3028 rtc; // create the RTC object

const uint8_t wakeUpPin(2); // connect Arduino pin D2 to RTC's SQW pin. Wakes up the Arduino each minute from sleep mode.

const uint8_t changeFunc(4); // Button to change function from changing hours to changing minutes to exit changing time mode
const uint8_t changeOn(5); // Button to turn on changing time mode, this button causes the interrupt. Do not change this pin to any other pin unless the PCINT paramaters are also changed below.
const uint8_t up(8); // Button to increase time
const uint8_t down(3); // Button to decrease time

volatile int changeTime = 0; //Integer that gets changed when the interrupt occurs
int changeFuncState = 0; //Function button set to 0 initally

void setup() {
Serial.begin(115200);
Wire.begin();
if (rtc.begin() == false) {
Serial.println("Something went wrong, check wiring");
while (1)
return;
} else
Serial.println("RTC online!");
Serial.println();

u8g2Fonts.begin(display); // connect the u8g2 display

// configures interupt pin
pinMode(wakeUpPin, INPUT_PULLUP);

// time adjustment buttons with pullup resistor
pinMode(changeOn, INPUT_PULLUP);
pinMode(changeFunc, INPUT_PULLUP);
pinMode(up, INPUT_PULLUP);
pinMode(down, INPUT_PULLUP);

// allows for the change time buttons to interput the loop
PCICR |= B00000100; //turns on PCINT for pins in group D
PCMSK2 |= B00100000; //Pin D5 will cause interupt
rtc.enableTrickleCharge(TCR_3K); //series resistor 3kOhm
//rtc.setTime(sec, minute, hour, day, date, month, year); //USE THIS TO INITALLY SET TIME. Once set it needs to be commented out so that it doesn't get set to this time every restart
displayDate();
}

ISR(PCINT2_vect) {
changeTime = 1;
}


// Displays the date in the bottom half of the screen
// and does a complete screen refresh
void displayDate() {
Serial.println("DISPLAY DATE = start");

display.init();

display.setRotation(3); //0 is 'portrait'

u8g2Fonts.setForegroundColor(GxEPD_BLACK);
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
u8g2Fonts.setFont(u8g2_font_logisoso20_tr);

rtc.updateTime();

String dateString = rtc.stringDate(); //rtc to output current date
Serial.println(rtc.stringDate());

uint16_t x = 73;
uint16_t y = 110; //bottom
// covers bottom half
display.setFullWindow();
display.firstPage();
do // update the whole screen
{
u8g2Fonts.setCursor(x, y);
u8g2Fonts.print(rtc.stringDate());
} while (display.nextPage());
display.hibernate();
Serial.println("DISPLAY DATE = finished");
}

// Displays the time in the top half of the screen, as a partial refresh
void displayTime() {
Serial.println("DISPLAY TIME = start");
rtc.updateTime();

u8g2Fonts.setForegroundColor(GxEPD_BLACK);
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
// Only numbers and symbols to save space.https://github.com/olikraus/u8g2/wiki/fntlist99#50-pixel-height
u8g2Fonts.setFont(u8g2_font_logisoso50_tn);

uint16_t x = 70;
uint16_t y = 62; //top half, depends on font

display.setPartialWindow(0, 0, display.width(), display.height() / 2);
display.firstPage();
do // Update the upper part of the screen
{
u8g2Fonts.setCursor(x, y);
if (rtc.getHours() < 10) {
u8g2Fonts.print(0);
u8g2Fonts.print(rtc.getHours());
} else {
u8g2Fonts.print(rtc.getHours());
}
u8g2Fonts.print(":");
if (rtc.getMinutes() < 10) {
u8g2Fonts.print(0);
u8g2Fonts.print(rtc.getMinutes());
} else {
u8g2Fonts.print(rtc.getMinutes());
}
} while (display.nextPage());
display.hibernate();
Serial.println("DISPLAY TIME = finish");
}


void loop() {
Serial.println("LOOP = start");

if (rtc.getMinutes() == 0) {
// Refresh the display completely on the hour
displayDate();
}

displayTime();

Serial.println("LOOP = before sleep");

delay(500);

rtc.enablePeriodicUpdateInterrupt(0, 0);

// Allow wake up pin to trigger on interrupt low.
attachInterrupt(digitalPinToInterrupt(2), alarmIsr, FALLING);

Serial.println("VOID LOOP = powering down");

delay(500); // if this isn't here the arduino seems to fall asleep before finishing the last line

// Power down
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);

// Wakes up here!
Serial.println("VOID LOOP = woke up");

// Disable external pin interrupt on wake up pin.
detachInterrupt(digitalPinToInterrupt(2));

// Change time code
while (changeTime == 1) {
Serial.println("changetime");
byte upState = digitalRead(up);
byte downState = digitalRead(down);
digitalRead(changeFunc);

if (digitalRead(changeFunc) == LOW) {
changeFuncState = changeFuncState + 1;
Serial.println(changeFuncState);
delay(250);
}

if (changeFuncState == 0) { // Changes the hours. Runs when changeFuncState == 0, therefore runs right after the ISR and is the default mode.
if (upState == LOW) {
delay(250);
Serial.println("hr, up");
rtc.setHours(rtc.getHours() + 1);
displayTime();
} else if (downState == LOW) {
delay(250);
Serial.println("hr, down");
rtc.setHours(rtc.getHours() - 1);
displayTime();
}
}

else if (changeFuncState == 1) { // Changes the minutes
if (upState == LOW) {
delay(250);
Serial.println("min, up");
rtc.setMinutes(rtc.getMinutes() + 1);
displayTime();
} else if (downState == LOW) {
delay(250);
Serial.println("min, down");
rtc.setMinutes(rtc.getMinutes() - 1);
displayTime();
}

} else if (changeFuncState == 2) { //Turns off change time function of the clock
changeFuncState = 0;
changeTime = LOW;
displayDate(); //Fully refreshes the display
}
}
}


void alarmIsr() {
}

Have a question? Ask the Author of this guide today!

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.

Comments


Loading...
Feedback

Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.