Steampunk Analog Clock

Intro: Steampunk Analog Clock

People who know me well also know that I really dig clocks and calendars. Trying to understand the Mayan calendar or the calculations of the eastern date are puzzles I just can't resist. Also clocks and not the ordinary ones have my special attention.

A friend pointed out that there was a contest here at instructables so I decide to participate. I'll build a clock as never seen before. Based on old displays and a very modern Arduino I intend to build a steampunk clock.

Wait and see

Step 1: Getting Started

In order to build a clock one has to make some choices and divide the project in different parts of development.

  • First get some old bakelite meters
  • Design a case
  • Choose functionality
  • Program the control

Trough the Dutch ebay i purchased some old meters. A meter with a range from 0-60A, one from 0-10A and the third with a range of 2 mA. A meter with a 2mA range is simply directly controlled by a output of an arduino. The other two meters needed a current of 60/10Amps which is much to high to be electronic simple. So I dismounted the old pointersystem and replaced it with a servo. (step 2)

Now I only need a RTC, an Arduino, two servo's and a resistor. For the design to be steampunk or art-deco I added two vacuumtubes and a color OLED display. (step 3)

The case design is made by my daughter (see image). (step 4)

The program (step 5)

The functionality is basic. Aftre some time I'll add some easter eggs
like a easter egg on the display during easter and more... (step 6)

Step 2: Making It Possible

Making it possible

A panel meter is an industrial instrument. Maybe it is a simple meter which can be directly driven by an arduino output. You can always hope it will. Most meters won't be so user-friendly. Especially meters of the V, W and A types a most common used for values which makes direct control by an Arduino impossible. Except the problem being far to great current s or voltages needed, most old meters (the prettiest) don't have a linear scale. I have overcome this problem by simply using a servo (photo)

Step 3: Schematics


The clock is build with just a few parts:

  • An Arduino Uno
  • Two servos
  • A RTC 3231
  • A OLED Display "just for fun"
  • A resistor
  • Two addressable multicolour LED's (cosmetic)
  • Two vacuum tubes with connector (cosmetic)


The rtc is used to maintain the right time and for backup if no power is available. It is simply connected through I2C and +5V and GND.


With a servo it is quite simple to point a pointer to the right angle. Some calibration is required for zero and span in case of a linear scale. My scales weren't linear so I divided the scale in linear parts en made the software calculate the correct angle for the according time. This may result in an angle for every possibility for some scales. Most scales have a scientific base which can be calculated.


One of the panel meters has a 2mA range. An analogue out can be programmed for a voltage output. By using a resistor this voltage is linear with the current through it. 5V and 220 ohms means a current of 2.27 mA. Beside the full scale current being to high, the resistor accuracy is bad (10 or 20%). I took a resistor slightly above the calculated value and let the software do the math.


The main reason the display was included was because I'd like to try to make a cat-eye vacuum tube like design. The voltages needed for a antique cat-eye vacuum tube were not suitable for my project. So I decided to use higher tech solutions. The advantage of a display is it's versatile purpose. It can be used for monitoring, setup and for design purposes.

Tube lights

(not implemented yet)

The tube lights are a design virtue like the vacuum tubes themselves. They have no purpose whatsoever, the are just for cosmetic use. With the two addressable multicolour led's it's possible to give a colour effect to the vacuum tubes.


(not implemented yet)

At least two switches will be used to set-up the time and date.

Step 4: Building a Case

My daugther had designed a case only by two words: steam punk and art-deco. From while to while she would send me some existing designs with the question wich i liked best. After some time she came up with a great design. It is simple and elegant. It has three blocks with a meter and for the show two vacuum tubes and a OLED color display. First I wanted to use a antique tube called cat-eye. I didn't because of the requered voltages (250 V).

Center Clock (CC)

The middle clock (CC) you can see in the cad drawing.

  • CC Front 116 x 116 x 9 mm
  • CC Back 116 x 116 x 9 mm
  • CC Right 98 x 98 x 9 mm
  • CC Left 98 x 98 x 9 mm
  • CC Top 116 x 98 x 9 mm
  • CC Bottom 116 x 98 x 9 mm

Right and Left Clock (RC and LC)

The right and left clock are exact mirrors of each other.

  • LC/RC Front 100 x 91 x 9 mm
  • LC/RC Top 82 x 62 x 9 mm
  • LC Right/ RC Left 140 x 80 x 9 mm
  • LC RRight / RC LLeft 40 x 80 x 9 mm
  • LC/RC Back 100 x 91 x 9 mm

No bottom !!!


The footer consist of a board with the size 320 x 116 x 9 mm with a little foot underneath. Between the left and rightclock, i.e. under the middle clock I made space for two vacuum tubes with connector and an OLED display. My foot is in total 20 mm high, but that's just some wood leftover.

Step 5: The Program

The program


First we include the needed libraries

#include <adafruit_tftlcd.h>   //OLED display
#include <wire.h>              //Needed for I2C
#include <servo.h>             //Servo
#include <time.h>              //Time lib
#include "ds3231.h"            //RTC
#include <adafruit_gfx.h>      //Display Graphics
#include <adafruit_ssd1331.h>  //SSD1331 lib
#include <spi.h>               //???


The 10A and 60A servos are connected to the PWM outputs 5 and 6. By trial and error I calibrated the zero and span of the meters. These are defined as an angle like MA10_0, meaning the 10A meter and the zero angle. Be careful with trying because a servo has a much greater possible angle than a meter can move without damaging the pointer.

//The PWM outputs for the servo's
#define MA10         5                //Output for 0-10A
#define MA10_0     110                //Servo angle for 0
#define MA10_10     36                //Servo angle for 10 A
#define A60          6                //Output for 0-60A
#define A60_0      156                //Servo angle for 0
#define A60_60      63                //Servo angle for 60 A

The display uses 5 pins. You could use any pin but for performance reason these are the best

//Display pins 
#define sclk 13
#define mosi 11
#define cs   10
#define rst   9
#define dc    8

I defined some colours for the colour OLED display to use. Brown, Gold, Maroon and Olive are 16bit colours from the art-deco colour pallet.

// Color definitions
#define BLACK           0x0000
#define BLUE            0x001F
#define RED             0xF800
#define GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xEDE0  
#define WHITE           0xFFFF
#define ORANGE          0xFD20
#define BROWN           0x61C3
#define GOLD            0xE5C0
#define MAROON          0x7800
#define OLIVE           0x7BE0

Define the output for the 0-2mA meter.

//Define the output for the 0-2mA meter
#define MA2             3


First we declare the two servos

//Declare the two servos
Servo servo_MA10;	//10A meter
Servo servo_A60;	//60A meter

Than the OLED display

Declare the display
Adafruit_SSD1331 display = Adafruit_SSD1331(cs, dc, rst);

Finally an bitmap logo. It is an monochrome bitmap. I used LCDassistent.EXE to convert a black and white picture to the HEX-code. Of course you could do this by hand if you have to much time on your hands.

//Logo bitmap (monochroom)<br>static const unsigned char PROGMEM logo[] = 
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x1F, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x0F,
0xE0, 0x0F, 0xFF, 0xF8, 0x1F, 0xFF, 0x80, 0x01, 0xFF, 0x00, 0x00, 0x3F, 0xF8, 0x0F, 0xFF, 0xF0,
0x03, 0xFF, 0xC0, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xFC, 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xC0, 0x03,
0xFF, 0x00, 0x00, 0xFF, 0x0E, 0x00, 0x3F, 0xF0, 0x00, 0xFF, 0xE0, 0x03, 0xFF, 0x0F, 0xE1, 0xFF,
0x0F, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0xE0, 0x07, 0xFF, 0x0F, 0xE1, 0xFF, 0x0F, 0x00, 0x7F, 0xE0,
0x00, 0xFF, 0xE0, 0x07, 0xFF, 0x0F, 0xE3, 0xFF, 0x0F, 0x80, 0x7F, 0xE0, 0x00, 0xFF, 0xE0, 0x0F,
0xFF, 0x0F, 0xE3, 0xFF, 0x0F, 0x80, 0xFF, 0xC0, 0x00, 0xFF, 0xC0, 0x0F, 0xFF, 0x0F, 0xE3, 0xFF,
0x0F, 0x80, 0xFF, 0xC0, 0x01, 0xFF, 0xC0, 0x1F, 0xFF, 0x0F, 0xE3, 0xFF, 0x0F, 0x81, 0xFF, 0xC0,
0x01, 0xFF, 0xC0, 0x1D, 0xFF, 0x0F, 0xE3, 0xFF, 0x0F, 0x81, 0xFF, 0x80, 0x01, 0xFF, 0xC0, 0x39,
0xFF, 0x00, 0x03, 0xFF, 0x0F, 0x81, 0xFF, 0x80, 0x01, 0xFF, 0x80, 0x39, 0xFF, 0x00, 0x03, 0xFF,
0x0F, 0x83, 0xFF, 0x80, 0x03, 0xFF, 0x80, 0x71, 0xFF, 0x00, 0x03, 0xFF, 0x0F, 0x83, 0xFF, 0x80,
0x03, 0xFF, 0x00, 0x71, 0xFF, 0x0F, 0xE3, 0xFF, 0x0F, 0x83, 0xFF, 0x80, 0x07, 0xFF, 0x00, 0xE1,
0xFF, 0x0F, 0xE1, 0xFF, 0x0F, 0x03, 0xFF, 0x80, 0x07, 0xFE, 0x01, 0xE1, 0xFF, 0xCF, 0xE1, 0xFF,
0x0F, 0x03, 0xFF, 0x00, 0x0F, 0xFE, 0x01, 0xFF, 0xFF, 0xCF, 0xE0, 0xFF, 0x0E, 0x07, 0xFF, 0x00,
0x0F, 0xFC, 0x03, 0xFF, 0xFF, 0x0F, 0xE0, 0x7F, 0x0C, 0x07, 0xFF, 0x00, 0x1F, 0xFF, 0xF0, 0x01,
0xFF, 0x0F, 0xE0, 0x3F, 0xF8, 0x07, 0xFF, 0x00, 0x1F, 0xFF, 0xF0, 0x01, 0xFF, 0x0F, 0xE0, 0x0F,
0xE0, 0x07, 0xFF, 0x00, 0x3F, 0xFF, 0xF0, 0x01, 0xFF, 0x0F, 0xE0, 0x00, 0x00, 0x07, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00


The time variable is a struct type (record in java and pascal). An object with several variables of various types. It is declared in the time library and ready for use after including time.h.

uint8_t time[8];
struct ts t;
    //Struct is een record
    //t.hour = hours
    //t.min = minutes
    //t.sec = seconds

Some variables which are used to finally show the time and displays.

int minAngle = 0;   // servo position in degrees
int hrAngle = 0;
int hr10Angle = 0;
int uren;
int uren10;
int min;
int i = 0;
int dir = 1;

Set up

First set-up lines are pretty standard to establish communication through the serial connection at a rate of 9600 Baud. I use this connection mostly to debug the code.

void setup()<br>{
    Serial.println("Analog clock by 24/07");

Then the wire library is activated. If I'm not mistaking this initiates the I2C communication.


Then the RTC is initialized. I used 3231 IC because of the accuracy.

//Initialize the RTC<br>    DS3231_init(DS3231_INTCN);

Next we make the pin 3 an output and attach both servos

//make an output of mA2<br>    pinMode(MA2, OUTPUT);

At last I fired up the OLED display and gave it an art-deco background colour.

display.begin();<br>    display.fillScreen(MAROON);


In the loop we first get the time and give the time variables their corresponding value. Than we write the time values to the serial connection to check the time to be correct.

void loop()<br>{
    //Get the time in the struct
    //Get the hours and minutes (Dutch)
    uren = t.hour;
    min = t.min;
    //Write time to serial
    Serial.print(" - ");

Depending on the value of the used resistor there are three values of the output. Between 0 and 10 hours the meter will show 0, so the output shall be 0. Between 10 and 20 the output must be according to the 1mA value, i.e. 2.2V. Theoretically it would be 112, but the resistor has an 10 % range and every meter has an error. So I determined the two values by trial and error.

//Determine output MA2<br>    //hr10Angle is an analog outputvalue (0-255)
    //hr10Angle = (176 / 24) * uren;
    if (uren < 10) {
        hr10Angle = 0;
    if ((uren > 9) && (uren < 20)){
        hr10Angle = 88;
    if (uren > 19) {
        hr10Angle = 176;
    analogWrite(MA2, hr10Angle);
    //Serial.print("hr10Angle: ");
    //Serial.print(" - ");

In fact I check for every value of the meter. With a linear meter you could do different but in my case with a none scientific scale one has to, to read the right time. After that there is a safeguard. If there is any error in the program causing the angle out of the range, the safeguard will bring it back in range to prevent damage to the meter.

//Determine angle of MA10<br>    //Get rid of the 10 or 20 by mod function
    uren10 =  uren % 10;
    if (uren10 == 0) {
        hrAngle = 109;
    if (uren10 == 1) {
        hrAngle = 105;
    if (uren10 == 2) {
        hrAngle = 100;
    if (uren10 == 3) {
        hrAngle = 93;
    if (uren10 == 4) {
        hrAngle = 85;
    if (uren10 == 5) {
        hrAngle = 77;
    if (uren10 == 6) {
        hrAngle = 65;
    if (uren10 == 7) {
        hrAngle = 58;
    if (uren10 == 8) {
        hrAngle = 47;
    if (uren10 == 9) {
        hrAngle = 40;
    if (uren10 == 10) {
        hrAngle = 36;
    //Safeguard of the  meter
    if ((minAngle > 109) || (minAngle < 36)) {
        minAngle = 77;
    //hrAngle = (MA10_10 - MA10_0)/10.0 * uren + MA10_0;
    //hrAngle = 40;
    //Serial.print("hrAngle: ");
    //Serial.print(" - ");

The 60 A has several ranges. Below 10 minutes every value is calculated separately. Above the 10a there are three semi-linear ranges. After calculation there is a safeguard again to prevent damage of the meter

//Determine angle of A60<br>    //Below 10 minutes 
    if (min < 2) {
        minAngle = 154;
    if (min == 2) {
        minAngle = 153;
    if ((min == 3) || (min == 4)) {
        minAngle = 152;
    if ((min == 5) || (min == 6)) {
        minAngle = 151;
    if (min == 7) {
        minAngle = 150;
    if (min == 8) {
        minAngle = 149;
    if (min == 9) {
        minAngle = 147;
    //10 tot 30 minuten (145 - 105)
    if ((min > 9) && (min < 30)) {
        minAngle = (105 - 145)/20.0 * (min - 10) + 145;
    //30 tot 40 minuten (105 - 87)
    if ((min > 29) && (min < 40)) {
        minAngle = (87 - 105)/10.0 * (min - 30) + 105;
    //40 tot 60 minuten (86 - 60)
    if ((min > 39) && (min < 60)) {
        minAngle = (60 - 86)/20.0 * (min - 40) + 86;
    //Beveiliging van de meter
    if ((minAngle > 154) || (minAngle < 60)) {
        minAngle = 105;
    //minAngle = 86;            //calibratie
    //Serial.print("minAngle: ");

The next line will display the logo on the OLED display in the colour GOLD, starting in 0,0 and with a width of 96 and a height of 32.

display.drawBitmap(0, 0, logo, 96 , 32, GOLD);

The next block of code is used to write the time to the display. Every loop first I draw a rectangle in the background colour and then I write the new time. The if statements are used to show leading zero's

display.setTextSize(1);<br>    display.setTextColor(WHITE);
    display.fillRect(0, 32, 96, 16, MAROON);
    if (uren < 10) {
    if (min < 10) {
    if (t.sec  < 10) {

The last block of code displays a cat-eye vacuum tube like animation. It are simply two rectangles drawn according a counter an a direction bit.

if (dir == 1) {<br>    i++;
  } else  {
  if (i == 6) {
    dir = -1;
  if (i == 0) {
    dir = 1;
    display.fillRect(0, 44, 96, 16, MAROON);
    display.fillRect(48 - i * 8, 44, i * 16, 16, OLIVE);

And standard every loop ends with these statements.

 Serial.println();<br> delay(1000);

Step 6: Did You Miss Me

Of course, I could be a politician, I promised more than I gave. It is all for a good reason. The main reason being my holiday next week and the end date of the clock contest 30 of July.

Vacuum tubes

Unfortunately I couldn't get the vacuum tubes so quickly. I think the design will improve greatly after adding them. And I will add them.

The addressable multicolour LED's

The LED's are meant to give the vacuum-tubes a colour matching your wishes, function or time of year. No tubes no lighting them. Can't have it all.

Case finish

Like the old wireless radios I would like to finish the case with shellac. It should get a beautiful warm colour for I used massif wood instead of board. Another To-do.


The only way to adjust time is through software. I'm still looking for some switches that match the design. In combination with the display it should be no problem whatsoever to adjust date and adept automatic to DST.

Easter Eggs

This project screams for Easter eggs to be included. For now I have some small ideas but the sky is the limit.

  • One vacuum-tube green and the other red during Christmas
  • An Easter egg in the display during Easter
  • Fireworks on new years eve
  • And all the ideas I'll get from you



    • Furniture Contest 2018

      Furniture Contest 2018
    • Metalworking Contest

      Metalworking Contest
    • Fix It! Contest

      Fix It! Contest

    4 Discussions

    Alex in NZ

    8 weeks ago

    Fantastic design, and I love the way you replaced the driver mechanism with a servo. Thank you for sharing this :-)


    2 months ago

    "The other two meters needed a current of 60/10Amps which is much to high to be electronic simple"

    For future reference, meters like these actually read in milliamps or even microamps. A shunt (resistor) is fitted internally to divert the rest of the current around the meter movement. If you're happy to remove the whole mechanism, then you shouldn't have any trouble locating, removing and replacing the shunt with an ordinary resistor to bring the scale to something more manageable. It doesn't even have to be an accurate full-scale-deflection of any particular number, as you can calibrate it in software later.

    Check out

    for example.

    1 reply

    Reply 2 months ago

    Thanks for your reply MikB.

    I know about a shunt in ampere meters. I reckoned these to have one too when I bought them through the Dutch E-bay (Marktplaats) but they didn't. The meters contained a coil with a big diameter wired coil. With simple panel meters with shunt the project would be more simple. Some resistors like with the 2mA meter would be sufficient to get a reading. More important, when a meter is used in the way it's designed there is no correction needed for a non-linear scale. I Guess I had a moving-iron meter.

    Penolopy Bulnick

    2 months ago

    Fun clock and I love that design your daughter came up with :)