Adjusting time on DS3231 after running.

Hello all,

I’m trying to create an automated device that runs based on time of day … figured a good place to start would be in making a basic two-line LCD clock.

One really important thing I want to do is have the ability to set the clock after the Arduino is up and running. Eventually I want to set time through a menu, but for now I just have an interrupt button that goes to a “set time” screen with hard-coded values (eventually it’ll pull rtc.nowHour/Minute values but for now it’s rando values).

Where I run into a real headache is at the end of my interrupt. You’ll see at the end where I’m trying to write time back to the RTC and that’s where my program just stops responding. If I comment out rtc.adjust then I can get out of the interrupt and back to the main loop. I’ve tried a half-dozen or so libraries and similarly get stuck in the same place. I figure it has something to do with stopping and restarting the RTC, but am at a loss on how to actually do that.

Any feedback would be most appreciated.

I’ve been trying to solve this problem for more than week with a lot of Googling and searching here (if anyone here can link to the solution I’ll be eternally grateful and eternally shamed) … I find it hard to believe no one else has done what I’m looking to here.

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

RTC_DS3231 rtc;

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

const byte interruptMenu = 2;
const byte okay = 3;

const byte draw_hour = 12;
const byte draw_minute = 0;

int lcd_key     = 0;
int adc_key_in  = 0;
#define btnRIGHT  0  //<50
#define btnUP     1  //<195
#define btnDOWN   2  //<380
#define btnLEFT   3  //<555
#define btnSELECT 4  //<650
#define btnNONE   5  

void setup() {
lcd.begin(16,2);
rtc.begin();
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
rtc.adjust(DateTime(1985, 2, 2, 14, 20, 0));

 pinMode(interruptMenu, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(interruptMenu), MenuInterrupt, LOW);
 pinMode(okay, INPUT_PULLUP);
}

void loop() {
DateTime now = rtc.now();

lcd.setCursor(2,0);
lcd.print(":");

if (now.hour()>11) {
  lcd.setCursor(5,0);
  lcd.print("pm");
}
else {
  lcd.setCursor(5,0);
  lcd.print("am");
}

if (now.hour()<13) {
  if (now.hour()<10) {
    lcd.setCursor(0,0);
    lcd.print(" ");
    lcd.setCursor(1,0);
    lcd.print(now.hour());
  }
  else {
    lcd.setCursor(0,0);
    lcd.print(now.hour());
  }
}
else {
  if (now.hour()<22) {
    lcd.setCursor(0,0);
    lcd.print(" ");
    lcd.setCursor(1,0);
    lcd.print(now.hour()-12);
  }
  else {
    lcd.setCursor(0,0);
    lcd.print(now.hour()-12);
  }
}
if (now.minute()<10) {
  lcd.setCursor(3,0);
  lcd.print("0");
  lcd.setCursor(4,0);
  lcd.print(now.minute());
}
else {
  lcd.setCursor(3,0);
  lcd.print(now.minute());
}

}

void MenuInterrupt() {

while(digitalRead(okay) == HIGH) {
  
  lcd.setCursor(0,0);
  lcd.print("Set Time");
  lcd.setCursor(0,1);
  
  lcd.setCursor(2,1);
  lcd.print(":");

 if (draw_hour>11) {
  lcd.setCursor(5,1);
  lcd.print("pm");
 }
 else {
  lcd.setCursor(5,1);
  lcd.print("am");
 }

 if (draw_hour<13) {
   if (draw_hour<10) {
     lcd.setCursor(0,1);
     lcd.print(" ");
     lcd.setCursor(1,1);
     lcd.print(draw_hour);
   }
   else {
     lcd.setCursor(0,1);
     lcd.print(draw_hour);
   }
}
else {
  if (draw_hour<22) {
    lcd.setCursor(0,1);
    lcd.print(" ");
    lcd.setCursor(1,1);
    lcd.print(draw_hour-12);
  }
  else {
    lcd.setCursor(0,1);
    lcd.print(draw_hour-12);
  }
}
if (draw_minute<10) {
  lcd.setCursor(3,1);
  lcd.print("0");
  lcd.setCursor(4,1);
  lcd.print(draw_minute);
}
else {
  lcd.setCursor(3,1);
  lcd.print(draw_minute);
}
  }

rtc.adjust(DateTime(1985, 2, 2, draw_hour, draw_minute, 0));
lcd.clear();

}

Interrupts are disabled in your interrupt and the I2C needs interrupts

Best thing to do is to just set a flag in your interrupt service routine, and then call a function from your main loop depending on the value of the flag to do what you want with interruption ON

Duh!

Thank you very much.

I have a new direction to go! Much appreciated.

Can I use the interrupt to get into a menu and then use flags to get out of menu? Freeing up the I2C bus?

Why interrupt? You can poll the button in loop and if it's pressed, you go to the menu to set the time.

sterretje:
Why interrupt? You can poll the button in loop and if it's pressed, you go to the menu to set the time.

I didn't want to waste cpu on waiting for buttons to get hit... and I want to make sure I can access the config menu at any moment, not just when in the main loop.

You will basically waste as many cycle on polling a flag that was set in an interrupt as on polling a button. Interrupts are there for 'things' that absolutely need attention immediately. If you e.g. don't want to miss data arriving on the Serial port.

Now you have figured out that interrupts don't work inside interrupts, you can e.g. use the following

const byte btnPin = 2;
byte btnStatus = HIGH;

void setup()
{
  pinMode(btnPin, INPUT_PULLUP);
}

void loop()
{
  btnStatus = digitalRead(btnPin);

  if (btnStatus == LOW)
  {
    // do something when the button is pressed
  }
  else
  {
    // do normal work
  }
}

instead of the usual interrupt approach below

const byte btnPin = 2;
volatile byte btnStatus = HIGH;

void setup()
{
  pinMode(btnPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(btnPin), MenuInterrupt, LOW);
}

void loop()
{
  if (btnStatus == LOW)
  {
    // do something when the button is pressed

    // when completed, clear btnStatus
    btnStatus = HIGH;
  }
  else
  {
    // do normal work
  }
}

void MenuInterrupt()
{
  btnStatus = LOW;
}

Note that the interrupt routine is very short so chances of blocking other interrupts for a longer period of time are extremely slim.

Further you are more than likely not interested in the pin being LOW but in the pin going LOW (change of state); that's reflected in the above code.

I also noticed that your loop() is constantly updating the lcd; there is no need for that, you're only interested in doing that when e.g. the minutes change.

You can use something like below

void loop()
{
  DateTime now = rtc.now();
  
  // remember the minute or second that the display was last updated
  // intialised with an impossible value so the first time we enter loop, it will display the time
  static byte lastUpdateTime = 255; 

  // update display when minute changes
  if(now.minute != lastUpdateTime)
  {
    // remember the last time we updated the display
    lastUpdateTime = now.minute;

    // update the display
    ...
    ...
  }
}

Please note that those are just suggestions.

drgruney:
Hello all,

I'm trying to create an automated device that runs based on time of day ... figured a good place to start would be in making a basic two-line LCD clock.

One really important thing I want to do is have the ability to set the clock after the Arduino is up and running.

My two-button clock project is made to do exactly that.
Here is a link to its thread: Two-button clock - Exhibition / Gallery - Arduino Forum

When I wrote that thread, the clock was a work in progress. Because of that, the different posts in the thread show different stages of the project.

Many thanks! Adding to my read list.