[Home] [Downloads] [Search] [Help/forum]

Gammon Forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Alarm clock from Atmega328 and 7-segment display
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Alarm clock from Atmega328 and 7-segment display

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (21,322 posts)  [Biography] bio   Forum Administrator
Date Thu 09 Jun 2011 02:20 AM (UTC)

Amended on Wed 18 Jun 2014 09:34 PM (UTC) by Nick Gammon

Message
Recent events in our household led me to develop a microprocessor-based "smart" alarm clock.

The design criteria were:

  • Remember the time after a power failure

    There is nothing more annoying than have your alarm not go off if the power goes off during the night for an hour. Thus the clock has a DS1307 clock chip, which has a button-battery for backup. The backup battery apparently keeps time for around 10 years.

  • Sound an alarm at a predetermined time, without having to be "armed" each day

    The program is written to ring the alarm each day at 6:15 am (this can easily be changed in the code). Once the alarm is cancelled by pressing the big red button it re-arms itself an hour later, ready for tomorrow.

  • Keep ringing for 15 minutes until you get out of bed to cancel it

    The program rings for 15 minutes (the time is configurable) to make sure you wake up. By physically locating the clock some distance from the bed you ensure that you have to get out of bed to shut it up.

  • Only sound on Monday to Friday (school/work days)

    The code can determine the day of the week from the clock chip, and thus only sound the alarm on weekdays. This saves the problem of turning the alarm off on the weekend, and forgetting to turn it back on on Sunday night.

  • Flash a LED to help locate the "cancel" button in the dark

    The LED next to the button flashes brightly to help you find it in your sleepy state.

  • Be able to cancel the alarm function during holidays

    A slider switch lets you disable the alarm during holidays.

  • Handle Daylight Saving Time easily

    Another slider switch adds an hour to the time as read from the clock chip. Thus you simply slide it across at the start of Daylight Saving Time, and back again at the end.

  • Dim the display at night

    Between the hours of 9 pm and 6 am the digits on the display are dimmed (by reducing the duty cycle of the pulse-width-modulation). This makes them unobtrusive when you are trying to sleep.






The finished clock looks like this:



A task not yet done is to assemble it into a nicer-looking box. The time read-out is a bit clearer than in the photo. Photographing multiplexed displays can be a bit of a problem.

The time is shown on a 4-digit 7-segment display. The LED has 12 pins (1 per digit, 7 for each segment, and one for the decimal point). The display is "multiplexed" which means that the code quickly draws each digit in sequence. Persistence of vision makes it look like all 4 digits are active at once.

On the top RH corner is a LM7805 voltage regulator, which takes power in the range 7V to 12V from a plug pack, and regulates it down to 5V for the processor.

Underneath that is the "alarm" LED and the cancel button. The cancel button is wired to one of the interrupt pins, so it can generate an interrupt when pressed for more reliable operation.

Underneath that is the piezo buzzer which sounds the alarm. This is done by pulsing it at the required frequency.

To the left is the DS1307 clock chip mounted on a breakout board, with the backup battery next to it.

For programming the chip you plug in a FTDI cable into the FTDI pins next to the processor.

There are a couple of decoupling capacitors, and a capacitor and resistor for the reset pulse used during programming.

For timing I used a 16 MHz resonator (cost around $0.80) but you could use a crystal and a couple of 22 pF capacitors instead. Or probably just run off the internal clock at 8 MHz, since the "real" timing is done on the clock chip, and the processor frequency is not critical.

The underside of the clock board shows a combination of soldering and wire-wrapping. It's a bit of a rats nest:






This is the circuit (however see reply further down for improved circuit):



This is the 7-segment display diagram from their spec:



If you are using a different LED, just connect the wires appropriately, along similar lines to the way I did it with this one. If you have a common-anode LED just change the constant COMMON_ANODE to be true rather than false in the sketch.

The pin-outs for this particular LED display seem to start at pin 1 on the left if you have the decimal points facing you (see photo).




Voltage regulator pin-outs:






This is the code as used in the Arduino IDE:


// Alarm clock board
// 
// Author:  Nick Gammon
// Date:    9th June 2011
// Version: 1.2

//  Version 1.1: Changed period to frequency so you can specify alarm sound in Hz
//  Version 1.2: Fixed bug re common anode displays.

/*

Permission is granted to use this code for any purpose.

The code is provided "as is" with no warranty as to whether you wake up or not, nor any other warranty.

---

Note: I have observed that digits look slightly brighter if they have less segments.
(eg. 1 compared to 8, or the decimal point on its own).

I think this is because the more segments, the more the current is "shared" between
them. You could conceivably compensate by counting the on segments and lighting them
for slightly longer if there are more of them, but I haven't done that.

*/

#include <Wire.h>
#include "RTClib.h"

// when alarm is to sound (24-hour clock)

const byte ALARM_HOUR = 6;
const byte ALARM_MINUTE = 15;

// cancel alarm after these many minutes
const long RING_FOR = 15; 
                
// what days to ring on (0 to 6)               
const boolean wantedDays [7] = { false, true, true, true, true, true, false };
//                                SUN    MON   TUE   WED   THU   FRI   SAT

// alarm frequency
#define FREQUENCY 600  // Hz
#define PERIOD 500000L / FREQUENCY  // (1 / frequency) * 1e6 / 2

// Make true, recompile and upload quickly to set the clock chip to the compile time.
// Then make false, recompile and upload again, or it will keep resetting the clock
// to the last compile time every time you turn it on.
const boolean ADJUST_TIME = false;

// the pins the anodes are connected to

/*
   
    --A--
  |       |
  F       B
  |       |
    --G--
  |       |
  E       C
  |       |
    --D--
            (DP)

*/

// make true if you are using common-anode - reverse sense of voltages
const byte COMMON_ANODE = false;

// bring digit low to activate cathode
#define DIG1 0
#define DIG2 1
#define DIG3 4
#define DIG4 6

// segments
#define SEGA A3
#define SEGB 12
#define SEGC A2
#define SEGD 7
#define SEGE A0
#define SEGF A1
#define SEGG 13
#define SEGDP 5  // decimal point

// buttons/switches
#define CANCEL_BUTTON 2  // press to cancel alarm
#define ALARM_ON 3  // want alarm to ring?
#define DST 11  // daylight savings time?

// LED to flash when alarm rings
#define ALARM_LED 8      // flash so you can find the button

// pins buzzer is connected to
#define BUZZER1 9
#define BUZZER2 10

// clock chip class
RTC_DS1307 RTC;

// patterns for the digits
const byte pat0 [7] = { 1, 1, 1, 1, 1, 1, 0 };  // 0
const byte pat1 [7] = { 0, 1, 1, 0, 0, 0, 0 };  // 1
const byte pat2 [7] = { 1, 1, 0, 1, 1, 0, 1 };  // 2
const byte pat3 [7] = { 1, 1, 1, 1, 0, 0, 1 };  // 3
const byte pat4 [7] = { 0, 1, 1, 0, 0, 1, 1 };  // 4
const byte pat5 [7] = { 1, 0, 1, 1, 0, 1, 1 };  // 5
const byte pat6 [7] = { 1, 0, 1, 1, 1, 1, 1 };  // 6
const byte pat7 [7] = { 1, 1, 1, 0, 0, 0, 0 };  // 7
const byte pat8 [7] = { 1, 1, 1, 1, 1, 1, 1 };  // 8
const byte pat9 [7] = { 1, 1, 1, 1, 0, 1, 1 };  // 9
const byte patspace [7] = {   0 };  // space
const byte pathyphen [7] = {  0, 0, 0, 0, 0, 0, 1 };  // -
const byte patA [7] = { 1, 1, 1, 0, 1, 1, 1 };  // A
const byte patF [7] = { 1, 0, 0, 0, 1, 1, 1 };  // F
const byte patL [7] = { 0, 0, 0, 1, 1, 1, 0 };  // L

// 0 to 9 are themselves in the patterns array
//  there are the other entries:
#define SHOW_SPACE 10
#define SHOW_HYPHEN 11
#define SHOW_A 12
#define SHOW_F 13
#define SHOW_L 14

// which pins which segments are on (including decimal place)
const byte pins [8] = { SEGA, SEGB, SEGC, SEGD, SEGE, SEGF, SEGG, SEGDP };
const byte * patterns [] = { pat0, pat1, pat2, pat3, pat4, pat5, pat6, pat7, pat8, pat9, 
                             patspace, pathyphen, patA, patF, patL };

// the pins each digit is connected to
const byte digits [4] = { DIG1, DIG2, DIG3, DIG4 };

// our variables
boolean tick, pm, rang, alarm, dim;
unsigned long ms;          // keep track of when we last drew the clock
unsigned long alarm_time;  // when the alarm sounded
volatile boolean button_down;  // true if cancel button pressed

// entered when "cancel" button is pressed
void button_isr ()
  {
  button_down = true;
  }  // end of button_isr

void setup ()
{

  // clock uses I2C
  Wire.begin();
  
  // activate clock
  RTC.begin();

  // set time in clock chip if not set before
  if (! RTC.isrunning() || ADJUST_TIME) 
    {
    // following line sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));
    }

  // set up pins - one for each digit
  for (byte i = 0; i < 4; i++)
    pinMode (digits [i], OUTPUT);

  // and one for each segment
  for (byte i = 0; i < 8; i++)
    pinMode (pins [i], OUTPUT);

  pinMode (BUZZER1, OUTPUT);   // piezo buzzer pins
  pinMode (BUZZER2, OUTPUT);
  
  // for flashing the LED
  pinMode (ALARM_LED, OUTPUT);
  
  // input mode, but high gives us a pull-up for the switch
  digitalWrite (CANCEL_BUTTON, HIGH);  // pull-up
  digitalWrite (DST, HIGH);            // pull-up
  digitalWrite (ALARM_ON, HIGH);      // pull-up

  // interrupt routine to quickly detect button presses
  attachInterrupt (0, button_isr, FALLING);
  
  beep (10);  // beep to show we started up

  // show "-AL-"
  ms = millis ();
  while (millis () - ms < 2000)
    {
    digit (1, SHOW_HYPHEN);
    digit (2, SHOW_A);  
    digit (3, SHOW_L);
    digit (4, SHOW_HYPHEN);
    }
  
  // show alarm set time for 5 seconds (24-hour time)
  ms = millis ();
  pm = ALARM_HOUR >= 12;
  while (millis () - ms < 5000)
    show_time (ALARM_HOUR, ALARM_MINUTE);  

  // show "OFF" if alarm off
  if (digitalRead (ALARM_ON) == LOW)
    {
    ms = millis ();
    while (millis () - ms < 2000)
      {
      digit (1, 0);
      digit (2, SHOW_F);  
      digit (3, SHOW_F);
      digit (4, SHOW_SPACE);
      }
    }
    
  ms = millis ();
  beep (20);  // second beep
    
  // clear any spurious button-press
  button_down = false;
     
}  // end of setup

void digit (const byte segment, const byte which)
{
  
  byte adjust = 0;
  
  // common anode uses opposite polarity
  if (COMMON_ANODE)
    adjust = 1;
    
  // get correct digit pin, zero-relative
  byte pin = digits [segment - 1];

  const byte * pat = patterns [which];
  
  for (byte i = 0; i < 7; i++)
    digitalWrite (pins [i], pat [i] ^ adjust);

  // decimal point flags
  switch (segment)
    {
    case 1: digitalWrite (SEGDP, (digitalRead (ALARM_ON) ^ adjust) & 1); break;
    case 2: digitalWrite (SEGDP, (tick ^ adjust) & 1);     break;
    case 3: digitalWrite (SEGDP, LOW ^ adjust);      break;
    case 4: digitalWrite (SEGDP, (pm ^ adjust) & 1);       break;
    } // end of switch
    

  // bring appropriate digit low to activate it

  digitalWrite (pin, LOW ^ adjust);

  // make dimmer at night
  if (dim)
    delayMicroseconds (50);
  else
    delay (2);  // milliseconds

  // back to high 

  digitalWrite (pin, (HIGH ^ adjust) & 1);
  for (byte i = 0; i < 7; i++)
    digitalWrite (pins [i], LOW ^ adjust);
  digitalWrite (SEGDP, LOW ^ adjust);
}  // end of digit

void show_time (const byte hour, const byte minute)
{
  digit (1, (hour / 10) ? (hour / 10) : SHOW_SPACE);  // leading space if zero
  digit (2, hour % 10);
  digit (3, minute / 10);
  digit (4, minute % 10);
}  // end of show_time

// Annoying beep for duration times
void beep (const int duration)
{

  for(int i = 0 ; i < duration ; i++)
  {
    digitalWrite (BUZZER1, HIGH);
    digitalWrite (BUZZER2, LOW);
    delayMicroseconds(PERIOD);

    digitalWrite (BUZZER1, LOW);
    digitalWrite (BUZZER2, HIGH);
    delayMicroseconds(PERIOD);
  }

  digitalWrite (BUZZER1, LOW);
  digitalWrite (BUZZER2, LOW);

}  // end of beep

void loop()
{
  // find the time  
  DateTime now = RTC.now();
  byte hour = now.hour ();
  byte minute = now.minute ();
  
  // daylight savings? one hour later
  if (digitalRead (DST))
    hour++;
  if (hour > 23)
    hour = 0;
  
  // make dimmer early in the morning (before 6 am) or late at night (after 9 pm)
  dim = hour < 6 || hour >= 21;

  // if not in alarm hour, reset alarm (ready for next day)
  if (hour != ALARM_HOUR)
    rang = false;
    
  // if RING_FOR minutes are up, or alarm switch turned off, cancel alarm
  if (alarm && ((millis () - alarm_time) > (RING_FOR * 60L * 1000L) || 
                 digitalRead (ALARM_ON) == LOW))
    {
    alarm = false; 
    digitalWrite (ALARM_LED, LOW);
    }
  
  // if we reach the alarm time, and we have't rang it yet, sound alarm
  // (only on Monday to Friday)
  if (hour == ALARM_HOUR &&       // correct hour
      minute == ALARM_MINUTE &&   // correct minute
      wantedDays [now.dayOfWeek ()] &&  // this day of week wanted
      digitalRead (ALARM_ON) &&   // they have alarm switched on
      !rang)                      // haven't already rung it today
      {
      alarm = true;
      alarm_time = millis ();
      }
  
  // check for button press  
  if (button_down)
    {
    // she cancelled it!
    if (alarm)
      {
      alarm = false;
      rang = true;
      digitalWrite (ALARM_LED, LOW);
      }
    else
      {
      // alarm wasn't on - just do alarm test
      if (digitalRead (ALARM_ON))
        {
        alarm = true;  // test alarm 
        alarm_time = millis ();
        }
      }
      
     // wait until let go of button
     while (digitalRead (CANCEL_BUTTON) == LOW)
      {}
      
     delay (100);  // debounce
     button_down = false;
    }  // end button pressed
    
  // work out AM/PM decimal point
  pm = hour >= 12;

  // make 12-hour time  
  if (hour > 12)
    hour -= 12;

  // don't dim if alarm ringing
  
  if (alarm)
    dim = false;
    
  show_time (hour, minute);

  unsigned long diff = millis () - ms;

  // "tick" the decimal place every 1/2 second
  if (diff > 500)
  {
    tick = !tick;
    ms = millis ();
    
    // beep away if alarm sounding
    if (alarm)
      {
      beep (100);
      digitalWrite (ALARM_LED, !digitalRead (ALARM_LED));
      }
      
  }

  delay (2);  // 2 ms delay for multiplexing
  
}  // end of loop





Setting the clock

If your clock chip has not been set correctly, install the battery first, then set ADJUST_TIME in the sketch to true, recompile and quickly upload it. That will set the time in the clock to the compile time. Then change ADJUST_TIME, recompile and re-upload. If you don't do that every time you power on the board the time will be reset back to the last compile time.

Testing

On power-up the clock should show "-AL-" for 2 seconds, followed by the currently-set alarm time (to confirm what you had it set to). If the alarm switch is off it will then show "OFF". After that it should show the current time, with one of the decimal points blinking every 1/2 second.

If the alarm is on the LH decimal point will light up. If the time is 12 midday or later the RH decimal point (PM) will light up. Between 9 pm and 6 am the display will automatically dim.

You can test the alarm, if the alarm switch is in the "on" position, by pressing the Cancel button. That forces an alarm test. This lets you check that the buzzer is working, and its volume. Press the Cancel button again to cancel it.

Uploading

Connect an FTDI cable to the board, with the black (ground) wire as shown, and the green (RTS) wire at the other end. The exact board type to choose in the Arduino IDE depends on what bootloader you have flashed onto your chip (if any). In my case I had the Optiboot, but some might come supplied with the Duemilanove loader, or none at all. If you don't have a bootloader you will need some sort of programmer, such as the USBtinyISP.

Example supplier:

FTDI cable: http://www.adafruit.com/products/70 ($US 20)
USBtinyISP: http://www.adafruit.com/products/46 ($US 22)

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (21,322 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Sat 22 Dec 2012 07:13 PM (UTC)

Amended on Sun 23 Dec 2012 12:40 AM (UTC) by Nick Gammon

Message
Digit brightness problem


As someone (Ima2ll) recently pointed out, there is a hardware bug in the design. The digits light up with different levels of brightness depending on how many segments are lit.

I found after I made it that the way I had the resistors is probably to blame. To save myself effort I had one resistor per section, not per LED segment. (so instead of D1, D2, D3, D4 they should be on A, B, C, D, E, F, G, DP). Because the way I have it the more segments that light up the more current the resistor carries and the duller it gets.

You can actually see that in my photo above, the 8 digit is duller than the 1 digit.

To fix that, remove resistors R3, R4, R5, R6 and instead put 270 ohm resistors in series with A, B, C, D, E, F, G, DP.

Amended schematic:


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (21,322 posts)  [Biography] bio   Forum Administrator
Date Reply #2 on Wed 18 Jun 2014 09:22 PM (UTC)
Message
There was a bug in the code above if you used a common anode display. This is now fixed and tested. The new version (above) is 1.2.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


28,092 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at FutureQuest]