Introduction
An obvious question, what is a binary clock? "A binary clock is a clock that displays the time of day in a binary format." Well thanks Wikipedia. The second obvious question is why? I suppose originally these were built because proper displays were expensive or simply unavailable. But at this time and age it's because they look cool and nerdy and no normal person can read them. I've never been particularly thrilled about binary clocks, but lately I've started to use one in Linux environment at work. I started using it purely for fun, because running Linux in Windows (emulated or VNC'd) makes you have two clocks on the display. With some luck they will even display the same time. Now since there are two of them and I'm used to the Windows clock, I've set the other one to show time in binary. And of course the XFCE clock can show the time in binary format (mode specifically binary-coded decimal), why wouldn't it? After some time I kind of started to like it even though I still cannot read it very fluently. Eventually I've decided that I want to build a physical binary clock myself..
Now there are plenty of binary clock projects on the net (and apparently there are also commercial products). Mostly these use LEDs to display the time. Some of these are made with discrete components and some are made with microcontrollers. I've decided to add some bling-bling to the clock and create one using individually addressable RGB LEDs (WS2812B), which is also not unique to this kind of device. The control logic will be implemented using an AVR microcontroller (not an Arduino) and there should also be some buttons for setting the time, USB-port for power, CR2032 battery for timekeeping when the main power is disconnected, some sensor to measure ambient light and then a temperature & humidity sensor. There should also be an optional output for a piezo speaker to set an alarm. Because why not cram everything that I can come up with at the moment into this device.
Now it should be noted that the temperature sensor might be quite useless in this device, because the LEDs might increase the temperature of the sensor compared to the ambient temperature. However I already decided to include the sensor. After all the footprint of the component doesn't cost anything (except time and energy spent on routing of the PCB). The sensor itself does cost some money, but that's fine. Similarly the light sensor might pick up the light from the LEDs themselves and thus render the feature absolutely useless. But there's more about these later on.
Original sketch |
Above is a sketch I've drawn of this device. This is not really the first sketch, but I don't have the real original anymore. I've decided to create an 4 x 4 array of above mentioned LEDs and place most of the other components on the back of the PCB. There should also be mounting holes on the edges of this square PCB and there should be a USB-port on both edges of the PCB. The USB-port can thus be soldered to either edge according to user preference. Again, the footprints don't cost anything.
I usually don't make enclosures for my projects unless there's a very good reason. A good reason would be insulation for example, but in this case the reason would be to diffuse the light of the LEDs. In retrospect, I was correct, the bare LEDs are painful to look at without any diffusor. Unfortunately I don't have a 3D printer and I'm not really thrilled about it anyways. I have however an idea for this project, which includes simply two pieces of specifically cut plexiglass or any similar transparent plastic. The pieces should then be sanded to have this kind of matt surface that should diffuse the light nicely.
Schematic
The schematic of the project is fairly simple. There is an ATMega328PB, a clock crystal, backup battery, string of daisy chained LEDs, lever type switch, two USB-ports and necessary capacitors/diodes and such. It's good to note that 13 LEDs would have been enough to represent hours and minutes, but I've decided to add all 16 LEDs for symmetry and for example to be able to represent the temperature with two decimals. Additionally there is the SHTC3 temperature & humidity sensor, voltage regulator for the sensor and also a two-way level shift circuit for the sensor. This level shift is necessary because the AVR and the LEDs should use 5V rail, but the maximum voltage for the sensor is 3V3.
There is also some circuitry that is necessary to detect loss of power, since the microcontroller should know when to switch off everything unnecessary to only keep up the time when operating from the backup battery. There should also be respective circuitry to switch back to normal operation when the power is connected. For this purpose I've added a voltage divider (R10+R11) to be used with the comparator circuit in the microcontroller but it was eventually unnecessary and a simple "sense" input (R16) from 5V rail to INT0 pin was enough. Additionally there are some chokes and other filtering in the schematic which is probably unnecessary. However it was easy to add these components and they might simplify the code and reduce number of issues, for example noise on the power rails. The schematic in the picture is quite difficult to read, but I will share the whole EasyEDA project later on (probably in part 2).
The full schematic |
PCB
I've decided to make the circuit board 2200 mils wide. I'm located in Europe but am still used to this unit since I've used it when I started designing electronics. Also some things are strongly related to this unit, for example the headers have either 100 or 50 mils pitch (there is, of course, a rarely used 2 mm pitch). Now I've located the LEDs with a pitch of 400 mils which is around 10 mm and the mounting holes are in the corners as explained before. This way the resulting device is around 5.5 x 5.5 cm.
The light sensor should then be on the left side closer to the top. Since the left column will normally display hours, the top two LEDs will never light up. This way the sensor will not pick up as much of the light that the clock itself creates. I've also tried to locate the temperature sensor at the bottom of the PCB, so that it would hopefully heat up less than the top of the board. The battery should be on the back of the PCB, since it doesn't fit anywhere else. The piezo speaker doesn't have a specific place, since it's connected with wires and can then be attached to any free space at the back of the PCB as long as the wires are connected.
The USB-ports are located on both edges close to the bottom edge. This way there's less chance the USB cable will tip over the device. The switch should be on the right side of the PCB closer to the top. I suppose it would be easier for right handed people and most people are right handed. One other important part is the programming connector, which should be located on the top side of the PCB next to the LEDs. History has shown that it's better to have the programming connector in front so that you don't have to flip the board constantly to reprogram it and see the results. Test-points are located on the topside of the PCB for the same reason.
PCB design, top side on the left, bottom side on the right |
Code
The programming part was actually quite fun. Here's a more or less detailed explanation of some of the features I implemented. Note that this code is not really universal and is quite specific to the used microcontroller which is an ATmega328PB.
Time
Obviously the main theme of this device is to have clock functionality. Some AVR microcontrollers (including ATmega328PB) have an asynchronous timer that can be clocked from an external 32768 Hz crystal when the rest of the chip is clocked from the internal RC oscillator. This is most useful in sleep mode, which on the other hand is necessary for the device to function from the CR2032 battery (for a sensible period of time). The AVR itself uses a considerable amount of current in active mode, so it's not enough to just switch off the LEDs when the USB-power is lost. More specifically the current consumption of active mode is some milliamperes whereas current consumption of the sleep mode is some microamperes.
The maximum time interval that can be created with this asynchronous timer is 8 seconds. Since this device is not even able to represent seconds (it could, but it won't) we're going to use exact interval of 8 seconds. It means that every other minute will potentially change 4 seconds late, but that's absolutely fine. The people who are going to see the clock, will not need to know that. Below is some sample code for time keeping.
Displaying the time requires some relatively simple maths. First the hours and minutes need to be changed from binary to bcd (binary coded decimal). This means that tens of hours (for example) should be represented with four bits and ones should be represented with separate four bits. A simple example could be number 24 (decimal). In binary 24 would be 0b00011000 (0x18 in hex) and in BCD format it would be 0b00100100 which, surprisingly, is 0x24 in hex format.
After transforming hours and minutes to BCD, both values could be stored in one 16-bit mask so that there is one bit per LED on the display. The hours should simply be shifted by 8 to fill all the bits. This way there are four numbers (two for hours and two for minutes) and each number requires up to 4 bits to be represented which makes 4 x 4 = 16 LEDs. Below is a slightly inefficient way to transform numbers to BCD and example of how the values should be shifted.
After some research I decided to scrap all the things I've found on the internet and use simple loops since they are much more efficient than using a remainder function. The AVR I'm using doesn't have direct CPU commands for division, so it's much better to use addition and subtraction in this case. The code below subtracts 10 from the source value and adds 10 in hex to destination value as long as the source is bigger than 10 (in decimal). After that the dst variable should have the tens in "bcd format" and the src should have only ones, they stay the same in both formats. It's important to note that this function works only for values less than 100, but it could of course be easily expanded for larger numbers.
Lastly the display should be updated according to the mask calculated above. I've used a ready made function from Pololulu to control the LEDs. This is because I don't know assembly that well, so I simply decided not to go there now. Use of assembly is required here, because the LEDs require very fast timing. To use the ready made function I simply need to create an array of colours for each LED, specify the active and passive colours and transform the bit mask from above to this colour array. The reason for using active and passive colours is simply because in the dark the user cannot really distinguish times 11:00 and 00:11. The display will also be completely dark at 00:00 and I still haven't got used to that. For this reason I've made it possible to set the passive to something else than "completely off".
There is also a minor problem with the display_bits above. The problem is that the LEDs are physically in the opposite order compared to this bit array (lsb is in top left corner and not bottom right). This means that the bits have to be reordered. This could be done explicitly, or it can be done in the colour generation below (see 15-i). Example code can be seen below.
Controls
The clock has to be set to the correct time and there are multiple other functions that the user should be able to use. For this purpose the device has a TMHU33 which is a "lever and push operation type switch tact switch" ... thing. In practice it's just three buttons and for simplicity I've decided to add hardware-based debouncing with discrete components. The chosen debouncing technique is not very good but it's sufficient for this device.
In software the buttons are read by using interrupts. The first stage is simply identifying which button was pressed and for how long. The next step is to handle the respective action. In this project the up and down movements of the "lever" are connected to pins (PCINT1 and 2) with the "pin change interrupt" feature. The downside of these pins is that 8 pins have a single interrupt. The push movement is connected to a specific interrupt pin (INT1) that has its own interrupt. I've also added a feature that the push button can be long pressed, which would have a different action than a quick press.
Below is an example of how the inputs can be processed. First the pins and respective action bits are defined for each button/action. After that the buttons variable is defined for processed key presses. After that there are two interrupt service routines, first for the push and long press actions and the second for up and down actions. These routines compare the current states of the respective pins to previous states and set the respective bit in the buttons variable if there is a rising edge. Additionally the first routine checks against the counter of the asynchronous timer to check if it was a normal press or a long press. The time is compared to a timestamp that is taken at the falling edge. At the bottom are the necessary register writes to enable the respective interrupts.
The buttons variable should then be processed to make some actions. After some trial and error in different projects, I have come to the conclusion that the structure represented below is the shortest and most clear one. The outer if statements are done according to device states and the inner ones are done according to the buttons pressed. Using one-liners makes the code neat and tidy as shown below. This example shows the necessary code for some state changes and setting the hours.
Sleep
Lastly the device should be able to function from a single CR2032 battery for as long as possible (without the display of course). I actually don't know why as long as possible. I guess it's just a challenge of sorts since the microcontroller is definitely able to do that. However most of the work is done by the two diodes in the schematic, since the power from the battery does not go to the LEDs or the temperature sensor at all. However the microcontroller itself consumes a considerable amount of current in active mode when clocked at 8 MHz. The microcontroller could go slower of course, but the 8 MHz frequency is required for driving the WS2812B LEDs (see the borrowed code). However the LEDs don't need to be driven when they have no power and neither the temperature sensor should be read. The time should of course be updated, but for that the 8 MHz frequency is no longer necessary. More over the 8 MHz frequency cannot be used in case the CR2032 battery voltage has dropped below 2.7 V. The datasheet specifies that the safe frequency range for voltages below 2.7 V is 0-4 MHz. The frequency however cannot be too slow since it has to be at least fore times faster than the asynchronous timer mentioned before. Since the timer works at 32768 Hz, the lowest usable frequency of the CPU is 125 kHz (8 MHz / 64). I have not done such a feature before but apparently changing the frequency divider on the fly in AVR microcontroller just works. This change affects the delays and buses, but we don't use those in this case (the only timer used is, again, asynchronous from the main CPU).
In addition to changing the main clock frequency, the device should also go into sleep mode to reduce the current consumption even further. In this case the device will then be woken up by the asynchronous timer. This is quite necessary since the current consumption of the active mode even at 125 kHz is too high for the device to function a meaningful amount of time from the back up battery. The back up battery could of course be bigger, but that's just unreasonable if the issue can be solved in other ways.
The main task here is to detect when the device loses or gains USB-power. In this project it's very simple. It's basically done with the resistor R16 that is connected from the 5V rail to the INT0 pin. The idea is that the respective interrupt is configured to detect any change on that pin, so that both the loss and gain of power are detected. This works well since the microcontroller should always have power with the help of the battery and the diode D2. A few things should be done immediately after the power loss: change the frequency, disable the button interrupts, stop driving the LEDs and reading the sensor. After that the device should also go into sleep mode. Unfortunately stopping the control of LEDs and reading of the temperature is not very easy. It would require a check to every part of code to break out if the device loses USB power. I didn't want to write so much extra code, so I just made sure that the device will not get stuck in either of these procedures.
Now there is one more important detail when dealing with this specific microcontroller. The asynchronous timer is running much slower than the main clock, which might cause the interrupt that wakes up the CPU to wake it up again multiple times if the device enters sleep too soon. For this purpose there is a dummy write to any (unused) Timer2 register and a check that the write was successful before going back to sleep. Without this code, the time might run a bit faster when running from a backup battery, which is a hilarious bug (not really). Below is an example with the interrupt service routine, necessary register writes and the if statement for the main code. Additionally I'm disabling the ADC feature and some others through the PRR register (power reduction register).
Note: The power loss might happen in the middle of reading the temperature. This will both affect the bus and the delays. But that's not the main issue, since the sensor loses power and cannot really reply to the i2c commands. For this reason the i2c library should take into account the case where the sensor is simply "disconnected" in the middle of a transaction. Similarly the power loss could happen while the LED data is being transmitted. In this case the transmission will be incorrect but the LEDs will not be receiving it in any case since they will also lose power like the temperature sensor. The control of LEDs is unidirectional so in this case the microcontroller will not get stuck. The power loss might also happen while the ADC conversion is running and I'm honestly not sure what happens in that case.
It's important to note that the sleep command should be part of the main loop in the code above (and not inside the interrupt). This way the normal code execution will resume after the first interrupt and only enter the sleep mode after that. The reason for this is that the time calculation is inside the main loop. After the timer interrupt the device will wake up, process the time and go back to sleep.
ps. I've measured that the device uses ~2.5 uA in sleep mode with the input voltage of 3 V. The reading goes down to almost 2 uA at 2 V but that's quite irrelevant. In the active mode the device consumes around 220 uA at 3 V and around 150 uA at 2 V. I've also measured with an oscilloscope that the active mode lasts for 377.5 microseconds. At this point we know that the frequency of the active mode is 128 kHz, which means that one clock cycle is 7.8 microseconds. This means that the CPU takes around 43 clock cycles to process the interrupt routine, update the time and go back to sleep. Perhaps I could have calculated that instead of measuring with an oscilloscope, but I didn't. However with these numbers we can calculate the average current consumption of the device, which is approximately:
I_avg = 2.5 uA + 220 uA * 377.5 us / 8 s = 2.51 uA,
which is not a lot more than the current consumption in sleep mode. Next we can find out that the capacity of the CR2032 cell is 235 mAh (according to Energizer datasheet). After that we can easily calculate that the device should be able to function from that battery for 93625 hours which is approximately 10 years.. It really feels like I have a mistake here somewhere in my calculations, but I cannot really find where so I suppose that's correct. The capacity of the battery assumes room temperature and 15 kOhm load impedance, which would mean around 200 uA at 3 V and is quite valid in this case.
Other features
There are obviously other things in this device including the temperature/humidity measurement, alarm and whatnot. I will to discuss these in the following posts.
Enclosure
The enclosure is also technically very simple. It consists of two sheets of plexiglass (or any similar plastic) that are cut to the size of the PCB and also rounded accordingly. Additionally there are mounting holes and originally there was supposed to be a hole for the battery holder in the back. The surface of the sheets should also be sanded with very fine (400+) sandpaper to create a matt but still transparent case. The downside here is that the device is obviously open from the sides. So this is not really a safe device for people who have an urge to stick conductive items into electronic devices.
Unfortunately I did not plan this phase as well as I should have but nevertheless the result is quite good in my opinion. I used random screws, nuts and "standoffs" to sandwich the PCB between the plastic sheets and lastly sanded the sheets so that the whole device is tilted at 85 degrees. This angle was measured empirically, the device would fall backwards at 75 degrees so I decided that 80 would be too much and made it 85. I would prefer to have chrome coloured screws in this device, but I only had these yellowish ones so I guess they will do.
Result
I'm quite satisfied with the results even though there are some issues here and there when looked at too closely. The most annoying issue regarding this device is that the light sensor was not connected to an ADC pin in the schematic. This is a very stupid mistake that I had to fix. While programming I also realised that there is no voltage measurement for the battery and that is something that would be nice to have. Eventually I had to add it with an extra wire. I've learned that soldering wires to an TQFP32 case with 0.8 mm pitch is not very simple but still it was doable.
The programming part was fun even though again I had some issues which took an unreasonable time to solve. I haven't yet tried the light sensor but the temperature sensor is clearly measuring a significantly higher temperature than the ambient temperature and I have yet to find a fix for it.
The resulting PCB is shown in the pictures below. The added wires can easily be seen in the picture as well as an RC filter for the battery voltage measurement. I'm not really sure if the RC filter is meaningful here, but nevertheless it's there. Honestly soldering the wires to the pins of the microcontroller wasn't fun. Using thin and uninsulated copper wire would have made it easier but I didn't have any at hand. The speaker can also be seen in the picture. It's attached with the adhesive that the speaker was sold with.
Final PCB design |
The final design including the case is also shown below. It might be seen that the pieces of plastic are a bit smaller than the PCB. That was a minor mistake, but it's not that bad. This issue was caused by lack of proper planning, proper tools and proper amount of time to finish this build. :)
Final result pictured from all (meaningful) sides |
I've also had an amazing idea of taking a video of the rotating device and I've actually succeeded in doing that. So here's the video which shows the 360 degree view of the device. It took me some time to balance it on the fan but in my opinion it was worth it.
Finally here's a video of the device in action. It doesn't really show much but I've thought that it's necessary to add one here. The camera doesn't like bright spots with sharp edges so the video looks a bit funny. I don't know how to describe this, but I would say that the display looks much better in real life. I will have to try to take more videos with different brightness settings and different background lighting in the following parts.
Final words
Obviously this project was made purely for the pleasure of making it. Originally I had no intention of using it and was already asking if my friend wants to have one. However I now have a place for it and it might have helped me a little to learn to read binary values. A normal person would never need to read binary (unless it's in some nerdy puzzle game) but for me the ability to read binary is a great help both in my hobbies and work, even if it's not exactly necessary.
I will definitely post more about this clock as soon as I get the other features implemented. I came up with lots of ideas regarding the basic functionality of the clock and also some purely fun features. There's a lot to learn from this project even though it was made purely for fun. Fortunately most of the solutions presented here (and the following parts) are applicable to many other projects that I've done or am going to do, so this hasn't been a waste of time.
ps. Someone might notice the strange programming connector on the board. The connector is an imitation of the Tag-Connect connector. The price of the connector was too much for me so I've created my own version using cheap eBay pogo-pins. I will make a post about this some time soon.
No comments:
Post a Comment