Using Time and Dates

12.0 Introduction

Managing time is a fundamental element of interactive computing. This chapter covers built-in Arduino functions and introduces many additional techniques for handling time delays, time measurement, and real-world times and dates. You’ll learn about Arduino’s built-in function for introducing delays into your sketch, as well as more advanced techniques for intermittently performing operations. Other recipes in this chapter cover how to measure time as it passes, and even how to use an external real-time clock for tracking time and dates.

12.1 Using millis to Determine Duration

Problem

You want to know how much time has elapsed since an event happened; for example, how long a switch has been held down.

Solution

The following sketch uses the millis() function to print how long a button was pressed (see Recipe 5.2 for details on how to connect the switch).

/*
  millisDuration sketch
  returns the number of milliseconds that a button has been pressed
 */

const int switchPin = 2;  // the number of the input pin

unsigned long startTime; // the value returned from millis when the switch is pressed
unsigned long duration;  // variable to store the duration

void setup()
{
  pinMode(switchPin, INPUT_PULLUP);
  Serial.begin(9600);
}

void loop()
{
  if(digitalRead(switchPin) == LOW)
  {
    // here if the switch is pressed
    startTime = millis();
    while(digitalRead(switchPin) == LOW)
      ; // wait while the switch is still pressed
    unsigned long duration = millis() - startTime;
    Serial.println(duration);
  }
}

Discussion

The millis function returns the number of milliseconds since the current sketch started running.

Warning

The millis function will overflow (go back to zero) after approximately 50 days. See Recipes 12.4 and 12.5 for information about using the Time library for handling intervals from seconds to years.

By storing the start time for an event, you can determine the duration of the event by subtracting the start time from the current time, as shown here:

unsigned long duration = millis() - startTime;

See Also

The Arduino reference for millis: https://www.arduino.cc/reference/en/language/functions/time/millis/

See Recipes 12.4 and 12.5 for information about using the Time library to handle intervals from seconds to years.

12.2 Creating Pauses in Your Sketch

Problem

You want your sketch to pause for some period of time. This may be some number of milliseconds, or a time given in seconds, minutes, hours, or days.

Solution

The Arduino delay function is used in many sketches throughout this book. It pauses a sketch for the number of milliseconds specified as a parameter. (There are 1,000 milliseconds in one second.) The sketch that follows shows how you can use delay to get almost any interval:

/*
 * delay sketch
 */

const unsigned long oneSecond = 1000;  // a second is a thousand milliseconds
const unsigned long oneMinute = oneSecond * 60;
const unsigned long oneHour   = oneMinute * 60;
const unsigned long oneDay    = oneHour * 24;

void setup()
{
  Serial.begin(9600);
  while(!Serial); // Needed on Leonardo and ARM-based boards
}

void loop()
{
  Serial.println("delay for 1 millisecond");
  delay(1);
  Serial.println("delay for 1 second");
  delay(oneSecond);
  Serial.println("delay for 1 minute");
  delay(oneMinute);
  Serial.println("delay for 1 hour");
  delay(oneHour);
  Serial.println("delay for 1 day");
  delay(oneDay);
  Serial.println("Ready to start over");
}

Discussion

Because it is limited by the maximum value of an integer, the delay function has a range from one one-thousandth of a second to around 25 days when you use a long integer. If you used an unsigned long, it will reach just under 50 days; see Chapter 2 for more on variable types).

You can use delayMicroseconds to delay short periods. There are 1,000 microseconds in one millisecond, and 1 million microseconds in one second. delayMicroseconds will pause from one microsecond to around 16 milliseconds, but for delays longer than a few thousand microseconds you should use delay instead:

delayMicroseconds(10);  // delay for 10 microseconds
Note

delay and delayMicroseconds will delay for at least the amount of time given as the parameter, but they could delay a little longer if interrupts occur within the delay time.

The drawback of using the delay function is that your sketch can’t do anything else during the delay period. You can find an alternative approach in the BlinkWithoutDelay example (FileExamples02. DigitalBlinkWithoutDelay). This approach uses a variable, previousMillis, to store the time at which an action was last performed. The sketch then checks the value of millis() (which is based on an internal clock that ticks once every millisecond the sketch is running). When the difference between the current value of millis() and previousMillis reaches or exceeds a given interval, it performs an action, such as blinking an LED. Here is an abbreviated version of that sketch:

int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000;

void setup() 
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() 
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;
    if (ledState == LOW) 
    {
      ledState = HIGH;
    } 
    else 
    {
      ledState = LOW;
    }
    digitalWrite(LED_BUILTIN, ledState); 
  }
  // You can perform other actions here.
}

Here is a way to package this logic into a function named myDelay that will delay the code in loop but can perform some action during the delay period. You can customize the functionality for your application, but in this example, an LED is blinked on or off every 250 milliseconds:

/*
 * myDelay example sketch to blink an LED for a set amount of time
 */
const int ledPin =  LED_BUILTIN; // the number of the LED pin

int ledState = LOW;               // ledState used to set the LED
unsigned long previousMillis = 0; // will store last time LED was updated

void setup()
{
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop()
{
  if (myDelay(blink, 250))
  {
    Serial.println(millis() / 1000.0); // print elapsed time in seconds
  }
}

/*
 * Perform the specified function, return true if it was performed
 */
bool myDelay(void (*func)(void), long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    func(); // invoke the function
    return true;
  }
  return false;
}

void blink()
{
  // if the LED is off turn it on and vice versa:
  if (ledState == LOW)
  {
    ledState = HIGH;
  }
  else
  {
    ledState = LOW;
  }
  digitalWrite(ledPin, ledState);
}

The void (*func)(void) parameter in the definition of myDelay indicates that the func argument is a pointer to a void function that takes no ((void)) arguments. So, every time func() is invoked in myDelay, it’s really calling blink(). When the interval is reached, myDelay resets previousMillis, calls blink, and returns true.

Arduino style is to avoid the use of pointers in sketches and to reserve those for use in libraries, which avoids confusing beginners who use your sketch. So, another approach is to use a third-party library available from the Library Manager, such as Tasker (https://github.com/joysfera/arduino-tasker). This example blinks LEDs on two pins at different rates, the built-in LED and an LED connected to pin 10. The sketch avoids needing to store the state of each pin in a separate variable by using digitalRead to determine whether the LED is currently on or off:

/*
 * Tasker demo sketch
 */
#define TASKER_MAX_TASKS 2 // Set this to the number of tasks you need
#include <Tasker.h>

// Declare the Tasker object
Tasker tasker;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(10, OUTPUT);

  // Blink the built-in LED every second
  tasker.setInterval(blink, 1000, LED_BUILTIN);

  // Blink the LED on pin 10 twice per second
  tasker.setInterval(blink, 500, 10);
}

void loop()
{
  tasker.loop(); // Run any pending tasks
}

void blink(int pinNumber)
{
  bool ledState = !digitalRead(pinNumber); // Toggle the current pin state
  if (ledState)
  {
    digitalWrite(pinNumber, HIGH);
  }
  else
  {
    digitalWrite(pinNumber, LOW);
  }
}

See Also

The Arduino reference for delay: http://www.arduino.cc/en/Reference/Delay

12.3 More Precisely Measuring the Duration of a Pulse

Problem

You want to determine the duration of a pulse (when a digital signal transitions from low to high back to low again, or high to low back to high) with microsecond accuracy; for example, to measure the exact duration of HIGH or LOW pulses on a pin.

Solution

The pulseIn function returns the duration in microseconds for a changing signal on a digital pin. This sketch prints the time in microseconds of the HIGH and LOW pulses generated by analogWrite (see the section on “Analog Output” in Chapter 7). Because the analogWrite pulses are generated internally by Arduino, no external wiring is required:

/*
  PulseIn sketch
  displays duration of high and low pulses from analogWrite
 */

const int inputPin = 3;   // analog output pin to monitor
unsigned long val;  // this will hold the value from pulseIn

void setup()
{  
  Serial.begin(9600);
}

void loop()
{
  analogWrite(inputPin, 128);
  Serial.print("Writing 128 to pin ");
  Serial.print(inputPin);
  printPulseWidth(inputPin);
  
  analogWrite(inputPin, 254);
  Serial.print("Writing 254 to pin ");
  Serial.print(inputPin);
  printPulseWidth(inputPin);
  delay(3000);
}

void printPulseWidth(int pin)
{
   val = pulseIn(pin, HIGH);
   Serial.print(": High Pulse width = "); 
   Serial.print(val);
   val = pulseIn(pin, LOW);
   Serial.print(", Low Pulse width = "); 
   Serial.println(val); 
}

Discussion

The Serial monitor will display :

Writing 128 to pin 3: High Pulse width = 989, Low Pulse width = 997
Writing 254 to pin 3: High Pulse width = 1977, Low Pulse width = 8

pulseIn can measure how long a pulse is either HIGH or LOW:

pulseIn(pin, HIGH); // returns microseconds that pulse is HIGH
pulseIn(pin, LOW);  // returns microseconds that pulse is LOW

The pulseIn function waits for the pulse to start (or for a timeout if there is no pulse). By default, it will stop waiting after one second, but you can change that by specifying the time to wait in microseconds as a third parameter (note that 1,000 microseconds equals 1 millisecond):

pulseIn(pin, HIGH, 5000); // wait 5 milliseconds for the pulse to start
Note

The timeout value only matters if the pulse does not start within the given period. Once the start of a pulse is detected, the function will start timing and will not return until the pulse ends.

pulseIn can measure values between around 10 microseconds to three minutes in duration, but the value of long pulses may not be very accurate.

See Also

The Arduino reference for pulseIn: https://www.arduino.cc/reference/en/language/functions/advanced-io/pulsein/

Recipe 6.5 shows pulseIn used to measure the pulse width of an ultrasonic distance sensor.

Recipe 18.2 provides more information on using hardware interrupts.

12.4 Using Arduino as a Clock

Problem

You want to use the time of day (hours, minutes, and seconds) in a sketch, and you don’t want to connect external hardware.

Solution

This sketch uses the Time library to display the time of day. The Time library can be installed using the Arduino Library Manager (if you have trouble finding it, try searching the Arduino Library Manager for “timekeeping”).

/*
 * Time sketch
 */

#include <TimeLib.h>

void setup()
{
  Serial.begin(9600);
  setTime(12,0,0,1,1,2020); // set time to noon Jan 1 2020
}

void loop()
{
  digitalClockDisplay();
  delay(1000);
}

// Pad digits with a leading 0
String padDigits(int digit)
{
  String str = String("0") + digit; // Put a zero in front of the digit
  return str.substring(str.length() - 2); // Remove all but the last two characters
}

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);
  
  String datestr = String(year()) + "-" + padDigits(month()) + 
                   "-" + padDigits(day()); 
  Serial.println(datestr);
}

Discussion

The Time library enables you to keep track of the date and time. Many Arduino boards use a quartz crystal for timing, and this is accurate to a couple of seconds per day, but it does not have a battery to remember the time when power is switched off. Therefore, time will restart from 0 each time a sketch starts, so you need to set the time using the setTime function. The sketch sets the time to noon on January 1, 2020 each time it starts.

Note

The Time library uses a standard known as Unix time (also called POSIX time or Epoch time). The values represent the number of elapsed seconds since January 1, 1970. Experienced C programmers may recognize that this is the same as the time_t used in the ISO standard C library for storing time values.

Of course, it’s more useful to set the time to your current local time instead of a fixed value. The following sketch gets the numerical time value (the number of elapsed seconds since January 1, 1970) from the serial port to set the time. You can enter a value using the Serial Monitor (the current Unix time can be found on a number of websites including https://www.epochconverter.com):

/*
 * SetTimeSerial sketch
 * Set the time from the serial port. Simplified version of TimeSerial example
 * from the Time library.
 *
 * Set the time by sending the letter T followed by ten digits indicating
 * number of seconds since January 1, 1970, for example T1569888000 would
 * represent 12am on October 1, 2019.
 */

#include <TimeLib.h>

#define TIME_HEADER  'T'   // Header tag for serial time sync message

void setup()  {
  Serial.begin(9600);
  Serial.println("Waiting for time sync message");
}

void loop(){
  if(Serial.available())
  {
    processSyncMessage();
  }
  if(timeStatus() != timeNotSet)
  {
    // Display the time and date
    digitalClockDisplay();
  }
  delay(1000);
}

// Pad digits with a leading 0
String padDigits(int digit)
{
  String str = String("0") + digit; // Put a zero in front of the digit
  return str.substring(str.length() - 2); // Remove all but the last two characters
}

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);
  
  String datestr = String(year()) + "-" + padDigits(month()) + 
                   "-" + padDigits(day()); 
  Serial.println(datestr);
}

// Parse the time message
void processSyncMessage() {

  time_t pctime = 0;

  if(Serial.find(TIME_HEADER)) {
     pctime = Serial.parseInt();
     setTime(pctime); // Set clock to the time received on serial port
  }

}

The code to display the time and date is the same as before, but now the sketch waits to receive the time from the serial port. See the Discussion in Recipe 4.3 if you are not familiar with how to receive numeric data using the serial port.

A processing sketch named SyncArduinoClock is included with the Time library examples (it’s in the Time/Examples/Processing/SyncArduinoClock folder). This Processing sketch will send the current time from your computer to Arduino at the click of a mouse. Run SyncArduinoClock in Processing, ensuring that the serial port is the one connected to Arduino (Chapter 4 describes how to run a Processing sketch that talks to Arduino). You should see the message Waiting for time sync message sent by Arduino and displayed in the Processing text area (the black area for text messages at the bottom of the Processing IDE). Click the Processing application window (it’s a 200-pixel gray square) and you should see the text area display the time as printed by the Arduino sketch.

You can also set the clock from the Serial Monitor if you can get the current Unix time; http://www.epochconverter.com/ is one of many websites that provide the time in this format. Make sure the converter you use is configured for microseconds (a 10-digit value, at least until sometime in 2286); if it is configured for milliseconds the number will 1000 times too large. Copy the 10-digit number indicated as the current Unix time and paste this into the Serial Monitor Send window. Precede the number with the letter T and click Send. For example, if you send this:

T1282041639

Arduino should respond by displaying the time every second:

10:40:49 17 8 2019
10:40:50 17 8 2019
10:40:51 17 8 2019
10:40:52 17 8 2019
10:40:53 17 8 2019
10:40:54 17 8 2019
. . .

You can also set the time using buttons or other input devices such as tilt sensors, a joystick, or a rotary encoder.

The following sketch uses two buttons to move the clock “hands” forward or backward. Figure 12-1 shows the connections (see Recipe 5.2 if you need help using switches):

/*
   AdjustClockTime sketch
   buttons on pins 2 and 3 adjust the time
 */

#include <TimeLib.h>

const int  btnForward = 2;  // button to move time forward
const int  btnBack = 3;     // button to move time back

unsigned long  prevtime;   // when the clock was last displayed

void setup()
{
  pinMode(btnForward, INPUT_PULLUP);  // enable internal pull-up resistors
  pinMode(btnBack, INPUT_PULLUP);
  setTime(12,0,0,1,1,2020); // start with the time set to noon Jan 1 2020
  Serial.begin(9600);
}

void loop()
{
  prevtime = now();   // note the time
  while( prevtime == now() )    // stay in this loop till the second changes
  {
    // check if the set button pressed while waiting for second to roll over
    if(checkSetTime())
      prevtime = now(); // time changed so reset start time
  }
  digitalClockDisplay();
}
    
// functions checks to see if the time should be adjusted
// returns true if time was changed
bool checkSetTime()
{
  int step;   // the number of seconds to move (backwards if negative) 
  bool isTimeAdjusted = false;  // set to true if the time is adjusted
  step = 1;   // ready to step forwards

  while(digitalRead(btnForward)== LOW)
  {
     adjustTime(step);
     isTimeAdjusted = true; // to tell the user that the time has changed
     step = step + 1; // next step will be bigger
     digitalClockDisplay(); // update clock   
     delay(100); 
  }
  step = -1;   // negative numbers step backwards
  while(digitalRead(btnBack)== LOW)
  {
     adjustTime(step);
     isTimeAdjusted = true; // to tell the user that the time has changed
     step = step - 1; // next step will be a bigger negative number
     digitalClockDisplay(); // update clock   
     delay(100); 
  }
  return isTimeAdjusted;  // tell the user if the time was adjusted
}

// Pad digits with a leading 0
String padDigits(int digit)
{
  String str = String("0") + digit; // Put a zero in front of the digit
  return str.substring(str.length() - 2); // Remove all but the last two characters
}

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);
  
  String datestr = String(year()) + "-" + padDigits(month()) + 
                   "-" + padDigits(day()); 
  Serial.println(datestr);
}
Two buttons used to adjust the time

The sketch uses the same digitalClockDisplay and printDigits functions from Recipe 12.3, so copy those prior to running the sketch.

Here is a variation on this sketch that uses the position of a variable resistor to determine the direction and rate of adjustment when a switch is pressed:

#include <TimeLib.h>

const int  potPin = A0;    // pot to determine direction and speed
const int  buttonPin = 2;  // button enables time adjustment

unsigned long prevtime;   // when the clock was last displayed

void setup()
{
  digitalWrite(buttonPin, HIGH);  // enable internal pull-up resistors
  setTime(12,0,0,1,1,2020); // start with the time set to noon Jan 1 2020
  Serial.begin(9600);
}

void loop()
{
  prevtime = now();   // note the time
  while( prevtime == now() )    // stay in this loop till the second changes
  {
    // check if the set button pressed while waiting for second to roll over
    if(checkSetTime())
      prevtime = now(); //  time has changed, so reset start time
  }
  digitalClockDisplay();
}

// functions checks to see if the time should be adjusted
// returns true if time was changed
bool checkSetTime()
{
  int value;  // a value read from the pot
  int step;   // the number of seconds to move (backwards if negative)
  bool isTimeAdjusted = false;  // set to true if the time is adjusted

  while(digitalRead(buttonPin)== LOW)
  {
    // here while button is pressed
    value = analogRead(potPin);  // read the pot value
    step = map(value, 0,1023, 10, -10);  // map value to the desired range
    if( step != 0)
    {
      adjustTime(step);
      isTimeAdjusted = true; // to tell the user that the time has changed
      digitalClockDisplay(); // update clock
      delay(100);
    }
  }
  return isTimeAdjusted;
}

The preceding sketch uses the same digitalClockDisplay and printDigits functions from Recipe 12.3, so copy those prior to running the sketch. Figure 12-2 shows how the variable resistor and switch are connected. If you are using a 3.3V board that is not 5-volt tolerant, connect the positive side of the variable resistor to 3.3V instead of 5V.

All these examples print to the serial port, but you can print the output to LEDs or LCDs. The download for the Graphical LCD covered in Recipe 11.9 contains example sketches for displaying and setting time using an analog clock display drawn on the LCD.

The Time library includes convenience functions for converting to and from various time formats. For example, you can find out how much time has elapsed since the start of the day and how much time remains until the day’s end.

A variable resistor used to adjust the time

You can look in TimeLib.h in the libraries folder for the complete list. More details are available in Chapter 16:

dayOfWeek( now() );           // the day of the week (Sunday is day 1)
elapsedSecsToday( now() );    // returns the number of seconds since the start
                              // of today
nextMidnight( now() );        // how much time to the end of the day
elapsedSecsThisWeek( now() ); // how much time has elapsed since the start of
                              // the week

You can also print text strings for the days and months; here is a variation on the digital clock display code that prints the names of the day and month:

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);

  String datestr = String(dayStr(weekday())) + ", " + 
                   String(monthShortStr(month())) + " " + String(year());
  Serial.println(datestr);
}

See Also

Time library reference: https://github.com/PaulStoffregen/Time

Wikipedia article on Unix time: http://en.wikipedia.org/wiki/Unix_time

http://www.epochconverter.com/ and http://www.onlineconversion.com/unix_time.htm are two popular Unix time conversion tools.

12.5 Creating an Alarm to Periodically Call a Function

Problem

You want to perform some action on specific days and at specific times of the day.

Solution

TimeAlarms is a companion to the Time library discussed in Recipe 12.4. Install the TimeAlarms library using the Arduino Library Manager (and install the Time library also if not already installed). TimeAlarms makes it easy to create time and date alarms:

/*
 * TimeAlarmsExample sketch
 *
 * This example calls alarm functions at 8:30 am and at 5:45 pm (17:45)
 * and simulates turning lights on at night and off in the morning
 *
 * A timer is called every 15 seconds
 * Another timer is called once only after 10 seconds
 *
 * At startup the time is set to Jan 1 2020  8:29 am
 */

#include <TimeLib.h>
#include <TimeAlarms.h>

void setup()
{
  Serial.begin(9600);
  while(!Serial);
  Serial.println("TimeAlarms Example");
  Serial.println("Alarms are triggered daily at 8:30 am and 17:45 pm");
  Serial.println("One timer is triggered every 15 seconds");
  Serial.println("Another timer is set to trigger only once after 10 seconds");
  Serial.println();

  setTime(8,29,40,1,1,2020); // set time to 8:29:40am Jan 1 2020

  Alarm.alarmRepeat(8,30,0, MorningAlarm);  // 8:30am every day
  Alarm.alarmRepeat(17,45,0,EveningAlarm);  // 5:45pm every day

  Alarm.timerRepeat(15, RepeatTask);            // timer for every 15 seconds
  Alarm.timerOnce(10, OnceOnlyTask);            // called once after 10 seconds
}

void MorningAlarm()
{
  Serial.println("Alarm: - turn lights off");
}

void EveningAlarm()
{
  Serial.println("Alarm: - turn lights on");
}

void RepeatTask()
{
  Serial.println("15 second timer");
}

void OnceOnlyTask()
{
  Serial.println("This timer only triggers once");
}

void loop()
{
  digitalClockDisplay();
  Alarm.delay(1000); // wait one second between clock display
}

// Pad digits with a leading 0
String padDigits(int digit)
{
  String str = String("0") + digit; // Put a zero in front of the digit
  return str.substring(str.length() - 2); // Remove all but the last two characters
}

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);
  
  String datestr = String(year()) + "-" + padDigits(month()) + 
                   "-" + padDigits(day()); 
  Serial.println(datestr);
}

Discussion

You can schedule tasks to trigger at a particular time of day (these are called alarms) or schedule tasks to occur after an interval of time has elapsed (called timers). Each of these tasks can be created to continuously repeat or to occur only once.

To specify an alarm to trigger a task repeatedly at a particular time of day use:

  Alarm.alarmRepeat(8,30,0, MorningAlarm);

This calls the function MorningAlarm at 8:30 a.m. every day.

If you want the alarm to trigger only once, you can use the alarmOnce method:

  Alarm.alarmOnce(8,30,0, MorningAlarm);

This calls the function MorningAlarm a single time only (the next time it is 8:30 a.m.) and will not trigger again.

Timers trigger tasks that occur after a specified interval of time has passed rather than at a specific time of day. The timer interval can be specified in any number of seconds, or in hour, minutes, and seconds:

  Alarm.timerRepeat(15, Repeats);            // timer task every 15 seconds

This calls the Repeats function in your sketch every 15 seconds.

If you want a timer to trigger once only, use the timerOnce method:

  Alarm.timerOnce(10, OnceOnly);             // called once after 10 seconds

This calls the onceOnly function in a sketch 10 seconds after the timer is created.

Note

Your code needs to call Alarm.delay regularly because this function checks the state of all the scheduled events. Failing to regularly call Alarm.delay will result in the alarms not being triggered. You can call Alarm.delay(0) if you need to service the scheduler without a delay. Always use Alarm.delay instead of delay when using TimeAlarms in a sketch.

The TimeAlarms library requires the Time library to be installed—see Recipe 12.4. No internal or external hardware is required to use the TimeAlarms library. The scheduler does not use interrupts, so the task-handling function is the same as any other functions you create in your sketch (code in an interrupt handler has restrictions that are discussed in Chapter 18, but these do not apply to TimeAlarms functions).

Timer intervals can range from one second to several years. (If you need timer intervals shorter than one second, the Tasker library may be more suitable; see https://github.com/joysfera/arduino-tasker.)

Tasks are scheduled for specific times designated by the system clock in the Time library (see Recipe 12.4 for more details). If you change the system time (e.g., by calling setTime), the trigger times are not adjusted. For example, if you use setTime to move one hour ahead, all alarms and timers will occur one hour sooner. In other words, if it’s 1:00 and a task is set to trigger in two hours (at 3:00), and then you change the current time to 2:00, the task will trigger in one hour. If the system time is set backward—for example, to 12:00—the task will trigger in three hours (i.e., when the system time indicates 3:00). If the time is reset to earlier than the time at which a task was scheduled, the task will be triggered immediately (actually, on the next call to Alarm.delay).

This is the expected behavior for alarms—tasks are scheduled for a specific time of day and will trigger at that time—but the effect on timers may be less clear. If a timer is scheduled to trigger in five minutes’ time and then the clock is set back by one hour, that timer will not trigger until one hour and five minutes have elapsed (even if it is a repeating timer—a repeat does not get rescheduled until after it triggers).

Up to six alarms and timers can be scheduled to run at the same time. You can modify the library to enable more tasks to be scheduled; Recipe 16.3 shows you how to do this.

onceOnly alarms and timers are freed when they are triggered, and you can reschedule these as often as you want so long as there are no more than six pending at one time. The following code gives one example of how a timerOnce task can be rescheduled:

Alarm.timerOnce(random(10), randomTimer);  // trigger after random
                                           // number of seconds

void randomTimer(){
  int period = random(2,10);             // get a new random period
  Alarm.timerOnce(period, randomTimer);  // trigger for another random period
}

12.6 Using a Real-Time Clock

Problem

You want to use the time of day provided by a real-time clock (RTC) such as the DS1307. External boards usually have battery backup, so the time will be correct even when Arduino is reset or turned off.

Solution

The simplest way to use an RTC is with a companion library to the Time library, named DS1307RTC.h. Install the DS1307RTC library using the Arduino Library Manager (and install the Time library also if not already installed). This recipe is for the widely used DS1307 and DS1337 RTC chips:

/*
 * TimeRTC sketch
 * example code illustrating Time library with real-time clock.
 *
 */

#include <TimeLib.h>
#include <Wire.h>
#include <DS1307RTC.h>  // a basic DS1307 library that returns time as a time_t

void setup()  {
  Serial.begin(9600);
  while(!Serial); // For Leonardo and 32-bit boards
  
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  if(timeStatus()!= timeSet)
     Serial.println("Unable to sync with the RTC");
  else
     Serial.println("RTC has set the system time");
}

void loop()
{
   digitalClockDisplay();
   delay(1000);
}

// Pad digits with a leading 0
String padDigits(int digit)
{
  String str = String("0") + digit; // Put a zero in front of the digit
  return str.substring(str.length() - 2); // Remove all but the last two characters
}

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);
  
  String datestr = String(year()) + "-" + padDigits(month()) + 
                   "-" + padDigits(day()); 
  Serial.println(datestr);
}

Most RTC boards for Arduino use the I2C protocol for communicating (see Chapter 13 for more on I2C). Connect the line marked “SCL” (or “Clock”) to Arduino analog pin 5 and “SDA” (or “Data”) to analog pin 4, as shown in Figure 12-3. (Analog pins 4 and 5 are used for I2C; see Chapter 13). Take care to ensure that you connect the +5V power line and Gnd pins correctly.

Connecting a real-time clock

Discussion

The code is similar to other recipes using the Time library, but it gets its value from the RTC rather than from the serial port or hardcoded value. The only additional line needed is this:

  setSyncProvider(RTC.get);   // the function to get the time from the RTC

The setSyncProvider function tells the Time library how it should get information for setting (and updating) the time. RTC.get is a method within the RTC library that returns the current time in the format used by the Time library (Unix time).

Each time Arduino starts, the setup function will call RTC.get to set the time from the RTC hardware.

Before you can get the correct time from the module, you need to set its time. Here is a sketch that enables you to set the time on the RTC hardware—you only need to do this when you first attach the battery to the RTC, when replacing the battery, or if the time needs to be changed:

/*
 * Set RTC time sketch
 * Set the RTC from the serial port. Simplified version of TimeSerial example
 * from the Time library.
 *
 * Set the time by sending the letter T followed by ten digits indicating
 * number of seconds since January 1, 1970, for example T1569888000 would
 * represent 12am on October 1, 2019.
 */

#include <TimeLib.h>
#include <Wire.h>
#include <DS1307RTC.h>  // a basic DS1307 library that returns time as a time_t

void setup()  {
  Serial.begin(9600);
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  if(timeStatus()!= timeSet)
     Serial.println("Unable to sync with the RTC");
  else
     Serial.println("RTC has set the system time");
}

void loop()
{
  if(Serial.available())
  {
     processSyncMessage();
  }
   digitalClockDisplay();
   delay(1000);
}

// Pad digits with a leading 0
String padDigits(int digit)
{
  String str = String("0") + digit; // Put a zero in front of the digit
  return str.substring(str.length() - 2); // Remove all but the last two characters
}

void digitalClockDisplay(){
  String timestr = String(hour()) + ":" + padDigits(minute()) +
                   ":" + padDigits(second());
  Serial.println(timestr);
  
  String datestr = String(year()) + "-" + padDigits(month()) + 
                   "-" + padDigits(day()); 
  Serial.println(datestr);
}

#define TIME_HEADER  'T'   // Header tag for serial time sync message

// Parse the time message
void processSyncMessage() {

  time_t pctime = 0;

  if(Serial.find(TIME_HEADER)) {
     pctime = Serial.parseInt();
     setTime(pctime); // Set clock to the time received on serial port
     RTC.set(pctime); // Set the RTC too

  }

}

This sketch is almost the same as the TimeSerial sketch in Recipe 12.4 for setting the time from the serial port, but here the RTC.set function is also called when a time message is received from the computer to set the RTC:

setTime(pctime); // Set clock to the time received on serial port
RTC.set(pctime); // Set the RTC too

The RTC chip uses I2C to communicate with Arduino. I2C is explained in Chapter 13.

With the Adafruit_RTCLib library, you can set the RTC time using the compilation time of your sketch with rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));. See https://learn.adafruit.com/ds1307-real-time-clock-breakout-board-kit for more information.

Some of the more recent Arduino boards have RTC capability built in (ZeroMKRZero or MKR1000), and just need a battery backup to be connected to maintain the time when the main board is not powered. See https://www.arduino.cc/en/Reference/RTC for more information.

See Also

The SparkFun Real Time Clock Module (BOB-00099).

The Adafruit DS1307 Real Time Clock breakout board (product ID 3296).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.8.82