4-layer PCB (HASL finish), manufactured and mostly assembled by JLCPCB

The ATmega328PB microcontroller is programmed through ISP using a PICkit5. MPLAB X is used as an IDE with avr-gcc as the C compiler.

This is a pretty simple clock I designed around an ATmega328PB MCU. The actual timekeeping is performed by pairing a 32.768 kHz crystal with the asynchronous Timer2 to generate a ~1s timekeeping interrupt (increment seconds). As the oscillator pins are occupied by the 32 kHz crystal, the MCU runs off of the 8 MHz internal oscillator.

This clock has some features I wanted in a clock:

A separate 1.024 ms interrupt (dervied from the 8 MHz internal oscillator) increments the system tick value, which is used for several peripheral features i.e. display blinking, button polling interval, alarm period, etc.

A CD4021 shift register is used to interface eight buttons with the MCU. This was done solely to minimize the number of MCU pins required to interface with the buttons. The following functions are accomplished by each button:

SW1 - Increment hour (wraps around)

SW2 - Increment minute (wraps around)

SW3 - Change timekeeping mode (AM/PM or 24 hours)

SW4 - Change day of the week

SW5 - Set to alarm mode and confirm alarm setting

SW6 - Set /unset alarm - unsetting an alarm during snooze mode will disable the snooze. The alarm must be set after being confirmed - if it is not set, the alarm will not activate when the alarm time is reached.

SW7 - Dim the display. Once the display is dimmed, any button (except for SW8) will exit the dimmed mode.

SW8 - Show the number of seconds in the current minute. If display is dimmed, SW8 will activate the lamp test. Press any button to leave the lamp test function.

When alarm is going off - all buttons except for SW8 will "snooze" the alarm, or push back the alarm another five minutes. SW8 will simply disable the alarm for the next day, when it will activate again. When the snoozed alarm goes off again, SW8 may disable it - any other switch will push it back another five minutes.

As the ATmega328PB has no native USB interface, I paired it with an FT230X to allow it to communicate with an external host PC for remote configuration. I chose a baud rate 38400 bps to minimize the error associated with using an 8 MHz internal oscillator. The following characters may be sent:

All commands (except for TEST) will return an ERROR if sent with incorrect parameters or when the current action cannot be performed (i.e. clock is already set to the desired feature or data is out of range). Otherwise, OK will be returned, except for QUERY / 'A' , which will return data.

'A' - queries the current time. Returns the current time as <seconds byte>, <minute byte>, <hours byte>, <days byte>.

'B' + <hours byte> + <seconds byte> + <days byte> - sets the current time to the bytes sent after B, with seconds set to zero.

'C' - Returns TEST

'D' - Arms alarm

'E' - Disarms alarm

'F' + <hours byte> + <minutes byte> - Updates the alarm time to the indicated hour and minute.

'G' - snoozes alarm if active - ERROR if alarm is not active

'H' - stops alarm if active - ERROR if alarm is not active

'I' - dim display on clock

'J' - activate lamp test on clock

'K' - set display back to normal - will fail if clock is not in lamp test, seconds, or dimmed mode

'L' - alter the display mode - switch between 24 hours and AM/PM mode

'M' - show the number of seconds in the current minute on the display