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.
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 of 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
);
}
}
The millis
function returns the number of milliseconds since the current sketch started running.
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
;
The Arduino reference for millis
See Recipes 12.4 and 12.5 for information about using the Time library to handle intervals from seconds to years.
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 ms 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 1,000 ms
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"
);
}
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 1 ms, and 1 million ms in 1 second. delayMicroseconds
will pause from one microsecond to around 16 ms, but for delays longer than a few thousand microseconds you should use delay
instead:
delayMicroseconds
(
10
);
// delay for 10 microseconds
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 (File→Examples→02. Digital→BlinkWithoutDelay). 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 ms:
/*
* 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. 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
);
}
}
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
.
(
"Writing 128 to pin "
);
Serial
.
(
inputPin
);
printPulseWidth
(
inputPin
);
analogWrite
(
inputPin
,
254
);
Serial
.
(
"Writing 254 to pin "
);
Serial
.
(
inputPin
);
printPulseWidth
(
inputPin
);
delay
(
3000
);
}
void
printPulseWidth
(
int
pin
)
{
val
=
pulseIn
(
pin
,
HIGH
);
Serial
.
(
": High Pulse width = "
);
Serial
.
(
val
);
val
=
pulseIn
(
pin
,
LOW
);
Serial
.
(
", Low Pulse width = "
);
Serial
.
println
(
val
);
}
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 ms for the pulse to start
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.
The Arduino reference for 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.
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
);
}
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.
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 Epoch Converter):
/*
* 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 10 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; Epoch Converter 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 be 1,000 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 check to see if the time should be adjusted
// return true if time was changed
bool
checkSetTime
()
{
int
step
;
// the number of seconds to move (backward if negative)
bool
isTimeAdjusted
=
false
;
// set to true if the time is adjusted
step
=
1
;
// ready to step forward
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 backward
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
);
}
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 check to see if the time should be adjusted
// return true if time was changed
bool
checkSetTime
()
{
int
value
;
// a value read from the pot
int
step
;
// the number of seconds to move (backward 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.
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
);
}
This Wikipedia article on Unix time
Epoch Converter and OnlineConversion.com are two popular Unix time-conversion tools.
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
);
}
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 hours, 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.
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.
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
}
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.
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 10 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 thisi Adafruit page for more information.
Some of the more recent Arduino boards have RTC capability built in, including Zero, MKRZero, and MKR1000, and just need a battery backup to be connected to maintain the time when the main board is not powered.