Introduction: ATtiny Watch Core

This instructable show how to use an ATtiny85 to create a tiny watch core that can run over 1 year before recharge or replace battery.

The instruction to make the watch case may be in next instructable.

I have made a few Arduino watch before, but I found the watch using ATmega boards or ATmega chips are a little bit too big to wear. So I would like to make it with a smaller chips.

I have choose ATtiny85 just because I can easy to get one.

The challenges for using ATtiny85 are:

  1. It only have 8K flash memory
  2. It only have 5 IO pins (I would like to keep the 6th IO pin as reset for easy re-programming)
  3. Since the above 2 limitations, it is hard to find a complete source code for an ATtiny watch on the web

I design to develop a tailor-make code to complete these challenge, it include:

  1. Power control to make the watch can run over 1 year
  2. Revise a time library to utilize WDT for power saving purpose
  3. Trim down a display library to meet 8K binary limitation
  4. Customizable font type
  5. Basic UI for adjust time just like a normal digit watch

Step 1: Preparation


Today core subject, ATtiny85.


Any ISP that can program ATtiny85.


In general expectation, a watch should run over 1 year without charging or replace battery. For my simple measurement of power usage and the battery specification, CR1220 can only run about half an year, CR2016, CR2025 and CR2032 can run over 1 to 2 years representatively.


Since the number of IO pins of ATtiny85 is very limited, I design display and further modules should all run in I2C. Power consumption and source availability are also important factors. I choose an OLED screen drive with SSD1306. It can find in 3 different sizes, 64x32 is the smallest one. (another sizes are 128x64 and 128x32)

Other Parts

A battery holder, a small bread board, some bread board wires, two buttons and a power switch (optional).

Step 2: Assembly

Connect all the parts on the bread board:


pin 1: not connected

pin 2: set button, other button pin connect to GND

pin 3: up button, other button pin connect to GND

pin 4: GND

pin 5: OLED SDA

pin 6: not connected

pin 7: OLED SCL

pin 8: VCC

Also connect battery and OLED board to VCC and GND.

Step 3: Power Down, WDT and Time

When MCU and OLED turned on, it consume about 6 mA.

In order to make the watch can run over 1 year, I will use the MCU most power saving sleep mode, SLEEP_MODE_PWR_DOWN, when user not in use. According to my cheap power meter, it show 0.1 uA if disable all the function. But It still need to enable WDT for time keeping, after enable WDT, it show 4 uA. Assume MCU and OLED auto sleep after 5 seconds and user view the watch 12 times every day in average, the watch will consume about 0.2 mAh per day. ((0.004 mA * 24 hours) + (6 mA * (5 / 60 / 60) hours * 12)) So a 150 mAh CR2025 battery can run 750 days.

The time source code mainly come from PaulStoffregen. However, the power down sleep mode will stop the normal timer, use millis() function for time keeping is not valid. So I keep another variable to replace millis() function. For each WDT interrupt, it increase a certain value. The increment value depends on the WDT interval settings and the chip's oscillator. When using 1 second WDT interrupt, my chip's calibrated increment value is 998 (around 1000 milliseconds).

And also I have added the readVcc() function for monitoring the battery status.


WDT and power related:

time function for Arduino v1.4:


Step 4: I2C Display

The code is reference from DigisparkOLED, but since its example code complied size is over 6K, the complete example cannot put on their ATtiny85 product, Digispark or Digithumb. (it require to comment out the bitmap code for running) The complete example can only run on their another product, Digispark Pro. (it have around 14K flash available)

Here are something I have revised or rewritten:

  1. Trim out many unused data, including font and bitmap
  2. Init SSD1306 setting according to the SSD1306 data sheet page 64
  3. Try to support all known resolution (64x32, 128x32, 128x64)
  4. Support custom font
  5. Function for turning OLED on and off for power saving purpose

It use TinyWireM library, but it have a bug (reported), you need revise one line of code in write() function to cover this:

if (USI_BufIdx >= USI_BUF_SIZE - 1) return 0; // dont blow out the buffer




SSD1306 data sheet:

Another instructables using ATtiny85 and SSD1306:

Step 5: Custom Font

8K flash has no enough room to store all characters in large font size. (such as 24 pixels font height)

As watch only require 10 digital characters, we can tailor-make a selected font type binary to fit in limited space.

I will use imagicmagick command line tools to show how to convert custom font characters to a c header file.

This program need 10 digit in 2 font sizes, one with font height 8 pixels to show date digits and one with font height 24 pixels to show time digits:

convert -depth 1 -font Lucida-Sans-Unicode -pointsize 11 label:00123456789 -crop 70x8+7+4 -flip -rotate 90 watch_digit.xbm

convert -depth 1 -font Cooper-Black -pointsize 25 label:00123456789 -crop 150x24+14+4 -flip -rotate 90 watch_3x_digit.xbm

Lucida-Sans-Unicode and Cooper-Black are the font type in Windows 7, you may use your selected font type in your OS.

The corp, flip and rotate option help to adjust the binary data in correct position and direction. You may change the output format from xbm to png to preview the output bitmap.

After export the xbm files, we can copy the font binary code to the watchdigit.h source file:

#include <avr/pgmspace.h>

#define FONTWIDTH 7
#define FONT3XWIDTH 15

static const uint8_t watch_digit[] PROGMEM = {
//watch_digit.xbm binary code

static const uint8_t watch_3x_digit[] PROGMEM = {
//watch_3x_digit.xbm binary code


Step 6: Program

I am using a littlewire board as ISP, and I have make a hacking ISP connector for easier plug and play.

Any ISP that can program ATtiny85 should be ok.


Step 7: User Input

2 buttons UI for adjust time, operation method just like any simple digital watch.

Set button: select adjust field, the field will highlighted

Up button: increment value of selected field

Step 8: Calibration

Debug Screen

Press up button when not selected any field will enter debug screen.

Time Calibration

The first number is WDT interrupt count, this value is used to calibrate the value of wdt_millis_per_interrupt.

This value for you chips should be calculate from:

actual time (in millisecond) passed / WDT interrupt count

e.g. if you turn it on at 2016/01/07 23:10 and it is now 2016/01/08 13:25, it passed 51300000 milliseconds. At the same time the first line of debug screen show 51454 then you should set wdt_millis_per_interrupt as:

51300000 / 51454 ~= 997

Voltage Value

The second number is current battery voltage in millivolt. It require calibrate the constant value in readVcc() with the real readings from a multimeter.

This value for you chips should be calculate from:

actual millivolt / debug value * current voltage reference value

e.g. current voltage reference value is 1125300, debug screen second line show 2823 and multimeter show 2.81 volt then you should alter voltage reference value as:

2810 / 2823 * 1125300 ~= 1120118

Step 9: What's Next?

  • watch case, it may be a big ring on a finger (should use a smaller battery like CR1220)
  • more precise time, try to tune WDT millis with current volt and temperature
  • connect other I2C modules
  • research sync time method, GPS, WiFi + internet, BLE + mobile phone and more


matthewfelgate made it!(author)2017-06-30

Does the ATtiny85 not need a Crystal to work?

%E9%99%B3%E4%BA%AE made it!(author)2017-06-30

yes, attiny can use internal oscillator only

AaravM made it!(author)2016-06-22

can we use this microcontroller instead

%E9%99%B3%E4%BA%AE made it!(author)2016-06-22

no idea, never use it yet. but it have a USB socket, seems similar to Digispark. if true, the boot loader eat up around 2 KB memory, then may not have enough room to fit this program.

RyeMAC3 made it!(author)2016-03-05

How do you modify the code for a 64x48 display?

%E9%99%B3%E4%BA%AE made it!(author)2016-03-22

just guess it similar to 64x32, but have 2 more page.

ReneR2 made it!(author)2016-03-22

Works fine, but i have bigger OLED ( ) and cant seem to find where to edit so it fills my screen, also id like to be able to edit sleep so it can be on all the time, where to do these changes?

%E9%99%B3%E4%BA%AE made it!(author)2016-03-22

I have made an ring watch base on this core, you may find more details here:

diy_bloke made it!(author)2016-02-07

You left one pin unused what a waste :-)
Seriously though: great project and looks good too

KharyL made it!(author)2016-02-29

can you send me the download files i don't want to pay thank you email; klambie at

diy_bloke made it!(author)2016-02-29

which download files? I have no idea what you are talking about

%E9%99%B3%E4%BA%AE made it!(author)2016-02-07

Not waste, I just reserve it for future development. Actually, in my latest code, I change to used only one pin to connect three or more buttons, and reserve remain more pin for further features, such as serial GPS or BLE.

diy_bloke made it!(author)2016-02-08

good idea though the purist in me would then think....hmmm attiny10 ;-)
Great project, well done

dan3008 made it!(author)2016-02-14

wow, I think i'm going to base my Attiny85 pocket watch on this :)

Jfieldcap made it!(author)2015-12-28

Nice idea and 'ible! I wonder if this could be compacted even more, maybe an SMD attiny, SMD buttons, and like you mentioned, a smaller battery? I wonder just how tiny this could be made. Also, I wonder if there's green on black versions of this display? Then you could make the font look like an old terminal, so it'd look like an ancient computer. Anyways, neat idea and project, I might make it sometime.

%E9%99%B3%E4%BA%AE made it!(author)2016-02-07

coming soon

Jfieldcap made it!(author)2016-02-08

Cool! I noticed the buttons and the resistor are still through hole, though. If you're not too intimidated by SMD soldering, couldn't it be just a bit smaller? ;)

%E9%99%B3%E4%BA%AE made it!(author)2016-02-09

yes, I do not have SMD button and resistor in hand. And I design add one more button and use resistors for the button panel too late, so it is temporary solution.

Jfieldcap made it!(author)2016-02-09

Ah, ok. Well, I look forward to seeing it once you've finished with it!

FrankenPC. made it!(author)2015-12-29

Even thermal compensated RTC units drift over time. How stable is the ATTiny as it relates to time drift?

%E9%99%B3%E4%BA%AE made it!(author)2015-12-29

It needs time to measure, in my previous experience, use Lithium battery is better than rechargeable battery. It run a few days ago, and the variation still not over 1 minute.

PeteWK8S made it!(author)2016-01-07

How do you calibrate? Step by step please! My build of this loose minutes every hour. Suggestions please

%E9%99%B3%E4%BA%AE made it!(author)2016-01-07

Revised step 8 for the time calibration details

PeteWK8S made it!(author)2016-01-08

Thank your for revised Calibration Step. However, to help the "clueless" like myself, it could also specify the calibrated values are edited into WDT_Time.cpp at lines:

static uint16_t wdt_millis_per_interrupt = 1076; // calibrate value

and for readVcc()

result = 3094575L / result; // calibrated value

%E9%99%B3%E4%BA%AE made it!(author)2016-01-08


Sorry for the misleading, the code come from over 3 sources and I have substantially revised. I will try to move also calibrate value at the top in next instrucables. (ATtiny Ring Watch)

PeteWK8S made it!(author)2016-01-10

Sorry but I'm still doing something wrong. After I determine the ms and mv numbers I still loose too much time. So I think I am not computing the calibration numbers correctly.

%E9%99%B3%E4%BA%AE made it!(author)2016-01-11

can you show your calculation?

PeteWK8S made it!(author)2016-01-11

WDT_Time.cpp shows to compute measured voltage in mv:

// result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000

result = 3375900UL / result; // calibrated value

return result; // Vcc in millivolts

My measured voltage is 3.3V which is 3375900 by this formula - When I make results=3375900UL then debug shows 14806 This is confusing to me.

%E9%99%B3%E4%BA%AE made it!(author)2016-01-11

I have updated step 8 for voltage calibration.

Please be noted that:

wdt_millis_per_interrupt vale should be within +/- 10 of 1000 milliseconds

voltage reference value should be within +/- 100k(~0.1 v) of 1125300UL

auto tune wdt_millis_per_interrupt by voltage level not yet implement (i.e. voltage reference calibration not affect time accuracy)

PeteWK8S made it!(author)2016-01-14

This is not working for me. Using Atmel Attiny85 20PU - IDE 1.65 with settings Tiny85 at 1Mhz Internal

  1. Program chip with Attiny85_Watch_ino with WDT_Time.cpp with default or original entries

static uint16_t wdt_millis_per_interrupt = 998; // calibrate value

result = 1089952UL / result; // calibrated value

  1. Note down current local time
  2. Set time on Attiny chip
  3. Measure and note actual Mv on Attiny chip
  4. Wait 1 to 8 hours and note elapsed time in milliseconds
  5. Note Debug values
  6. Compute wdt_millis_per_interrupt as: 51300000 / x = y (x = debug value noted, y= calibrated value)
  7. Enter value of y at “static uint16_t wdt_millis_per_interrupt = 998; // calibrate value” in WDT_Time.cpp replacing 998
  8. Compute millivolts using x / debug value * 1125300 = y (x=measured millivolts, y= calibrated value)
  9. Enter values of x and y at “result = 1089952UL / result; // calibrated value” replacing 1089952 in WDT_Time.cpp
  10. Reprogram Attiny85 chip with the newly calibrated values.
  11. Reset time of Attiny85 to current local time.

I then recheck the Attiny clock but time is either gaining or losing after only a few minutes. Am I still doing something wrong?

%E9%99%B3%E4%BA%AE made it!(author)2016-01-14

I am writing a new version to help auto calibrate the value, it should release at these few days.

PeteWK8S made it!(author)2016-01-14

Thanks -- looking forward to it.

%E9%99%B3%E4%BA%AE made it!(author)2016-01-15

Try this:

it will auto tune the interrupt value when you set the time, if this not the first time of time setting and the previous setting is over 1 hour.

PeteWK8S made it!(author)2016-01-20

Hello, this version works but with caveat. I must reset the unit when trying to set time for self calibration. The time will start rapidly changing by itself. Once I reset it, it behaves and keeps time accurately. Thanks for your work and sharing. Pete

PeteWK8S made it!(author)2016-01-22

This version works quite well. However, I have a question. How and where do I change where the text begins to display.? It displays low, towards the bottom of the display. Its a 128x64 0.96 inch display. Maybe your display is smaller and the text seems bigger because of that. Thanks again for all your help.

%E9%99%B3%E4%BA%AE made it!(author)2016-02-01

Sorry for late reply, I have just tested on 128x32 and 128x64 screen. You may uncomment the screen #define at the file ssd1306.h for change resolution setting.

Please find the updated at the same place:

joe57005 made it!(author)2016-01-02

Haven't tried with attiny, but the megas drift several seconds every hour. An experiment was off by about twenty minutes after roughly 8 hours at room temperature. My mechanical watch was more accurate.

dunk8888 made it!(author)2016-01-25

Hi,that ssd1306 looks verry small,it looks smaller than mine,my ssd1306 128x64 wont fit on half a small breadboard like this does,where did you buy it from?

%E9%99%B3%E4%BA%AE made it!(author)2016-01-25

the resolution is 64x32

dunk8888 made it!(author)2016-01-25

Hi does the number on these batterys corraspond to mah cr2025 and cr2016 etc?

%E9%99%B3%E4%BA%AE made it!(author)2016-01-25

CR2016, CR2025 and CR2032, the first 2 digits if the battery diameter and the last 2 digits is thickness. Thicker battery should have more mAh.

ronw5 made it!(author)2016-01-17

these are the errors when i try to compile:

This report would have more information with

"Show verbose output during compilation"

enabled in File > Preferences.

Arduino: 1.0.6 (Windows NT (unknown)), Board: "ATtiny85 @ 8 MHz (internal oscillator; BOD disabled)"

attiny85watch.ino: In function 'void setup()':

attiny85watch:17: error: 'INPUT_PULLUP' was not declared in this scope

attiny85watch:23: error: 'TinyWireM' was not declared in this scope

attiny85watch:24: error: 'class SSD1306' has no member named 'begin'

attiny85watch:25: error: 'class SSD1306' has no member named 'fill'

attiny85watch.ino: In function 'void enter_sleep()':

attiny85watch:42: error: 'class SSD1306' has no member named 'fill'

attiny85watch:43: error: 'class SSD1306' has no member named 'off'

attiny85watch.ino: In function 'void wake_up()':

attiny85watch:53: error: 'class SSD1306' has no member named 'on'

attiny85watch.ino: In function 'void draw_oled()':

attiny85watch:74: error: 'class SSD1306' has no member named 'fill'

attiny85watch:80: error: 'class SSD1306' has no member named 'print_digits'

attiny85watch:82: error: 'class SSD1306' has no member named 'draw_pattern'

attiny85watch:83: error: 'class SSD1306' has no member named 'print_digits'

attiny85watch:84: error: 'class SSD1306' has no member named 'draw_pattern'

attiny85watch:85: error: 'class SSD1306' has no member named 'print_digits'

attiny85watch:87: error: 'class SSD1306' has no member named 'print_digits'

attiny85watch:88: error: 'class SSD1306' has no member named 'draw_pattern'

attiny85watch:89: error: 'class SSD1306' has no member named 'print_digits'

attiny85watch:91: error: 'class SSD1306' has no member named 'print_digits'

attiny85watch:92: error: 'class SSD1306' has no member named 'print_digits'

%E9%99%B3%E4%BA%AE made it!(author)2016-01-17

it require Arduino 1.6.5, ATtiny core source and TinyWireM library.

ronw5 made it!(author)2016-01-18

thank you.

ChristianK35 made it!(author)2016-01-07

Hi i want to make an weather station with an dht22 sensor and this oled display and the attiny, everithing works grat with an 20x4 display lcd
i just need the code to print some text how i use the command oled.print_digits("some text"); ?

ChristianK35 made it!(author)2016-01-07

Solved! i use this code:

#include "SSD1306_minimal.h"
#include <avr/pgmspace.h>

#define DEG "\xa7" "C"

SSD1306_Mini oled;

void setup(){

void loop(){
oled.printString( "Oled_Attiny85");

%E9%99%B3%E4%BA%AE made it!(author)2016-01-07


In this program, it only contain digit bitmap font, so it cannot display any other character. I will implement it in the future if it still have enough room in the 8K flash.

nhuynhduc made it!(author)2016-01-04

Dear 陳亮

I'm upload error

sketch\WDT_Time.cpp: In function 'void setup_watchdog(int)':

WDT_Time.cpp:323: error: 'WDTCR' was not declared in this scope

WDTCR |= (1 << WDCE) | (1 << WDE);

How can i debug ?


%E9%99%B3%E4%BA%AE made it!(author)2016-01-05

It depends on which ATtiny source you are using, sometimes WDTCR constant may be WDTCSR.

I am currently using this source:

PeteWK8S made it!(author)2016-01-07

I had same error. Create folder WDT_Time in libraries folder. Put WDT_Time.cpp and WDT_Time.h inside that folder and re-compile. This worked for me under IDE 1.65

About This Instructable




Bio: Do it yourself if you cannot buy it!
More by 陳亮:IoT Power Consumption ConcernATtinyPowerMeterIoT Fidget
Add instructable to: