Pushing the ATtiny85 to its Limits - decino.nlPushing the ATtiny85 to its Limits
After 12 years my trusty bedroom clock drew its last breath (R.I.P.) which left me with a small problem, how do I quickly check the time when in bed? Sure, I could just buy a new one or
check my phone, but that's no fun. You know what's fun? Building your own from scratch.
Initially I wasn't planning to turn it into a big project. Building a clock is relatively simple, just build something that can render digits and add a component like an RTC which ensures
the time stays accurate. I made a plan which involved 4 prototype boards, Adafruit NeoPixels for the digits, a mini-USB connector for power, and a DS1317 to keep track of the time.
Prototype board with wiring and components.
The front when powered on.
After 2 evenings of stripping wires and soldering them I finally finished the first digit. This took a lot longer than expected, and, honestly, I wasn't really looking forward to making the other 3, so I did
what I should've done from the beginning -- I took out the sodium persulphate from my storage room and...
PCBs! Much better and much, much faster to make.
Now with the hardware done, I noticed a few problems. How will I handle daylight saving time? The DS1317 is fairly inaccurate, how will I fix it when it lags behind? I don't want to manually fix the
time by re-uploading the code every now and then. I needed a way to provide input. Luckily I had a spare Bluetooth module laying around and nonchalantly soldered it to one of the digit PCBs.
Now I can use my phone to communicate with the clock.
Now I had LEDs which could render the time as seen on the image above (it shows 13:20 if you look closely), but it's hard to tell. With my 3D printer I made a digit with its segments cut out so
the emitted light diffuses into its respective segment only. When overlaying it with a piece of paper you can clearly see the digits appearing as seen on the video below.
Materialising the digits.
I also needed something to encapsulate the PCBs, so I made a wooden casing in Inkscape and went to a local laser cutter company to manifest my design into the real world. Once done, I put
it all together and I was quite happy with the result.
Aesthetic as hell.
I was incredibly hyped to power on the clock. Everything up to this point looked amazing. I quickly programmed a fancy rainbow pattern for the clock to see if all colours are nice and clear,
Uhh... what time is it exactly?
New problem: the material I used for the 3D-printed digits is slightly translucent causing the light to bleed to other segments. Man, what a bummer, but no despair. I thought of a solution and, with
pain in my heart, disassembled the clock to spray paint the digits black to ensure the light bounces off the material.
Back to square one, but now in black.
Clock seen from above. All digits are now painted black.
Light now stops bleeding to other segments with an incredible result.
Up to this point the software did nothing but take the DS1317's time and render its hours and minutes. Now it was time to add Bluetooth communication to be able to sync the time when needed.
Tackling the ATtiny85's Limitations
I made a list of features I wanted for the clock:
1. Sync the time with your phone's;
2. Adjust the digit brightness for both day and night;
3. Set a time when the clock should dim its brightness (i.e. for during the night);
4. Set a different digit colour for each day of the week;
5. Ability to upload special days (like birthdays and holidays) where the clock will emit a rainbow pattern.
While writing the clock's firmware I never really paid attention to the sketch size and RAM usage. Once I added serial communication and defined an array with some dynamic values I noticed that
the clock would stop working. After a while I figured it was the RAM usage reaching its limit.
The ATtiny85 has 256 bytes of dynamic memory. Some bytes are reserved for essential stuff, so in the end you end
up with 239 bytes of available memory. I had to optimise my code step by step: using 16-bit RGB instead of a byte for each colour, using a byte for the year instead of 2, etc. All these tiny
optimisations trimmed down the RAM usage a little bit. For the special dates I reserved 64 EEPROM addresses -- 2 bytes for each date, so enough for 32 dates.
The sketch size also got dangerously close to its limit, but I managed to pull it off.
One more line of code and it would've stopped working.
I started writing the Android app. Compared to the firmware, this was fairly easy to do. The app has a rather simple layout and sends a 5 byte "packet" to the
clock which in return will attempt to decipher it. The format of this packet isn't too complicated:
For instance, a "brightness adjust" command would look like "10128". The first byte indicates the command ID (1 for adjusting brightness), the 2nd byte tells the clock if it's brightness
for during the day (0) or night (1), and the remaining 3 bytes indicate the brightness value (from 0 to 255 in this case).
For choosing the weekday's colour it'd look like "54088". The first byte (5) is the "weekday colour" command. The 2nd byte is the day of the week (4 is Wednesday). The remaining 3 bytes are
the RGB values ranging each from 0-8 due to compression reasons.
The most complex one is the "special date" command. Its command ID is 7, the metadata tells if it's a day or month, the 3rd byte is the EEPROM address (0-32), and the remaining 2 bytes are
used for the day (1-31) or month (1-12) value. This means you need to send the command twice -- one for uploading the day, and the other one for the month.
There's 9 commands in total.
The App in Action
Below are some clips of the app communicating with the clock.
Adjusting the brightness.
Setting the digit colour for Sundays.
Today's a special day. Party time!
It's been one hell of a month, but I can finally check the time in bed again. Not just that, it will also remind me when it's someone's birthday.
Hopefully I'll be on time with birthday presents if I consider myself that forgetful.