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
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:
- I2cstart: This commant will startup I2c communications
- I2cstop: This command will stop I2c communications
- I2crbyte var: This command reads a byte from the device and stores it in 'var'
- I2cwbyte var: This command writes the variable 'var' to the device
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:
04H Date The H tell us that these are hexadecimal figures.
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
There are two ways to set up a DS18B20:
- With parasite power
- 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.
- 1wreset: This commands resets the communication
- 1wwrite var: This command writes 'var' to the device
- 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
waitms 750 The convertion in 10bit mode can take upto 750ms so we wait
750ms before we start to read the scratchpad.
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.
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
Enable INT0 Enables this interrupt
config INT1 = Falling
On INT1 button2 When the interrupt is triggered the program will jump to
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
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
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
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.