Short story:
I can't make a clone of Arduino Pro mini to enter in sleep mode after setting an alarm via an RTC DS3231 module. I tested the same code on Nano or UNO boards and it works well.
I tested a second Pro mini board and I got the same problem. So I suspect a problem with those board but I can't see why. I will appreciate some help on this problem.
Looong story: Context:
I'm building a chicken guard system that opens/closes the door each day based on an RTC DS3231 estimation of the sunset/sunrise. The project will be power by a battery a, so I'm looking for a very low power system.
So I decided to use a clone of Arduino Pro mini 5V-16MHz.
As the system will spent most of his time waiting to open/close the door (which happen two times a day), I'm trying to make it deep sleep to save battery power.
So my first try is to make the pro mini sleep for several hours and wake up thanks to the RTC DS3231 alarm when time to move the door is raised.
Problem:
Based on documentation and forum information, I wrote this simple testing code that must wait 5 seconds, then set the RTC alarm to fire 10 seconds later and enter in sleep mode. The problem is the Arduino never enter in sleep mode! Serial output show it never stop and wait the 10 seconds.
I measure the SQW pin tension and always got 4.9V. So it is always in the HIGH state and never goes to the LOW state. This could eventually explain why the Arduino will never wake up. But the problem is it never enter in sleep mode.
I tested the same code with an Arduino Nano and an Arduino UNO board and for both of them, it works as it does: the Arduino enter in sleep mode and 10 seconds later it wakes up. SQW tension change from HIGH to LOW when alarm is fired.
I also tested an other Arduino Pro mini board to see if the problem comes from the board, but I got the same problem: it never enter in sleep mode.
I also try to use either pin D2 or D3 for interrupts, but I got the same problem.
I checked the Dupon wire are fine (but I used the same with the others tested boards).
I also checked that I sold well everything on the Pro mini: all seems fine. And when I tested the other pro mini I got the same problem.
Here is the code :
/*
Connections
-----------
SDA -> Arduino Analog (SDA pin)
SCL -> Arduino Analog (SCL pin)
VCC -> Arduino 5V
GND -> Arduino GND
SQW -> Arduino D2 (Needs to be an interrupt capable pin)
*/
#include "RTClib.h"
#include <avr/sleep.h>
const int alarmPin = 2;
RTC_DS3231 rtc;
void setup() {
Serial.begin(9600);
pinMode(alarmPin, INPUT_PULLUP);
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
// Disable and clear both alarms
rtc.disableAlarm(1);
rtc.disableAlarm(2);
rtc.clearAlarm(1);
rtc.clearAlarm(2);
rtc.disable32K();
rtc.writeSqwPinMode(DS3231_OFF); // Place SQW pin into alarm interrupt mode
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Serial.println("Starting");
}
void loop() {
delay(5000);
// Get current time and set alarm to a time to wake
DateTime now = rtc.now();
rtc.setAlarm1(now + TimeSpan(0, 0, 0, 10), DS3231_A1_Second);
Serial.print(F("## Now date: "));
printDateTime(now);
Serial.print(F(">> Alarm1: "));
printDateTime(rtc.getAlarm1());
enterSleep();
DateTime wake_now = rtc.now();
Serial.print(F("$$ Wake date: "));
printDateTime(wake_now);
}
void enterSleep() {
sleep_enable();
noInterrupts();
attachInterrupt(digitalPinToInterrupt(alarmPin), alarm_ISR, LOW);
Serial.println("Going to sleep!");
Serial.flush();
interrupts();
sleep_cpu();
/* The program will continue from here when it wakes */
rtc.disableAlarm(1);
rtc.clearAlarm(1);
Serial.println("I'm back!");
}
void printDateTime(const DateTime date_time) {
char char_date[12] = "DD hh:mm:ss";
date_time.toString(char_date);
Serial.println(char_date);
}
void alarm_ISR() {
sleep_disable();
detachInterrupt(digitalPinToInterrupt(alarmPin));
}
Here is a picture of the Pro mini with the RTC module :
You mayfFollow these steps for troubleshooting/debugging: 1. Check that Pro Mini is working by blinking an external LED. 2. Check that the normal clock of the DS3231 works by showing the time of the day correctly. 3. Check that the Pro Mini goes to slepp for 20 sec (for example) and wakes up when an external interrupt signal is asserted on INT0-pin using a button. 4. Now, play with alarm system of DS3231.
It would be best not to use print() and flush() while interrupts are disabled. If it were not for the code working on an UNO and Nano, I would speculate that there might be a pending interrupt from the serial hardware that was triggering immediately after interrupts were enabled and the processor was put into sleep mode.
@GolamMostafa: Thanks for the clues. I tested each step as you asked and 1., 2. and 3. works like a charm. Only using the DS3231 alarm doesn't work (I use the same code as given in my first post).
@jim-p: I'm quiet surprised because there is nothing written on my processor
Even with macro photo or a magnifying glass I can't see anything...
The only thing I cant say is the specifications of the documentation I received with the board mention ATmega328 without the 'P' :
@david_2018: You are right that was the serial flush() and print() statements that was the cause of the problem! I move them before the interrupts are disabled and the Pro Mini works as expected: it sleep and wake when alarm is fired.
The final code I used is the following:
/*
Connections
-----------
SDA -> Arduino Analog (SDA pin)
SCL -> Arduino Analog (SCL pin)
VCC -> Arduino 5V
GND -> Arduino GND
SQW -> Arduino D2 (Needs to be an interrupt capable pin)
*/
#include "RTClib.h"
#include <avr/sleep.h>
const int alarmPin = 2;
RTC_DS3231 rtc;
void setup() {
Serial.begin(9600);
pinMode(alarmPin, INPUT_PULLUP);
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
// Disable and clear both alarms
rtc.disableAlarm(1);
rtc.disableAlarm(2);
rtc.clearAlarm(1);
rtc.clearAlarm(2);
rtc.disable32K();
rtc.writeSqwPinMode(DS3231_OFF); // Place SQW pin into alarm interrupt mode
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Serial.println("Starting");
}
void loop() {
delay(5000);
// Get current time and set alarm to a time to wake
DateTime now = rtc.now();
rtc.setAlarm1(now + TimeSpan(0, 0, 0, 10), DS3231_A1_Second);
Serial.print(F("## Now date: "));
printDateTime(now);
Serial.print(F(">> Alarm1: "));
printDateTime(rtc.getAlarm1());
enterSleep();
DateTime wake_now = rtc.now();
Serial.print(F("$$ Wake date: "));
printDateTime(wake_now);
}
void enterSleep() {
Serial.println("Going to sleep!");
Serial.flush();
sleep_enable();
noInterrupts();
attachInterrupt(digitalPinToInterrupt(alarmPin), alarm_ISR, LOW);
interrupts();
sleep_cpu();
/* The program will continue from here when it wakes */
rtc.disableAlarm(1);
rtc.clearAlarm(1);
Serial.println("I'm back!");
}
void printDateTime(const DateTime date_time) {
char char_date[12] = "DD hh:mm:ss";
date_time.toString(char_date);
Serial.println(char_date);
}
void alarm_ISR() {
sleep_disable();
detachInterrupt(digitalPinToInterrupt(alarmPin));
}
Thanks everybody for your help, I spend hours to find what was wrong.
@abje@jim-p
After disabling global interrupts, the user executes Serial.print() and Serial.flush() , both of which could potentially be sources of pending interrupt activity. After re-enabling global interrupts, it is reasonable to assume that the MCU services any pending interrupts and then returns to the sleep_cpu() instruction. Consequently, the MCU should enter the sleep state.
The OP's complaint is that the current sketch does not put the Pro Mini MCU to sleep, although it works correctly on UNO and NANO boards, which use the same MCU family. Therefore, it is not theoretically clear to me why moving the two Serial -related statements before the noInterrupts() call would solve the problem, although @abje reports that it does.
@jim-p : Nano and UNO I'm sure they are ATmega328P : it's written on their processor.
But for the Mini there is nothing. I managed to take a macro picture and you can see it in high resolution:
By the way the two mini board tested have the same behavior : doesn't enter in sleep mode with the flush() and print() statement placed after the noInterrupts() one. If they are lace before both of them works.
For Nano it works wherever is placed those serial statements. I can't test with the UNO right now as I used the board to upload sketch on the Mini (my FDTI connection is unstable otherwise).
I can do some test if you have some in mind, but I'm not an advanced Arduino user and I have really have no idea of what this is the cause of the problem. I didn't expect a software problem when I opened this topic.
I don't think it is. The fact that the code works differently on supposedly the same processor would suggest it actually is not a 328P but something else, especially since they scrubbed off the part marking.
Don't bother with more test but I would be concerned with other unforeseen code issues
I have found references that state that the instruction following sei() is guaranteed to be executed before any pending interrupts:
From the sleep.h file:
73 As the \c sleep_mode() macro might cause race conditions in some
74 situations, the individual steps of manipulating the sleep enable
75 (SE) bit, and actually issuing the \c SLEEP instruction, are provided
76 in the macros \c sleep_enable(), \c sleep_disable(), and
77 \c sleep_cpu(). This also allows for test-and-sleep scenarios that
78 take care of not missing the interrupt that will awake the device
79 from sleep.
80
81 Example:
82 \code
83 #include <avr/interrupt.h>
84 #include <avr/sleep.h>
85
86 ...
87 set_sleep_mode(<mode>);
88 cli();
89 if (some_condition)
90 {
91 sleep_enable();
92 sei();
93 sleep_cpu();
94 sleep_disable();
95 }
96 sei();
97 \endcode
98
99 This sequence ensures an atomic test of \c some_condition with
100 interrupts being disabled. If the condition is met, sleep mode
101 will be prepared, and the \c SLEEP instruction will be scheduled
102 immediately after an \c SEI instruction. As the intruction right
103 after the \c SEI is guaranteed to be executed before an interrupt
104 could trigger, it is sure the device will really be put to sleep.
Also from the atmega328 datasheet:
As to why the code works on an UNO/Nano, that is the confusing part, because it seemingly should not.
@abje are you using the same board package to compile for all of the boards? That might make a difference.
This expected behavior occurs because, during the execution of the sei() instruction, the CPU may detect active interrupt flags but global interrupts are still disabled until the instruction completes.
As a result, the CPU cannot immediately branch to the corresponding ISR. Instead, it completes the sei() instruction and proceeds to execute the next instruction.
Only after global interrupts are enabled and the current instruction finishes does the CPU respond to any pending interrupt requests.
In the OP’s sketch, the CPU enters sleep immediately after the sei() instruction. As a result, it does not get an opportunity to service any pending interrupts.
You can simply your sketch by keeping the attachInterrupt() in the setup(). This is to avoid attaching/deattaching interrupt every time the interrupt occurs.
You clear the alarm flag in the setup() function. After the interrupt, you clear it in the loop() function by polling a flag bit that has been set in ISR() following interrupt.
The very first lesson in Arduion porgramming is this that the code that will be executed only once should be underr setup() function - I would like to know from @jim-p why has OP violated this approach in his sketch of post #1? Here is my doubt if the code are generated by AI.
Looking at the atmega328 datasheet, my original post is wrong anyway. The interrupt from the serial hardware will not wake the processor from sleep mode, so print() and flush() should have no effect, even if there is a dangling interrupt waiting to be processed.