For my Introduction to Analog & Digital Electronics class, students were tasked with building a digital clock. The core logic had to be implemented in 7400 series TTL logic, and the clock was required, at a minimum, to display hours and minutes, and be settable. However, the more extra features the better, and embedded systems are neat, so I designed a clock that would not only keep time accurately, but could display temperature, current date, and arbitrary information. I also used capacitive touch sensing for the human interface components, instead of physical pushbutton switches. The challenge was designing around the constraint of having to use 7400 series logic chips for the core logic, i.e. counters, display driver, and rollover logic. Additionally, I wanted the clock to be settable with mechanical switches, in case I wasn't able to make the extended features work. The resulting clock is somewhat overkill, and certainly klugey, but I'm quite happy with it.
The core logic of the clock consists of six 4-bit counters (74LS161), driving six 7-segment decoder/drivers (74LS47), which drive 7-segment LED displays. This allows for hours, minutes, and seconds. The rollover logic of the counters is configurable via jumpers which select between a maximum display value of 23:59:59 or 99:99:99. Auxiliary logic controls clock input inhibit, and carry inhibit. The total integrated circuit count for this core section of the clock is:
...along with a few decoupling capacitors, 42 current-limiting resistors, and a few pullups resistors. The circuit is straightforward, but takes up a great deal of physical space. Each group of two digits has an increment line and a display blank line. Additionally, the logic has a clock input and a carry inhibit control line.
The key idea behind my design of the clock logic was that I would be able to set the display to arbitrary values using a microcontroller. I added two 16-bit IO expanders (MCP23016) that read the BCD busses between the counters and display decoder/drivers. These IO expanders allowed the microcontroller to read the displayed value. By connecting the microcontroller to the set lines for each digits, it was possible to set the display to arbitrary values. How? With each group of digits set to rollover at 99, the microcontroller could simply calculate how many times the counters needed to be incremented to show the desired value. This operation could be performed much faster that the human eye could notice, and so even in the worst case scenario (decrementing a display by 1, effectively counting up by 99), flicker on the display was barely noticeable. I used an Arduino Nano for the main microcontroller, mainly because it had the right number of IO lines, and I am comfortable with AVR-C as well as the Arduino development environment. The microcontroller communicates with the IO expanders via I2C, a two-wire serial communication buss. Being able to set the display to arbitrary values means that the clock can be cycled through display of the time, date, alarm time, and more.
In order to keep time accurately, I used a real-time clock chip (Maxim DS1307), which also uses the I2C buss. The DS1307 is a great chip; give it a quartz crystal and a backup battery, it will keep time for ten years without external power. It also has 56 bytes of RAM that can be accessed via I2C, which I used to store an alarm time. I also added an I2C digital thermometer (Maxim DS1621) and a small audio amplifier (LM386) with speaker to provide an audible alarm.
Finally, for user input, I decided against pushbuttons, as I was not looking forward to debouncing the mechanical switches. Instead, I used an ATtiny microcontroller to implement simple capacitive touch sensing. Six standard pennies serve as touch sensors, and the ATtiny forwards touch events to the Arduino via a single-line serial connection. Each touch sensor requires a single dedicated microcontroller I/O line. Using a separate microcontroller for the touch sensors is necessary, because the touch sensors are highly dependent on uninterrupted processor cycles, something that would be difficult on the Arduino, as it needs to update the display and poll the I2C buss quite often. The principle behind the touch sensing is based on the code here, although I added an exponential moving average to improve responsiveness and noise rejection.