Skip to content

Yannick Ruijter

C/C++ Embedded Systems & Algorithms

Arduino Advanced Alarm

Context

I built this project back in high school, before I really knew anything about version control or had the coding habits I’ve picked up since. So there’s no GitHub repo for it. It was also written at a Dutch-language school, so the comments and variable names are in Dutch rather than English. I’m sharing the code as-is here, unpolished parts included, as an honest look at where I started.

The program

The alarm runs on a custom control loop. No RTOS, no delay()-based timing, just a millis()-driven state machine handling a bunch of things at once. Time and two independent alarms are tracked in software and saved to EEPROM, so your alarm settings survive a power cycle.

Input comes from two places: eight buttons wired through a TM1638 driver I wrote myself, bit-banging the strobe/clock/data lines directly instead of using a library, and an IR remote for navigation, with my own decoder translating raw IR signals into usable commands. Between the two, you can set the time, configure two separate alarms, adjust display brightness, and toggle whether seconds are shown.

Instead of a buzzer, alarms are signaled through blinking LEDs and a flashing display. I ditched the buzzer after deciding it gave me more of a headache than it was worth. You can dismiss an alarm with a button, or snooze it with a wave: an ultrasonic sensor picks up three hand-waves within a few seconds and treats that as “snooze,” temporarily quieting the alarm before it kicks back in.

alarm 05

Technical limitations

A lot of the design here comes down to what the Arduino Uno can and can’t do:

  • No real-time clock. The Uno doesn’t have an onboard RTC, so time is tracked purely in software via millis(). That means the clock resets every time it loses power and has to be manually re-synced. Right now that’s done by sending the time over serial from a computer.
  • Limited pins. There aren’t nearly enough digital pins to directly drive 8 buttons, 8 LEDs, and 8 seven-segment displays. That’s the whole reason the TM1638 is in the picture — it lets me control all of that over just 3 pins.
  • One core, one thread. Everything — reading buttons, updating the display, checking alarms, reading the distance sensor — runs one after another in a single loop, timed with millis() instead of blocking delay() calls.

What I learned

This was the first time I wrote a hardware driver completely from scratch. Bit-banging the TM1638’s protocol by hand with shiftOut/shiftIn taught me a lot about what’s actually happening at the bit level when you talk to a chip, instead of just calling a library function and trusting it works. Decoding raw IR signals was a similar kind of lesson, turning a messy physical signal into something my code could actually use.

Self Reflection

Looking back at the code, there’s a lot of things I’d do differently now. At the time, I didn’t know how to structure a project beyond one long file with a pile of global variables and boolean flags. That’s simply how I was taught to write it back then, in plain C rather than C++. If I rebuilt this today, I’d bring the C++ knowledge and guidelines I’ve picked up since. Splitting the code into proper classes across separate files. One for the alarm logic, one for the display, one for input, instead of cramming everything into a single loop and a handful of global state. I’d also actually reach for real design patterns instead of stumbling into a rough imitation of one. The State pattern for cleanly handling “current time” vs. “alarm 1” vs. “alarm 2” vs. “set time mode,” and probably the Command pattern for the button and IR input handling, so adding new actions wouldn’t mean digging through a wall of if-else chains.