Hello, I’m a beginner with Arduino and struggling with a time concept. For my project I’m calculating time of dusk each day to turn on a light and turn it off some time after midnight next day. E.g turn on at 18.00 (variable) each day and turn off at 00:30 (fixed). I’ve worked through some sketches converting time to seconds and checking the midnight rollover, but I think I’d need to use interrupts for active alarm duration checks lasting several hours?
The DS3231 RTC module has two alarms. Some sketches set alarm duration across midnight use specific days from date whereas I want my alarm off time to be next day, irrespective of date. I will also be using DST and time zone sketches
Can I use a DS3231 library alarm function to set an alarm on pin high at my dusk time each day e.g 18:00 and take the same pin low after midnight at 00:30? I’ve been puzzling over this for a while. I can work out a sketch, but its the program structure I don’t understand at the moment.
The sketch can be in one of two states, either light on or light off. Hold that state in a boolean variable, perhaps lightOn = true; or `lightOn = false;
Have the program check the time periodically, say once per second.
If the light is on and the time is 00:30, turn off the light, invert the boolean variable and calculate the time to turn the light on
If the light is off and it is time to turn the light on then turn it on and invert the boolean variable
You may find many such problems can go away with some code, as @UKHeliBob suggests.
But when they just won't, you may get mileage out of the ease with which a full date and time can be converted to a number, and back.
You can use a Unix timestamp, known as epoch time. It's just the number of seconds since the beginning of time (midnight on 1 January 1970), before which point there is no need to talk about anything.
When you next strukkle with something like this, google
If I understand your question, the answer is that the DS3231 alarm pin goes low when the alarm time first matches the current time, and it goes back high when the alarm flag in the Status register is cleared by your code or by your library. So you as the programmer are responsible for bringing it back high. (Technically, it isn't really "high" because the alarm pin is open drain, meaning it can be either active low or off, but not active high. So you would need to make the Arduino pin INPUT_PULLUP.) But there is nothing in the DS3231 that will bring the alarm pin back high at a certain time.
So you would set up the DS3231 to generate the interrupt at dusk, and at that time your code would clear the alarm flag bit in the Status register, then set the alarm for the after-midnight time, then go back to sleep. Then when that interrupt occurs, you go through the same process to set the alarm for the next dusk interrupt. Of course you will need to know which interrupt you're servicing, but you can do that by simply reading the current alarm hour register, or the time hour register - both should be the same value.
Others here can suggest a good DS3231 library. I have given up on them, and just roll my own functions. I would just say that in my experience some libraries automatically clear the alarm flag when you set the next alarm time, and some don't.
You didn't ask, but I will just volunteer that if battery life is important, the DS3231 with a backup coin cell can also be used, with a mosfet, to switch power for the entire circuit, so no current will flow between interrupts.
I'm not clear what you intend to do with time zones and DST. Just remember that the DST switch dates vary depending on where you are. But if you are just switching on a light at dusk, maybe standard time would be good enough.
Thanks for replies. I’ve had some past dyslexia and struggle with syntax memory and programming, but I can use, modify, and test sketch examples. I’ll convert ideas in my head into a flow chart using function blocks.
I’m already moving away from library alarm functions. As a beginner I don’t know what an alarm function call is doing and what will happen during a midnight roll over, month ends, DST or leap year ends unless I use the full year /date/time.
Re time zones and DST. I’m calculating civil dusk times for each day each year. I’ve compared a script values with an online calculator prediction and results are within a minute over 3 or 4 years. My original concept was to use a GPS module, but realized I could use fixed location co-ordinates for my home with a RTC.
I have another idea: Compare current time against my calculated dusk time. When current time >= dusk time, turn on lamp. Keep polling and convert current time to seconds. Preset the off time in seconds as a fixed Int. variable. Subtract off time from current time and if negative add 8640 until zero, then switch off lamp. Poll time from RTC every 15 seconds, if mains fails and returns during lamp on time polling ensures it switches off? To keep hardware simple I won’t add a serial display or buttons to set up time. At first instance the PC can do it at compile time with correction if I’m working in UTC. It won’t matter If RTC clock accuracy shifts even a minute a year for this application. I’m o.k designing opto isolated mains triac interfaces.
Thanks for your input, it’s harder work for me to code new functions from scratch, but I’ll learn more, unless I can come up with a simple way of using library alarms.
Most of my Arduino learning is using search and TBH I’ve found AI search comes back with good results and sketch examples although program syntax used in sketches doesn’t always seem the same. When you get AI replies for time over midnight you get the same answer using day change to make time after midnight unique. The robot learns from Arduino Forum et Al and spits out the same replies with words in different order!
Indeed, but even if he thinks about it as turning off on the next day, he doesn't have to worry about which day. If it's currently 18:00, an alarm set for 00:30 will trigger on the next day - that's the first time a match can occur. So if he's doing this every day, then it doesn't matter which day it is. It's just the next 00:30 or the next 18:00.
I should have said in my previous reply that if the 00:30 turn-off alarm is fixed, then he could set up Alarm2 for that, and use Alarm1 for the variable turn-on alarm, and leave both of them enabled all the time. He would just have to clear the alarm flag each time either one of them triggers. Then they would alternate generating an interrupt. Reading the current hour would tell him which alarm interrupted, or he could actually read the alarm flags in the Status register before clearing them.
I'd suggest to the OP that he read the datasheet for the DS3231, or DS3231M if that's what he's using, to see how the registers work.
Unless my thinking is muddled, that’s the crux of my misunderstanding Alarm on times are the same daily. E.g on time is before midnight, then there’s a change to next day for the alarm off time of e.g 00:30. Whilst using midnight 23:59 is simple and doesn’t involve rollover, I’d like to choose off time next day later e.g up to 03:00 and repeat every day. The alarm on duration could be several hours and I don’t want any registers clearing if there’s power interruption during alarm on duration. As a newbie, I thought regularly polling I2C to check and compare time would be mcu wasteful when it’s being done for alarms in the RTC module? ? I’m going to spend some time with the DS3231 looking at alarm clock sketches and writing simple snippets to learn if alarms can be used before and after midnight every day. As I understand it, calling RTC internal routines during long delays or using its alarms for interrupts is more cpu efficient?
I’ll go quiet for a while now whilst I digest helpful replies and come back after doing some code testing. Thanks
My suggestion all along was not to use RTC alarms but to write your own code to compare the current time with the time that the next action (off or on) should occur.
As it happens it looks like you can set two independent alarms for 2 different dates
Here is an example using the Adafruit RTCLib library that I found with a quick Google search
// Example: Alarm 1 on the 10th, Alarm 2 on the 15th
// Assumes you are setting the Date (Day), Hour, Minute
rtc.setAlarm1(DateTime(0, 0, 10, 8, 0, 0), DS3231_A1_Date); // 10th, 08:00
rtc.setAlarm2(DateTime(0, 0, 15, 14, 30, 0), DS3231_A2_Date); // 15th, 14:30
However, this seems to be intended to be used with monthly alarms because you need to know the number of days in the month and handle month rollover
One advantage to using an RTC is that with its backup coin cell installed it can maintain the time across power glitches and failures. So when the Arduino powers up after a failure, it can simply read the current time, set the switch accordingly, clear the alarm flags, and go back to sleep.
Edit. Sorry, I misread. You said not to use RTC alarms. Somehow that became not to use an RTC. Anyway, I don't know if batteries are involved here, but if they are, it seems to me that using the RTC alarms is the way to go, so the Arduino can sleep except when something needs to be switched on or off. I just don't see any point in continuous polling.
You are making things complicated with the date change when it’s not a problem.
The DS3231 allows you to choose which events trigger the interrupt, including matching hours, minutes, or hours, minutes, and seconds, without considering the date (although you need to set a date when configuring the alarms).
You can set an alarm, for example, for 0:30:00 on 2045/02/14 and then set the alarm to go off at 0:30:00 on any day by configuring it to only compare the time without including the date.
As a result, it will go off at 0:30 tomorrow (it won't wait until the year 2045).
Look...
If you are using an UNO or equivalent AVR NANO here is a skeleton for using the alarms for the on and off times.
If things are not clear after looking at the data sheet and the library .h file ask questions. Not tested but should be close - similar code works.
#include <avr/interrupt.h>
#include <Wire.h>
// Pick a Real time clock library - this one has the functions I like
#include <RtcDS3231.h> // from "Rtc by Makuna" in library manager
RtcDS3231<TwoWire> Rtc(Wire); // Construct RTC object
// On and off times
uint8_t onHour = 18; // initial value
uint8_t onMin= 0; // initial value
uint8_t offHour = 0; // initial value - midnight
uint8_t offMin= 30; // initial value
// possibly some code - maybe more declarations
// volatile because it is shared with the interrupt routine
volatile boolean timerInterupt = false;
const uint8_t INTERRUPT_PIN = 8; // Pick a pin
/********************************************************************
*
* Interrupt service routine for interrupt from RTC
*
* ********************************************************************/
void RTC_interrupt() //
{
// Keep this as short as possible.
if (!digitalRead(INTERRUPT_PIN)) {
timerInterupt = true; // we are only interested in the high to low change
}
}
/********************************************************************
*
* set alarm 1 for daily on time
*
* ********************************************************************/
void SetOnTime(uint8_t hourPart,uint8_t minutePart){
// set next timer interrupt
// create an alarm struct
DS3231AlarmOne alarm1(
0, // day - always today
hourPart,
minutePart,
0, // seconds
DS3231AlarmOneControl_HoursMinutesSecondsMatch); // interrupt at (h,m,s)
Rtc.SetAlarmOne(alarm1);
}
/********************************************************************
*
* set alarm 2 for daily off time
*
* ********************************************************************/
void SetOffTime(uint8_t hourPart,uint8_t minutePart){
// set next timer interrupt
// create an alarm struct
DS3231AlarmTwo alarm2(
0, // day - always today
hourPart,
minutePart,
DS3231AlarmTwoControl_HoursMinutesMatch); // interrupt at (h,m)
Rtc.SetAlarmTwo(alarm2);
}
// possibly more code - maybe other functions
/********************************************************************
*
* Initial setup routine
*
*********************************************************************/
void setup()
{
// other set up code
// Set up real time clock/calendar
Rtc.Begin();
Rtc.SetIsRunning(true);
Rtc.Enable32kHzPin(false);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeAlarmBoth);
// set up interrupt for Real Time Clock
pinMode(INTERRUPT_PIN,INPUT_PULLUP); // Only one pin is used for the RTC interrupt
// It is a pin change interrupt which we will handle here
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN),RTC_interrupt, CHANGE);
SetOnTime(onHour,onMin);
SetOffTime(offHour,offMin);
// more setup code if needed
}
//********************************************************
// Processing loop
//********************************************************
void loop()
{
// some code
//
// Handle RTC interrupts
//
if (timerInterupt) {
// This clears the interrupt flag in status register of the clock chip
// The next timed interrupt will not be sent until this is cleared
// get alarm flags - note that this may generate a low to high pin change interrupt
DS3231AlarmFlag flag = Rtc.LatchAlarmsTriggeredFlags();
if (flag & DS3231AlarmFlag_Alarm1){ // Handle on time (alarm 1)
// on time processing
// possibly change on time
SetOnTime(onHour,onMin); // reset alarm
}
if (flag & DS3231AlarmFlag_Alarm2) { // Handle off time (alarm 2)
// off time processing
// possibly change off time
SetOffTime(offHour,offMin); // reset alarm
}
timerInterupt = false; // done processing RTC interrupt
}
// more code if needed
}