Introduction: Numitron Clock & Thermometer

About: I'm mainly interested in music, food and electronics but I like to read and learn about a lot more than that.
I really like nixie and numitron clocks, but I never worked with them before. So I decided to give it a go. I choose numitrons because of 2 reasons: first of all nixies need a higher voltage than numitrons to work. Nixies need around 170V DC and numitrons only 4,5V so they are safer to work with and don't need a special powersupply.

The timekeeping is done by a ds1307 I2C realtime clock and the temperature is measured with a DS18B20 1wire temperaturesensor.

An Atmega48 is used to proces the data and to drive the segments in the numitrons. I used Bascom to write the code and a MyAvr MK2 Programmer to stuff it into the microcontroller. You can find a free Bascom demo here. The only limitation of the demo version is that you can only compile 4Kbytes of code (but the atmega48 has only 4Kbytes so that will work fine.)

I added this instructable to the microcontroller contest, so give it a vote if you like it.

Step 1: The DS1307 Realtime Clock

The timekeeping will be done by a DS1307 IC. This is a handy little IC, because it not only keeps track of time but also of the date and the day of the week.

For this little project we'll only use it to keep track of the time. Therefore it needs a 32.768kHz quartz crystal connected between pins 1 and 2. We can also add a battery with + to pin 3 and - to pin 4. This enables the IC to keep working when the mainpower is switched off. If you don't want to use this feature, you can just connect pin 3 to pin 4 and everything will work fine.

Pin 5 and pin 6 will be used to transfer the data to our microprocessor. They should be connected to the SCL and SDA pins on your microprocessor. These lines need to be pulled high by a 4K7 pullup resistor.

Bascom makes working with I2C devices easy. You only need to know 4 commands:
  1. I2cstart: This commant will startup I2c communications
  2. I2cstop: This command will stop I2c communications
  3. I2crbyte var: This command reads a byte from the device and stores it in 'var' 
  4. I2cwbyte var: This command writes the variable 'var' to the device
Using the write or read command is not enough, we will also have to tell the device whether we want to write to it or read from it. We do this by using the right address. These addresses can be found in the datasheet. The write-address for the DS1307 is D0H and the read-address D1H (the H behind it tell us that these are hexadecimal figures).

The DS1307 sends and wants to receive data in BCD format. This is a variation on binary for diplays where every digit is represented by four bits. More about that here. Luckily converting from BCD to decimal and visa versa is very easy in Bascom.
  • var = Makebcd(var) will convert decimal, hex and binary into BCD
  • var = Makedec(var) will convert hex, binary and BCD into decimal

The data is stored on the IC in register. You can imagine them as those oldfashioned filingcabinets. Each drawer has its number and contains some info:

00H Seconds
01H Minutes
02H Hours
03H Day
04H Date                                               The H tell us that these are hexadecimal figures.
05H Month
06H Year
07H Control
08H to 3FH Ram

If we want to read or store some data we'll first have to tell the device in which drawer we want to be. We can do this by writing the hex code for that drawer to the device. The device then will grant us acces to that drawer. After you write or read something from or to this register the device will automatically jump to the next one. So there is no need to send the location every time

Now lets put this in code:

For this code you will need to dim hours as byte, minutes as byte and seconds as byte.

First, we will set the clock:

Seconds = Makebcd(Seconds)                             We convert our variables into BCD format
Minutes = Makebcd(Minutes)
Hours = Makebcd(Hours)

reset hours.6                                                            We reset bit 6 of the hoursbyte to make sure that
                                                                                    our Clock runs in 24h modus. If bit 6 is 1 then the
                                                                                    clock runs in 12h modus and bit 5 will then 
                                                                                    contain the AM/PM data.

I2cwbyte &HD0                                                         We tell the device that we want to write a byte
2cwbyte &H00                                                           We start at the register for seconds hex 00  
I2cwbyte Seconds                                                    Adding seconds
I2cwbyte Minutes                                                      Adding minutes
I2cwbyte Hours                                                         Adding hours

Now our clock is set! Lets read from it now.

I2cwbyte &HD0                                                        We tell the device that we want to write a byte.
I2cwbyte &H00                                                         We ask the device to go to the seconds register.
I2cwbyte &HD1                                                        We tell the device that we want to read bytes.
I2crbyte Seconds , Ack                                           We read the data and acknowledge that we want 
                                                                                     to read the next byte too.                  

I2crbyte Minutes , Ack
I2crbyte Hours , Nack                                             We don't ackowledge here so the device knows
                                                                                     that we are done reading.


Hours = Hours And &B00111111                        We remove bits 6 and 7 as they contain other
                                                                                    data. If you are in 12h modus, then you need to
                                                                                    remove bit 5 too

Hours = Makedec(Hours)                                      We convert back to decimal format.
Minutes = Makedec(minutes)
Seconds = Makedec(seconds)

Now we know what time it is.

In the next step we will take a closer look at the DS18B20.

Step 2: The DS18B20 Temperature Sensor

The DS18B20 is a so-called 1wire device. Sadly, as with many things in life, this description was made up by some clever salesmen as we need at least two wires for it and in our case even three.

There are two ways to set up a DS18B20:
  1. With parasite power
  2. With external supply

To know more about these options, just check the datasheet.

The data line DQ will need a 4K7 pullup resistor and can be connected to the vast majority of pins on your microcontroller.

Communication with a 1wire device is, again, not very difficult in Bascom. There are some commands but we only need three of them for this project.

  1. 1wreset: This commands resets the communication
  2. 1wwrite var: This command writes 'var' to the device
  3. 1wread var: This command reads from the device into 'var'

We also need to setup the 1wire bus with the following code: config 1wire = pinX.y Where X is the name of the port and y the number of the pin.

Lets try to put this all into code now:

We will use the DS18B20 in 12bit mode (default setting) so every bit corresponds with 0.0625degr C or to put it easier: we will have to divide the result by 16 to get the temperature.

For this code you will need to dim tempdata(9) as byte and temperature as integer.

config 1wire = portd.0                  This tells the microcontroller whereto look for the device

1wreset                                          resets and starts the communication
1wwrite &HCC                             This skips transmitting the unique ROM code for the device. This
                                                        code is needed when there are more devices on the same wire
                                                        but we have only one so we can skip it.

1wwrite &H44                              Starts the A/D convertion in the sensor and stores the data into
                                                        the scratchpad

waitms 750                                  The convertion in 10bit mode can take upto 750ms so we wait
                                                        750ms before we start to read the scratchpad.

1wwrite &HCC
1wwrite &HBE                              Tells the device that we want to read the scratchpad.
Tempdata(1) = 1wread(9)          We read 9 bytes into tempdata(), starting from tempdata(1).

If tempdata(9) = Crc8(tempdata(1) , 8) Then                       This checks the validity of the data and
Temperature = Makeint(tempdata(1) , tempdata(2))          combines the 2 first bytes into an
Temperature = temperature / 16                                             By dividing this integer by 16 we have
                                                                                                       our temperature in degr C.

End If

We have our sensor working now.
In the next step we will talk about the buttons.

Step 3: Buttons and Interrupts

To set the time we need some sort of inputdevice. In this project, we will use two buttons. These buttons are connected to the interrupt -pins on the atmega48 (pind.2 and pind.3).

An interrupt does, as its name suggests, interrupt the program to do a little routine. When the interrupt is done, the program continues where it was interrupted. There are lots of different interrupts triggered by internal events caused by timers, comparators.... but we will use 2 external interrupts triggered by the two buttons.

The buttons connect the interrupt pins with ground while a 1K5 pullup resistor keeps the pin high (the interrupt is triggered when the pin goes low).

At the begining of our code, we'll need to setup the interrupts.

config INT0 = Falling                                  The interrupt is triggered on the falling edge.
On INT0 button1                                          When the interrupt is triggered the program will jump to
                                                                        label button1.

Enable INT0                                                 Enables this interrupt

config INT1 = Falling
On INT1 button2                                          When the interrupt is triggered the program will jump to
                                                                        label button2.

Enable INT1

Enable interrupts                                         Enables the use of all interrupts

At the end of the code, after the End - Statement, we will put the 2 needed labels with the routine.


code to set the hour



code to set the minutes


One important thing to remember is that you cannot trigger an interrupt inside the routine of another one!

Now that we are able to set the time, we can look at our display aka the numitrons in the next step.

Step 4: The Numitrons

I bought some IV-9 numitrons rather cheaply, so this clock/thermometer was an ideal opportunity to use 4 of them. They come with long leads so they can be soldered directy onto your pcb. The numitrons came with a very handy Russian datasheet (I really need to learn Russian...). After some testing I found out that they need between 3,5V and 4,5V and that the current per fillament is approx. 20mA.

Numitrons are 7 segment displays, so we need to build up our digits with these segments. There are lots of IC´s to drive 7 segment diplays like the HEF4511 (BCD to 7 segment) but to save space on the pcb, we will drive them directly with the microcontroller. To do so, we will use portb to drive the 7 segments and to the first four pins of portc to multiplex inbetween the four numitrons.

When you use multiplex numitrons, you need to use diodes on all pins, except the common one. This to prevent 'ghost digits' in other numitrons. I used the very common 1N4007 for my clock.

Earlier, I stated that numitrons need between 3,5V and 4,5V. But when we multiplex them, you won't see anything happen. As we enable each numitron for only 5ms at a time, we'll need a higher voltage to make the fillaments glow. Any voltage between 7,5V and 12V will work fine, but I choose 7,5V because I didn't want them to be to bright.

As we use a higher voltage for the numitrons than for our microcontroller, we'll need some transistors too. I used the common 2N3906 PNP-transistor to drive the segments and the BD137 NPN transistor to assist the multiplexing.

Now for some code:

First of all: when we want to display time on four numitrons, we'll need to split it into 4 digits. There are numerous ways to do this, but I do it with this code:

For this code you'll need to dim tube(4) as byte and temp as byte.

The digits are numbered from right to left so the minutes have numitron 1 and 2 and the hours 3 and 4.

Tube(2) = Mins / 10
Temp = Tube(2) * 10
Tube(1) = Mins - Temp
Tube(4) = Hours / 10
Temp = Tube(4) * 10
Tube(3) = Hours - Temp

So if the time is 09:24  :

tube(2) = 24/10  = 2 (the decimals are dropped)
temp = 2*10 = 20
tube(1) = 24-20 = 4
tube(4) = 9/10 = 0
temp = 0*10 = 0
tube(3) = 9-0 = 9

Now that we have the digits, we can multiplex them:

For this code, you need to dim i as byte and j as byte.

For I = 1 To 4

Temp = 7seg(tube(i))
Portb = Temp
J = I -1
Set Portc.j
Waitms 5
Reset Portc.j


This code selects one of the numitrons, converts its corresponding value into 7 segments and enables it to light up by setting the right pin of portc high for 5ms. As the numitrons are numbered 1 to 4 and the pins 0 to 3, variable j will be variable i substracted by 1.

Now last but not least the 7seg-function:

We'll add this funtion in the code to convert the value of the digits into a 7 segment pinout for portb.

First we need to declare the function.

Declare Function 7seg(byval Q As Byte) As Byte

Then at the end of the code we will add the code for the function.

Function 7seg(byval Q As Byte) As Byte

Select Case Q

Case 1 :
7seg = &B01111100

Case 2 :
7seg = &B00010010

Case 3 :
7seg = &B00011000

Case 4 :
7seg = &B00101100

Case 5 :
7seg = &B00001001

Case 6 :
7seg = &B00000001

Case 7 :
7seg = &B01111000

Case 8 :
7seg = &B00000000

Case 9 :
7seg = &B00001000

Case 0 :
7seg = &B01000000

End Select

End Function

Important here is that, as we used PNP transistors, a 0 means that the segment is activated and a 1 that the segment is deactivated. If you wire your munitrons differently to portb, you'll need to find out which bit is needed for each segment.

In the next step we'll look at the brains of our contraption: the atmega48.

Step 5: The Atmega48

The atmega48 is the brain of our contraption. It wil collect all the data from the clock and the thermometer and put it onto our display.

We won't use an external crystal for this project but the internal 8MHz oscillator. There are 2 reasons for that. First of all we don't need a very accurate or highspeed timing and secondly we can use portb.6 for our 7 segments.

The only 2 things that our atmega needs is power and a way to program it.
For power, we hook up VCC to 5V and the 2 GND pins to ground (we also add a 100nF capacitor between VCC and GND).

To connect the atmega with you ISP- programmer, you just have to connect MOSI to MOSI, MISO to MISO, SCK to SCK and reset to reset. (I added a schematic in the images)

At the begin of our code, we'll need to tell the compiler a few details about our microcontroller.

 First of all we need to tell which microcontroller we use:

$regfile = "m48def.dat"

Then we need to tell what clockspeed we use (in Hz):

$crystal = 8000000

Then there are some other bits and bobs we need to discribe(I won't go into detail about them here):

$hwstack = 32
$swstack = 8
$framesize = 24

After this, you can start to write your code.

In the next step, we will bring everything together.

Step 6: Schematic and Full Code

In this step, I added the complete schematic for our project. Below you will find the full code for Bascom in different extentions.

The schematic is split up into 2 parts as it made it on 2 pcb's. I couldn't find a good Eagle library for the IV-9 so I used 8pin sockets instead. (the 2th pin on the numitron is for the decimal dot but we just remove that lead as we don't need it)

I added an image on how to connect the numitron.

In the last step you'll find some details about the casing.

Step 7: The Casing

The casing is made out of five layers of 3mm CNC-milled opague acrylate with 3mm spacers between them. Everything is held together with four M4 bolts.

You can find the drawings in a pdf below.

Step 8: Why I Would Love to Win the Lasercutter

I really wanted this project to look as nice as possible, so I invested a lot of time in the overall design and building the case. I hope that this build is awesome enough to have a chance in the lasercutter contest as I would love to have one of these machines.

They would allow me to make even nicer looking and more intricate cases and parts. Also making gears would be a lot easier, so I would be able to make complex mechaninical movements. A lasercutter would be the ideal solution, as I work a lot with acrylate and other plastics. It would enable me to make designs that that a CNC-router can't make and I wouldn't have to bother my friends all the time to use theire machines.

It would ofcourse also be great for my friends, who also happen to build a lot of stuff, to have access to such a device. So it would surely boost the creativity of those people around me too ( they would surely pay me a lot more visits ;) ). I have no commercial intensions with the machine, just building stuff and helping people build theire projects and ofcourse sharing everything on Instructables.

Microcontroller Contest

Runner Up in the
Microcontroller Contest

3rd Epilog Challenge

Finalist in the
3rd Epilog Challenge