Introduction: Berlin Clock, Arduino Nano, DS1307 Real Time Clock. 74HC595N 8 Bit Shift Register.

About: I love making things. I have for as long as I can remember liked to make stuff. Now days I have two kids (Thomas and Emma) and most of the things I do are safe for them! I love electronics and Microchips, I ha…

For some time now I have wanted to build a Berlin clock. I don’t really know why? I just like the “different” approach to telling the time.

So how does it work??

Starting at the bottom there are 4 yellow lights each light represents 1 minute, so if there are none on this would be zero, one illuminated would be 1 minute and so on.

Next row is the 5 minute row, each lit light represents 5 minutes and the red lights indicate ¼’s of an hour. So to carry on from the bottom line once all 4 lights are lit the next number would be to increment the 2nd row and the bottom would be Zero.

The third row up is hours and like the minutes there are 4 lights (red) each one representing an hour. Once 4 are lit the next hour would be displayed on the top row as one * five hour.

The top row is 5 hours per light. 5, 10, 15 and 20. This is a 24 hour clock.

The last light is the round light at the top which blinks at a ½ Hz frequency i.e. 1 second on and 1 second off.

Step 1: The Mechanical Build.

I thought for a while how I was going to build this clock, then decided that the easiest way was to do was design the clock pieces using QCAD on my raspberry pi, then print out the sections 1:1 then stick the plans onto the
wood and cut out with my fret saw.

I did however want the clock to be dimensionally correct. So I found a few photos of the clock and measure the relevant bits and decided on a main area of 200mm by 200mm the top light will then go on top. The main clock is made out of 3 mm plywood, and the rounded corners are white oak. The glass lens are just frosted Perspex which I had left over from my plasma display.

So I am not going into great detail about the clock build, but you should find all the information you need in the photos and PDF files attached of the bits.

Sequence is as follows

  1. Print out plans and stick to the ply wood, double up the wood as required.
  2. Cut out the bits using a fret saw.
  3. Clean up the edges using sand paper and trial fit parts.
  4. Glue the LED separator parts together and stick to the front and clamp the sides all at once. I use grease proof paper (nick it from the kitchen draw) and this stops the wood sticking to the table or clamps.
  5. Then add the end pieces of ply and shaped corner bits.
  6. Varnish the front sections.
  7. Glue the frosted Acrylic Perspex windows.
  8. Fix all LED’s and connect wires.
  9. Glue in the led sheet.
  10. Glue all the sections together.
  11. Add all connecting wires.
  12. Add back section of ply.
  13. Add the back and buttons.

To make the round top section I formed 3 strips of 1/16" ply wood around a spray can then added a 3mm front and back.

I will point out that I tried a couple of different ways to construct the sections and gluing the back on before the ends didn’t work, you are better off gluing the ends on first and holding the back on temporary (with a weight) so it gets the ends inline for gluing.

For the spacers in-between I laminated 3 pieces of 1/8” ply wood, then cut to size and used a round nose cutter in my router to make the internal radius.

For the back section I have used 1/16” ply which I have carefully cut buttons into. The 1/16” ply was perfect for this and the switches are mounted to the back of the panel to get the correct spacing.

Step 2: The Electronics Build

For the circuits I am using “vero board” One bit is needed for the 3 shift registers, and another bit is needed for the 5 buttons. When I was thinking about the layout for the shift board I decided it would be best to bring Q0 output to the left side to join the other 7 outputs and also get rid of the 9th pin (Q7” serial output) to allow the ground to take its place, I did this by removing the pin from the chip holder, this then leaves a nice clean layout with all outputs on one side (with the ground) and the inputs on the other side. (See the pictures for more info)

I decided to fit the required LED resistors in the ground leg of the singles and in-between the two LED’s where they are mounted in two. This gives 4 different resistor values to give the required brightness.

Red single = 560 Ohms (5V – 1.88V / 560 = 5.5 mA)

Red double = 220 Ohms (5V – 1.88V – 1.88V / 220 = 5.6 mA)

Yellow single = 470 Ohms (5V – 2V / 470 = 6.4 mA)

Yellow double = 150 Ohms (5V – 2V – 2V / 150 = 6.6 mA)

Basically I chose a value of resistance which gave me the best brightness (with the two LED’s) then used a Multimeter to check the voltage and used the value to calculate the single value or resistance required.

Step 3: The Electronics and Program Explained

As explained I am using an Arduino Nano (or clone) to drive 3 * 595 shift registers and a RTC connected to the I2C to keep time. At this point I will try and explain the more challenging parts of the code, as it’s not an obvious one to work out, (although easy once you work it out).

The time is supplied by the Real Time Clock as seconds 0-59, minutes 0-59, and hours 0-23. Because the clock is based on 5’s you need to divide the minutes (and hours) by 5 first this will give you the “5 minutes” row (second
one up) if you then work out the modulus of the calculation then that gives you the remainder which is the 0-4 minutes (bottom) line.

i.e. minutes = 48

48/5 = 9 (store it as minFives)

48%5 = 3 (store it as minUnits)

9 lights on the second row for 45 minutes then 3 lights on the bottom row for 3 minutes add the two together gives you 48.

staying with the minutes the next problem is the 595 shift register only holds 8 bits and I need to use one completely and then 3 “bits” of another register.

So to do this look at the minFives (0-11) and if the number is greater than 7 then split the value into two values.

i.e. if (minFives > 7)

if (minFives > 7)

{
regOne = minFives;
regTwo = minFives – 8;
}
else
{
regOne = minFives;
regTwo = 0;
}

The next issue is to display all the lights and not just the actual number, so in the above example (48) you have 3 lights on the bottom row and 9 lights on the second row. So to correct this for the bottom row you need to turn on light 1and light 2 and light 3, meaning a number of 7 (total 1+2+4) needs to be sent to the serial register. I am sure there is more than one way to do this but I chose to run a loop for the given number and for each loop double the index number and add to the file. (see below for pow() function as an alternative)

i.e.

k = 1; //number to be added each time but will double for each loop.
j = 0; // number of times the loop will run. 
while (j < (regOne))// regOne is a number 0-8 
{ 
  minFivesBar1 = minFivesBar1 + k; 
  k = k * 2; 
  j = j + 1; 
}

hopefully you can work this out, but basically you will get the correct required number of LED’s lit for a given input number.

i.e. 1=1, 2=3, 3=7, 4=15, 5=31

I realised after I wrote this that I could use the pow() function so I will have a try and see if its more efficient. example, input 6 (pow(2,6))-1 = 63

(I tried it out and there really isn't much difference in the time, both are really quick. I guess the pow() looks neater but I stuck with the above function)

Step 4: Shift Registers

Right at this stage we have three values:-

minFivesBar1 (8 bits used for LSB of the second row)

minFivesBar2 (3 bits used for the MSB of the second row)

minBar(4 bits used for the first row)

Now I only want to use 3 shift registers so I need to combine minBar and minFivesBar into the same register, which thankfully is easy to do.

File rotate the minBar 3 times and add to the register.

i.e.

minBar = minBar << 3;
minFivesBar2 = minFivesBar2 + minBar; 

That leaves one last position in the 8 bit register and that will be used for the second pulse, and implemented by determining where the seconds is odd or even and then adding 128 to the file as required. The code looks something like…

if (seconds%2) 
{
  minFivesBar2 = minFivesBar2 + 128; 
}
else 
{
  minFivesBar2 = minFivesBar2 – 128;
}

as I was writing this it didn’t seem correct so in the end I check not only if the number was even but also if it was (AND) greater than 128.

The hours will again have to, firstly be divided by 5 then work out the modulus this will give you the two numbers, then as before work out the “running” total and then store the two 4bit values in one register by file rotating one value 4 times so it occupies the 4 MSB and adding it to the other number which is in the 4 LSB.

The command for driving the 595 serial register is very easy, and requires 4 arguments.

which pin to use for data
which pin to use for clock
whether to send MSB or LSB first
Data to be sent.


Once the command has run the data then needs to be latched by setting the relevant latch pin low then high.

Adjustment

I wasn’t really sure how I was going to adjust this clock when I started to build it, but then I decided that because I had enough input spare I would have 4 buttons and one switch and generate a sub routine to set the RTC depending on which button is pressed. I have also made a GPS clock which can be used to compare the RTC
to GPS and correct it as required (but that’s for another instructable!)

Step 5: The Full Program

#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>
int i = 0;
int k = 0;
int x = 0;
int minFivesMSB = 0;
int minFivesLSB = 0;
int temp = 0;
int minFives = 0;
int hours = 0;
int hoursFives = 0;
int minUnits = 0;
int oldHoursFives = hoursFives;
const int changeTime = 14;
const int setHourPos = 15;
const int setMinPos = 16;
const int setHourNeg = 17;
const int setMinNeg = 11;
tmElements_t tm;
void setup()
{
  pinMode(2, OUTPUT);//latchOne
  pinMode(3, OUTPUT);//latchTwo
  pinMode(4, OUTPUT);//latchThree
  pinMode(5, OUTPUT);//clockPinOne
  pinMode(6, OUTPUT);//clockPinTwo
  pinMode(7, OUTPUT);//clockPinThree
  pinMode(8, OUTPUT);//dataPinOne
  pinMode(9, OUTPUT);//dataPinTwo
  pinMode(10, OUTPUT);//dataPinThree
  pinMode(changeTime, INPUT);
  pinMode(setHourPos, INPUT);
  pinMode(setMinPos, INPUT);
  pinMode(setHourNeg, INPUT);    
  pinMode(setMinNeg, INPUT);
  Serial.begin(9600);
}
void runningTotal(int number)
{
  int s = 1;
  int j = 0;
  temp = 0;
  while (j < number)
  {
    temp = temp + s;
    s = s * 2;
    j = j + 1;
  }
}
void obtainNo()
{
  minFives = i/5;
  minUnits = i%5;
  hoursFives = k/5;
  hours = k%5;
  if (minFives > 7)//split minutes as it rolls over two shift registers
  {
    minFivesLSB = 8;
    minFivesMSB = minFives - 8;
  }
  else
  {
    minFivesLSB = minFives;
    minFivesMSB = 0;
  }
}
void obtainRunning()
{
  runningTotal(minUnits);
  minUnits = temp;
  runningTotal(minFivesLSB);
  minFivesLSB = temp;
  runningTotal(minFivesMSB);
  minFivesMSB = temp;
  runningTotal(hours);
  hours = temp;
  runningTotal(hoursFives);
  hoursFives = temp;
}
void compileRegs()
{
  minUnits = minUnits<<3;
  minUnits = minUnits + minFivesMSB;
  hoursFives = hoursFives <<4;
  hoursFives = hoursFives + hours;
  if ((tm.Second)  % 2 && minUnits < 128)
  {
    minUnits = minUnits + 128;
  }
  else 
  {
    if (minUnits > 128)
    {
      minUnits = minUnits - 128; 
    }
  }
}
void sendToRegs()
{
  shiftOut(8, 5, MSBFIRST, minFivesLSB);
  digitalWrite(2, LOW);
  digitalWrite(2, HIGH);
  shiftOut(9, 6, MSBFIRST, minUnits);
  digitalWrite(3, LOW); 
  digitalWrite(3, HIGH);
  shiftOut(10, 7, MSBFIRST, hoursFives);
  digitalWrite(4, LOW);
  digitalWrite(4, HIGH);
}
void checkAdj()
{
  if (digitalRead(changeTime) == HIGH)
  {
    if (digitalRead(setHourPos) == HIGH)
    {
      unsigned long v = RTC.get();
      v = v + 3600;
      RTC.set(v);
    }
    if (digitalRead(setMinPos) == HIGH)
    {
      unsigned long v = RTC.get();
      v = v + 60;
      RTC.set(v);
    }
    if (digitalRead(setHourNeg) == HIGH)
    {
      unsigned long v = RTC.get();
      v = v - 3600;
      RTC.set(v);
    }
    if (digitalRead(setMinNeg) == HIGH)
    {
      unsigned long v = RTC.get();
      v = v - 60;
      RTC.set(v);
    }
    i = (tm.Minute);
    k = (tm.Hour);
    obtainNo();
    obtainRunning();
    compileRegs();
    sendToRegs();
    delay(300);
  }  
}
void loop()
{
  if (RTC.read(tm))
  {
    checkAdj();
    i = (tm.Minute);
    k = (tm.Hour);
    obtainNo();
    obtainRunning();
    compileRegs();
    sendToRegs();
  }
}
Time Contest

Participated in the
Time Contest

Make It Glow! Contest

Participated in the
Make It Glow! Contest