Close
0%
0%

Alarm Clock with Remote USB Interface

A device I designed to fuse 74-series logic with an ATmega328PB microcontroller

Similar projects worth following
582 views
I've combined an ATmega328PB microcontroller with some glue logic to implement an alarm clock. This clock also contains on an on-board USB-to-serial converter that allows it to directly interface with a host PC to enable remote configuration of its features.

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 simple four-character display shows the current hour and minutes.
  • It has a simple alarm feature that allows a single alarm to be set. A buzzer with a 500 Hz tone will sound when this alarm is activated.
  • It tracks the day of the week with seven LEDs
  • It can display the number of seconds in the current minute (accessible by a user interface setting)
  • It can switch between AM/PM and 24 hour display modes (indicated by LEDs as well)
  • It has a dimmed display mode that disables all lights on the display during the night time to allow it to be used as an alarm clock and reduce power consumption.
  • It has a lamp test mode that lights up all LEDs on the board for testing.
  • A Micro-USB interface allows for a simple power interface (have lots of micro USB cables laying around), as well as remote testing of the board's features without the user display through the USB data interface.

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)

  • In normal mode, the current hour is incremented
  • In alarm mode, the alarm hour is incremented

SW2 - Increment minute (wraps around)

  • In normal mode, the current minute is incremented (seconds are set to zero)
  • In alarm mode, the alarm minute is incremented

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

  • LED will show the current mode (D8 is on to show PM, D9 is on to show AM, both D8/D9 will be off for 24 hour mode)

SW4 - Change day of the week

  • Seven LEDs indicate the day of the week (D1-D7)
  • D1 indicates Sunday, D7 indicates Saturday, etc.

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...

Read more »

source-12-27-2024.zip

C source for the ATmega328PB MCU

Zip Archive - 6.59 kB - 12/27/2024 at 23:00

Download

serialtest.py

Python script for testing the serial interface between a host PC and the alarm clock

x-python-script - 2.63 kB - 12/13/2024 at 02:17

Download

Alarm Clock.pdf

Schematic file for PCB

Adobe Portable Document Format - 799.42 kB - 11/23/2024 at 02:20

Preview

Manufacturing.zip

Manufacturing files (Gerbers, drill) for PCB manufacturing

Zip Archive - 275.83 kB - 11/20/2024 at 01:37

Download

  • 8 × PTS647SM38SMTR2LFS Buttons
  • 1 × ATMEGA328PB-AU
  • 1 × SN74LS47DR Logic ICs / Decoders, Encoders, Multiplexers, Demultiplexers
  • 1 × FT230XS-R Microprocessors, Microcontrollers, DSPs / Microprocessors (MPUs)
  • 2 × ULN2003A Discrete Semiconductors / Transistor Arrays

View all 18 components

  • Update: bug fix, calling project complete

    Zachary Murtishi20 hours ago 0 comments

    I've identified a bug that will set the number of seconds in the current minute to zero whenever the alarm is updated remotely and corrected it in the latest firmware update.

    Other than that, I'm calling this project complete as the clock is now functioning exactly how I intended it to. I've let one run for a few days and have identified no further issues with it at this time.

  • Update: Code optimization and feature to display seconds

    Zachary Murtishi7 days ago 0 comments

    For this update to the firmware, I added a mode that displays the number of seconds in the current minute. I thought this would be useful for my purposes as I'm always wondering how many seconds are in the current minute - the clock has the data, but not a good way to display it in addition to the current time. I thought the best way to implement this would be to make it a separate display mode. As always, I also added support for this mode to be activated through the remote interface.

    I also collapsed the separate lampTest and dimmedLights flags into a single displayMode flag for changing the display mode. This display mode can either allow the showing of normal time, display the digits for a lamp test, dim the display, or show the number of seconds counted in the current minute.

  • Update: lamp test feature

    Zachary Murtishi12/13/2024 at 02:14 0 comments

    Update: short update today. Added a lamp test feature to test all of the light-emitting elements on the PCB. How do I accomplish this? Feeding 8s to the 74LS47 during the normal 61 Hz refresh cycle, turning on all of the LEDs directly controlled by the MCU, and then performing a 35 Hz refresh of the day LEDs (required as they are controlled by the 74HC238 and therefore can only be turned on one a time normally).

    My camera appears to have caught the day LEDs between refreshes so some of them appear to be off - but to the human eye, they appear to all on at the same time.

    How do you trigger the lamp test? Press SW7 to dim the lights, then press SW8 to activate the lamp test. Press any button after that to exit the lamp test.

  • Update: cleaned up USART code

    Zachary Murtishi12/12/2024 at 01:10 0 comments

    Bytes:

    0x41 - Query current time. Returns 4 bytes representing current seconds, minutes, hours, and days
    0x42 - Set current time. Send 3 bytes afterward containing minutes, hours, and seconds. It will always zero out seconds. Each byte must be sent within 10 ms of the last one to avoid a timeout. ERROR will be sent if the time is invalid. DONE will be sent if the time is valid.
    0x43 - Will return "TEST", followed by a newline and carriage return.
    0x44 - Will enable the alarm. If the alarm is already enabled, ERROR will be sent.
    0x45 - Will disable the alarm. If the alarm is already disabled, ERROR will be sent.

    0x46 - Set current alarm time. Send 2 bytes afterward containing minutes and hours. Each byte must be sent within 10 ms of the last one to avoid a timeout. ERROR will be sent if the received time is invalid. DONE will be sent if the time is valid.

    0x47- Snooze the alarm if it is active. If the alarm is inactive, ERROR will be sent. If it is active, the alarm will be snoozed and DONE will be returned.

    0x48- Stop the alarm if it is active. If the alarm is inactive, ERROR will be sent. If it is active, the alarm will be stopped  and DONE will be returned.

    0x49- Dim all lights on the clock if the lights are not dimmed. If the lights are already dimmed, ERROR will be returned. Otherwise, DONE will be returned and all lights will be dimmed.

    0x5- Turn on active lights on the clock if the lights are dimmed. If the lights are already on, ERROR will be returned. Otherwise, DONE will be returned and all active lights will be turned on.


  • Update: code cleanup, minor bug fixes

    Zachary Murtishi12/11/2024 at 02:03 0 comments

    I finally got around to some housekeeping on the code:

    1. Fixed a bug from the method I'm using to blank the display. This bug would leave the display blanked if it an alarm was snoozed or disabled during the off period of the display flashing. 
      1. It feels wrong to use the port direction register to accomplish this so I'm just using the same technique I use to blank the digits mid-refresh - send the null character (0xFF) to the 74LS47 at all times.
    2. Changed how the buzzer works. Instead of leaving the Timer1 overflow interrupt on the entire time and toggling the port direction register to activate the buzzer, the Timer1 overflow interrupt is enabled upon activation of an alarm.
    3. Got rid of modulo operations wherever I could. They weren't necessarily an issue as far as program execution speed went, but conditionals are easier for people to follow + have an inherent speed advantage on the ATmega328PB, which doesn't have any floating point hardware.
    4. Cleaned up the code. Moved all functions to a header file and removed their dependencies on global variables - now, global variables are passed and updated through pointers. I still have more to do, but this is a start.

  • Update: Code optimization, dimmed light mode, quality of life improvements

    Zachary Murtishi12/10/2024 at 01:31 0 comments

    I went and added some more features to the clock today, including:

    • A feature that dims all light-emitting elements on the PCB, including the seven-segment displays and discrete LEDs. I figured this would make it a more practical alarm clock, as the brightness might make sleeping next to it an issue. This is activated by pushing SW7.
    • Any button will bring the clock out of this dim mode. Alarms going off will automatically end the dim mode.
    • All switches will now snooze the alarm if it goes off - except for SW8, which turns off the alarm.
    • The display now flashes when the alarm goes off as an additional indicator that you have to wake up.
    • Changed the way LEDs update: this is now updated periodically during a 4ms periodic refresh i.e. conditions are checked and LEDs are illuminated accordingly. This removes the need to update LEDs whenever modes are switched.
    • Refresh rate of the screen was bumped from ~50 Hz to ~60 Hz. Originally, refresh rate was on a 5ms periodic timer in which the digit to be illuminated would switch every 5ms - this results in a 20ms period to refresh all LEDs, in turn resulting in a ~50 Hz refresh period. This has been updated to be a 4ms periodic timer to bring the refresh rate higher to ~ 60 Hz

    The updated code is in the files section.

  • Update: Alarm code working, added basic serial functionality

    Zachary Murtishi12/09/2024 at 11:09 0 comments

    I've been working on adding the alarm and snooze/stop functions to the alarm clock and have been successful so far. Things to note so far:

    • A single alarm can be set with SW5 (using the same buttons to change the time) and armed with SW6
    • The alarm tone is TBD, but right now it is a 250 Hz square wave with a 50% duty cycle. Need to work on this more, but it is simply a placeholder that seems to work well.
    • Snooze causes the alarm to stop going off, but then triggers another alarm in 5 minutes unless the alarm is de-activated.
    • Added some basic remote configuration functionality over serial. This uses the on-board FT230X to communicate with a host PC. This remote configuration capability should ideally allow configuration of all clock settings from a serial interface without the use of buttons. Right now, the current time (day/hour/minutes), alarm status, and alarm time (hours/minutes) can be set over serial. The ability to snooze and stop an alarm will be added next.
    • Serial here is a simple 8N1, 38400 baud interface. Why 38400? The error for a 38400 baud rate seems to be much less than all others and this is important for a system running off an internal oscillator.
    • I foolishly tied the FT230X's reset line to the main reset line without a jumper so now I can't use debugWIRE for serial functions. Bummer.

  • Update: PCBs arrived and prototype clock code

    Zachary Murtishi12/07/2024 at 12:42 0 comments

    I received 2 assembled PCBs earlier from JLCPCB this week and have been toying around with the board's various peripherals. Everything seems to work as intended, thankfully - I'm always a bit worried about my PCBs not coming out how I intended. In fact, the PCBs look great:

    The good news is that the FTDI FT230X USB-to-serial converter on the PCB works very well and responds , despite me accidentally using the wrong resistors specified (22 ohm vs 27 ohm), neglecting to use the capacitors they specified, and relying on the 8 MHz internal oscillator on the ATmega328PB. The USB power interface works - nothing to report more there.

    The interface to the ULN2003 load drivers seems to work flawlessly - before anyone says "but the ULN2003 is old and outdated!", I used them solely because JLCPCB classifies them as a basic part without additional setup costs. The LEDs (and the 74HC238 glue logic for day selection) work well and are reasonably bright.

    The seven-segment driver hardware I put together also works very well - the 74LS47 and a series of 4 PMOS transistors driven by a 74HC139 are responsive and respond well to the scan algorithm executed by the ATmega328PB. Right now, a 50 Hz refresh rate is being used and results in no visual discontinuities.

    It took some tinkering, but the 4021 shift-register interface for the 8 user input buttons is implemented and uses some NOP commands to ensure that the datasheet delay times are being respected. What is unfortunate is that routing challenges resulted in SW1 not matching up to bit 0 on the shift register, but things like this are easily fixed in software.

    The Timer2 overflow interrupt driven by the 32 kHz crystal results in a near-perfect 1 second interrupt interval as configured. I've previously experienced some minor annoyances trying to do the same on a PIC16LF1823 due to the 16-bit timer register there and slow PIC CPU operation throwing off the timing slightly.

    I wrote a test program to activate the buzzer with a variable Timer1 output and it's just loud enough for this application. I still have to work on a proper frequency to get that working how I want it to.

    I've been using avr-gcc + MPLAB X with a PICkit 5 (configured for the ISP interface) for development and have experienced no issues so far.

    I wrote a test program to implement a basic clock with 24HR/AM/PM modes implemented as well as some buttons to change the minutes/hours/day of the week. Day of the week is implemented and will rollover when the hour counter hits 24. I set the clock, left it on the entire night, and returned to find it was keeping time throughout the night.

    Next, I will have to implement the alarm clock function - which appears to be a challenging part due to the UX considerations.

    I've uploaded the (so far) source code to the project page for anyone who wants to read it.

View all 8 project logs

Enjoy this project?

Share

Discussions

Ken Yap wrote 11/19/2024 at 08:10 point

Interesting concept but seems a waste of the MCU computing power to do in hardware tasks like 7 segment decoding easily done in firmware. If you're short of GPIO pins port expander ICs would alleviate that problem. Or use an AVR model with more pins.

  Are you sure? yes | no

Zachary Murtishi wrote 11/19/2024 at 12:55 point

Yeah, the Atmega328 can definitely handle the seven-segment decoding with a lookup table - but I figured I would use the 74LS47 as it has open-collector outputs and can drive LEDs directly.

I tend to like the I2C I/O expanders and similar devices - if this was not for fun, I’d probably use some I2C constant-current drivers for the LEDs like I did with my mancala board. This is honestly just an exercise in seeing how much I could get done with an AVR paired with cheap logic ICs.

It does feel like a waste of the MCU’s computing power though -I’m really just using it as a RTC with PWM/digital outputs. 

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates