How to use an ezButton to turn off the alarm on a sunrise alarm clock

I've created a sunrise alarm clock that begins to brighten 20 minutes before the scheduled alarm time and then runs a bird chirping routine when the alarm time is reached. Ity uses an Arduino Uno R3 board; a DS3231 RTC board named "rtc"; a TM1637 LED panel called "display"; a 48 chip LED WS28125050 device called "ring" connected to pin 13; an LED called "ledAlarm" connected to pin 12; an LED called "ledPM" connected to pin 10; five ezbuttons, named "modeBtn" connected to pin 2, "hrBtn" connected to pin 3, 'minBtn" connected to pin 4, "alarmBtn" connectd to pin 5, and "lightBtn" connected to pin 6. There is also an active Piezeoelectric Buzzer named "buzzer" connected to pin 12. The rtc is configured for 12 hour display, and the ledPM light is lit at 40% brightness when the time is PM. The hardware is set up to be a sunrise alarm clock with the following functions:

  1. The modeBtn button controls a variable named "setMode" which is initially set to 0. It's value can be 0, 1 or 2. Pressing the modeBtn increments the setMode value by one, and blinks the ledPM light setMode number of times; when setMode is 1, the clock is in time set mode; when setMode is 2 the clock is in alarm set mode; when setMode is 0 the clock is in normal mode. If setMode > 2 set it back to 0.

  2. When setMode is 1 (time set mode), pressing the hrBtn increments the time by one hour, using a 12 hour clock. Pressing the minBtn increments the time by one minute, and if it passes 59 it reverts to 0. If the new value is past noon, turn on the ledPM at 40% brightness; if it's past midnight, turn ledPM off.

  3. When setMode is 2 (alarm set mode), pressing the hrBtn increments the rtc alarm1 by one hour, using a 12 hour clock. Pressing the minBtn increments the rtc alarm1 time by one minute, and if it passes 59 it reverts to 0. If the new alarm1 time is past noon, turn on the ledPM at 40% brightness; if it's past midnight, turn ledPM off.

  4. The alarmBtn controls a boolean variable named "alarmOn" that is initially set to false. When the alarmBtn is pressed and alarmOn is false the value of alarmOn is set to true and the ledAlarm is illuminated at 40% brightness. If alarmOn is true, pressing the alarmBtn will turn off the ledAlarm light and set alarmOn to false. It must also terminate the alarm routine if it is running.

  5. The lightBtn allows the LED ring to function as a lamp. It controls a value named "lightLev" that is initially set to 0. Pressing the lightBtn increments lightLev by 1, but if lightLev is > 3 it is set back to 0. When lightLev is 1 the LED ring chips 40 to 47 are illuminated; if lightLev is 2 the LED ring chips 23 to 47 are illuminated; if lightLev is 3, all 48 led ring chips are lit. When lightLev is 0, no ring chips are lit. The ring should display a warm light at 60% brightness.

  6. If alarmOn is true, 20 minutes before the rtc alarm1 time the sunrise routine begins. Over 20 minutes the led ring is gradually illuminated up to 60 percent of full power, changing over that time from dark orange to light yellow in color one led chip at a time. At any time during the execution of this routine, if the alarmBtn button is pressed, the routine terminates and the ring is turned off. Otherwise, when the routine finishes the ring stays on until pressing the alarmBtn turns it off.

  7. When alarmOn is true, upon reaching the rtc alarm1 time, implement a bird chirping routine on the buzzer that will operate continuously until the alarmBtn is pressed to turn the alarm off.

I've written the following code for it:

#include <Wire.h>
#include <RTClib.h>
#include <TM1637Display.h>
#include <Adafruit_NeoPixel.h>
#include <ezButton.h>

ezButton modeBtn(2);      // create ezButton object attached to pin 2; modeBtn changes the setMode value, which determines whether the time or alarm time is set with the hrBtn and minBtn buttons.
ezButton hrBtn(3);        // create ezButton object attached to pin 3.
ezButton minBtn(4);       // create ezButton object attached to pin 4.
ezButton alarmBtn(5);     // create ezButton object attached to pin 5.
ezButton lightBtn(6);     // create ezButton object attached to pin 6.

#define LED_PIN   13
#define LED_COUNT 48
Adafruit_NeoPixel ring(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

RTC_DS3231 rtc;
#define CLK 9
#define DIO 8

TM1637Display display(CLK, DIO);
const uint8_t blank[] = {0x00, 0x00, 0x00,0x00};

DateTime myNow;
DateTime myTime;

// These are output pins for single LED lights and the buzzer.
int buzzer = 12;
int ledAlarm = 11;
int ledPM = 10;

int count = 10;
int count2 = 5;
int i, t, h, m, a;
int red[20] = {128,134,140,147,153,159,166,172,178,185,191,197,204,210,216,223,229,235,242,248};
int green[20] = {64,73,83,92,102,111,121,130,140,149,159,169,178,188,197,207,216,226,235,245};
int blue[20] = {0,11,22,33,44,55,66,77,88,99,110,121,132,143,154,165,176,187,198,209};
int bright = 5; // This variable controls the brightness increments for the sunrise, and can be a value from 1 to 12.
int ringLeds[] = {48,40,24,0};
int alarm_time, sunrise_time;
int setMode = 0;
int lightLev = 0;
bool alarmOn = false;

//-------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);  // Begin serial communication at a baud rate of 9600: 
  modeBtn.setDebounceTime(50); // set debounce time to 50 milliseconds
  hrBtn.setDebounceTime(50); // set debounce time to 50 milliseconds
  minBtn.setDebounceTime(50); // set debounce time to 50 milliseconds
  alarmBtn.setDebounceTime(50); // set debounce time to 50 milliseconds
  lightBtn.setDebounceTime(50); // set debounce time to 50 milliseconds

  delay(3000);  // Wait for console opening:
  if (! rtc.begin()) {  // Check if RTC is connected correctly:
    Serial.println("Couldn't find RTC");
    while (1);
  }

 /*
  // Set the clock once, then comment this block out, as the rtc chip has a battery...
    if (rtc.lostPower()) { // Check if the RTC lost power and if so, set the time:
        Serial.println("RTC lost power, lets set the time!");
        // The following line sets the RTC to the date & time this sketch was compiled:
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
        rtc.setAlarm1(DateTime(2025, 1, 1, 5, 15, 0), DS3231_A1_Hour);
        rtc.setAlarm2(DateTime(2025, 1, 1, 4, 55, 0), DS3231_A2_Hour);
    }
 */
 
  Wire.begin();
  pinMode(buzzer, OUTPUT);
  pinMode(ledAlarm, OUTPUT);
  pinMode(ledPM, OUTPUT);
  myNow = rtc.now();
  ring.begin();
  ring.setBrightness(0);    
  ring.show();  
  lightLev = 0;
  alarmOn = false;
  display.setBrightness(2);
  display.showNumberDecEx(1234, 0b01000000, true);
  delay(1000);
}

//-------------------------------------------------------------------------------
void loop() {
  // These .loop functions reset the buttons; they don't actually loop
  modeBtn.loop();  // MUST call the loop() function first
  hrBtn.loop();    // MUST call the loop() function first
  minBtn.loop();   // MUST call the loop() function first
  alarmBtn.loop(); // MUST call the loop() function first
  lightBtn.loop(); // MUST call the loop() function first

  showTime();
  if(alarmOn==true) { isAlarmTime(); }

  // ----------------------------------------------------------------------------
  // Handle toggling the alarm and alarm LED on/off.
  if (alarmBtn.isPressed()) {
      if(alarmOn==false) { // if button is pressed and boolean is false
         analogWrite(ledAlarm, 64); // turn LED on
         alarmOn= true; // set boolean to true
      } else if (alarmOn==true) { // if button is pressed and boolean is true
         digitalWrite(ledAlarm, LOW); // turn LED off
         alarmOn = false; // set boolean to false
      }
  }
  
  // ----------------------------------------------------------------------------
  // Set the default time and alarm.
  if (hrBtn.isPressed() && minBtn.isPressed()) { 
      setDefaultAlarmTime(); 
      showAlarmTime();    
  }

  // The main light has three levels -- the three rings -- activated by pressing the lightBtn sequentially...
  if(lightBtn.isPressed()){
     ring.clear();
     lightLev++;
     if(lightLev > 3) { lightLev = 0; }
     for(int i = ringLeds[lightLev]; i < ring.numPixels(); i++){ ring.setPixelColor(i, 64, 64, 32); }
     ring.setBrightness(128); 
     ring.show();
  }
  
  // ----------------------------------------------------------------------------
  // Change the setMode value if the button is pressed...
  if(modeBtn.isPressed()){
     analogWrite(ledPM,0);
     setMode++;
     for (int i = 1; i <= setMode; i++) {
          delay(300);
          analogWrite(ledPM,64);
          delay(300);
          analogWrite(ledPM,0);
     }
     if(setMode > 2) { setMode = 0; }
  }

  // ----------------------------------------------------------------------------
  // Handle the Hour incrementing for time and alarm...
  if(setMode>0) {
     if (hrBtn.isPressed()) {
       // setMode 1 is to set the current time...
       if(setMode==1) {
          myNow = rtc.now();
          DateTime plusOne (myNow.unixtime() + 3600); // add one hour
          myNow = rtc.now();
          rtc.adjust(plusOne); // set to new time
       } else {
          // Adjust the alarm time
          DateTime alarm = rtc.getAlarm1();
          DateTime alarm1 (alarm + TimeSpan(0,1,0,0));
          rtc.setAlarm1(alarm1, DS3231_A1_Hour);
          DateTime alarm2 (alarm1 - TimeSpan(0,0,20,0));
          rtc.setAlarm2(alarm2, DS3231_A2_Hour);
          alarm_time = (alarm1.hour()*100)+alarm1.minute();
          sunrise_time = alarm_time - 20;
       }
       showTime();
       delay(300);
     }
 
     // ----------------------------------------------------------------------------
     // Handle the Minute incrementing for time and alarm...
     if (minBtn.isPressed()) {
       // setMode 1 is to set the current time...
       if(setMode==1) {
          myNow = rtc.now();
          DateTime plusOne (myNow.unixtime() + 60); // add one minute
          myNow = rtc.now();
          rtc.adjust(plusOne); // set to new time
       } else {
          // Adjust the alarm time
          DateTime alarm = rtc.getAlarm1();
          DateTime alarm1 (alarm + TimeSpan(0,0,1,0));
          rtc.setAlarm1(alarm1, DS3231_A1_Hour);
          DateTime alarm2 (alarm1 - TimeSpan(0,0,20,0));
          rtc.setAlarm2(alarm2, DS3231_A2_Hour);
          alarm_time = (alarm1.hour()*100)+alarm1.minute();
          sunrise_time = alarm_time - 20;
       }
       showTime();
       delay(300);
     }
  }
} 

//-------------------------------------------------------------------------------
void setDefaultAlarmTime() {
     rtc.disableAlarm(1);
     rtc.disableAlarm(2);
     rtc.clearAlarm(1);
     rtc.clearAlarm(2);
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
     rtc.setAlarm1(DateTime(2025, 1, 5, 5, 15, 0), DS3231_A1_Hour);
     rtc.setAlarm2(DateTime(2025, 1, 5, 4, 55, 0), DS3231_A2_Hour);
}

//-------------------------------------------------------------------------------
void checkAlarmStatus() {
  alarmBtn.loop(); // MUST call the loop() function first
  if (alarmBtn.isPressed()) { // if button is pressed and boolean is false
      if(alarmOn==false) {
         analogWrite(ledAlarm, 64); // turn LED on
         alarmOn= true; // set boolean to true
      } else if (alarmOn==true) { // if button is pressed and boolean is true
         digitalWrite(ledAlarm, LOW); // turn LED off
         alarmOn = false; // set boolean to false
      }
  }
}

//-------------------------------------------------------------------------------
void isAlarmTime() {
  //if (rtc.alarmFired(1) == true){
  myNow = rtc.now();
  t =(myNow.hour()* 100 )+ myNow.minute();
  if(t == sunrise_time && myNow.second()==0){
     sunrise();
     chirp();
  }
}

//-------------------------------------------------------------------------------
void showAlarmTime() {
  myNow = rtc.getAlarm1(); 
  t =(myNow.hour()* 100 )+ myNow.minute();
  // Serial.println(t);
  display.showNumberDec(t, false, 4, 0);
  analogWrite(ledPM,0);
  delay(500);
  for (int i = 1; i <= 3; i++) {
          delay(500);
          analogWrite(ledPM,64);
          delay(500);
          analogWrite(ledPM,0);
  }
} 

//-------------------------------------------------------------------------------
void showTime() {
  myNow = rtc.now();
  a = myNow.second()%2;
  if (setMode == 2) { myNow = rtc.getAlarm1(); } // If we're in alarm set mode, change the display time to the alarm time...
  t =(myNow.hour()* 100 )+ myNow.minute();
  if(t > 1200) {
        analogWrite(ledPM,32);
        if (t >= 1300) { t = t - 1200; }
  } else {
        analogWrite(ledPM,0);
  }

  // Serial.println(t);
  if(a==0){display.showNumberDec(t, false, 4, 0);}
  else{display.showNumberDecEx(t, 0b11100000, false, 4, 0);}
} 

//-------------------------------------------------------------------------------
void alarmOff(){
  alarmOn = false;
  analogWrite(ledAlarm,0);
  ring.clear();
  ring.setBrightness(0);
  ring.show();            
  digitalWrite(buzzer,LOW);
}

//-------------------------------------------------------------------------------
void sunrise() {
  if(alarmOn==true) {
     ring.clear();
     lightLev = 0;
     for (int j = 0; j <20; j++){
         ring.setBrightness((bright+1)*j); 
         for(int i = 0; i < ring.numPixels(); i++){
             ring.setPixelColor(i, red[j], green[j], blue[j]);
             ring.show();
             showTime();
             checkAlarmStatus();
             if(alarmOn==false) {return;}
             delay(1250); // To make this loop last 20 minutes.
             // delay(125); // For testing purposes.
         }
         checkAlarmStatus();
         if(alarmOn==false) {return;}
     }
  }
}

//-------------------------------------------------------------------------------
void chirp() {
  while (alarmOn==true) {
    showTime();
    while(count2 >0){
      for(int i = 300; i>0; i--){
          digitalWrite(buzzer,HIGH);
          delayMicroseconds(i);
          digitalWrite(buzzer,LOW);
          delayMicroseconds(i);
      } 
      delay(100);
      count2 = count2 -1;
   } 
   count2 = 5;
   while(count >0){
     for(int i = 0; i <300; i++){
         digitalWrite(buzzer,HIGH);
         delayMicroseconds(i);
         digitalWrite(buzzer,LOW);
         delayMicroseconds(i);
     } 
     count = count -1;
   }
   count = 10;
   for(int i2 = 300; i2>0; i2--){
       digitalWrite(buzzer,HIGH);
       delayMicroseconds(i2);
       digitalWrite(buzzer,LOW);
       delayMicroseconds(i2);
   } 
   delay(400);
   while(count2 >0){
     for(int i = 300; i>0; i--){
         digitalWrite(buzzer,HIGH);
         delayMicroseconds(i);
         digitalWrite(buzzer,LOW);
         delayMicroseconds(i);
     } 
     delay(400);
     count2 = count2 -1;
   } 
   count2 = 5;
   while(count >0){
     for(int i = 0; i <300; i++){
         digitalWrite(buzzer,HIGH);
         delayMicroseconds(i);
         digitalWrite(buzzer,LOW);
         delayMicroseconds(i);
     }
     delay(100); 
     count = count -1;
   }
   count = 10;
   for(int i2 = 300; i2>0; i2--){
       digitalWrite(buzzer,HIGH);
       delayMicroseconds(i2);
       digitalWrite(buzzer,LOW);
       delayMicroseconds(i2);
    }
    delay(random(5000,15000));
    checkAlarmStatus();
  } // end while loop...
}

I want to allow the alarmBtn to turn off the alarm at any point in the alarm / chirp functions. But the button doesn't seem to let that happen. How would I use the alarmBtn ezButton to do this?

Thanks! -Steve

You have 14 calls to delay. Without figuring out all your code, I can still suggest that this is you problem.

delay() means all you code stops. And waits.

Here

  // These .loop functions reset the buttons; they don't actually loop
  modeBtn.loop();  // MUST call the loop() function first

and the other calls is what actually looks at the buttons. So if you press and release a button and there is no call to the button's loop method, that button activity will not be seen.

That call does not

... reset [a]  button[]

It reads the button.

And ezButton is not a good choice even if you have found that sometimes you can hold a button down and eventually it will get read and your code will react to it.

With "debounce time [set] to 50 milliseconds", ezButton needs its service method loop to be called much more often. Your loop must run free.

To accomplish this, your entire sketch must be done over after you learn about a different way to make things happen over time, or every so often, or after some amount of time has past that does not depend on just stopping progress through the code with delay.

To begin your journey, investigate and come to a full understanding of "blink without delay".

Start here

https://docs.arduino.cc/built-in-examples/digital/BlinkWithoutDelay/

and google

arduino blink without delay

to see that there are dozens of people who wanna tell you all about it, each in her own way. Read a few to get different perspectives on the basic concept.

You may not see right away how that can possibly solve you problem; trust me, knowing this first step is the first step.

You'll get more advice on this, we all want you to be able to make stuff happen without delay and I mean without delay(), not as soon as possible. But that, too.

a7

You may find this useful.

Thanks a7! I'll admit I was a bit trepidatious about using the delay function from the start, especially as there are so many places in the code where something has to wait. I've looked into the blink without delay subject, and now wonder if I can substitute a call to this function for the places I used delay in the code...(setting aside for the moment the debouncing issue with ezButton).

// Function to pause for a specified number of milliseconds
void pauseMS(unsigned long ms) {
  unsigned long startTime = millis();  // Get current time
  while (millis() - startTime < ms) {} // Loop until specified time has passed
}

Thanks, -Steve

That's a good start and it looks like you have the basic pattern.

What you have created, however, is a hand-rolled version of delay()!

It will block just as effectively. There are a few ways to go from here. I'll share a hack, just to stir up some dust here, and I do not recommend becoming reliant on it... but it might be just the thing in these circumstances.

Without digging into your code, or digging out any of the real pins and variables you used, see if this might work:

// Function to pause for time or button...
void myDelay(unsigned long ms) {
  unsigned long startTime = millis();  // Get current time
  while (millis() - startTime < ms) {
    if (digitalRead(someButtonPin) == LOW) {
      break; // bust out of this wheel spinning
    }
  }
}

It assumes (as does ezButton) that your switches are pulled HIGH and read LOW when pressed.

Look up the break statment where ever it is you look stuff up. It allows for an early exit of both the for loop and while loop and the do/while loop statements.

But is is a hack, and will do nothing for the other objections around delay(), in particular the problems it will def cause any ezButtons you have.

I called it myDelay() here, not my name. It has been discovered or invented many many times. You will outgrow it. But in the right place it can do a fine job, and it's hard to argue with success.

I just now searched the fora

@alto777 myDelay

Poke around in the results - I have been involved with discussion around hacks and dealy() for some time.

Here's a post and it has a link. A place to start.

The "real" way to do things like this is somewhat more involved, as you may suspect, but it is just one more thing about this hobby that will be hard until it is easy. I can almost say I don't recall not knowing this stuff and don't remember learning it. That could be due to other factors, some of which were fully in my control. :expressionless:

In case you are faster than my usual cycle and have a fire to curl up in front of or like me a nice chair at the beach under just enough umbrella, the concepts can be found explained many times - multiple perspectives (did I say that already) will let your own way of internalizing ideas have the best chance of doing.

Use google on the entire internet:

arduino blink without delay (check!)

Arduino two things at once

arduino finite state machine

arduino traffic lights state machine

There are also full tutorials right here, try this on

HTH

a7

Hi alto,

This looks interesting, and I've extended it to accomplish what I need. My sunrise clock routine takes 20 minutes to do, so once it's triggered I'm not in the loop() cycle again until I break out of the sunrise routine. Same with the chirp routine, which executes continuously once the sunrise routine finishes, until the alarm is turned off. So I have to break out of your routine if the button is pressed, and then break out of the sunrise or chirp routines. I also wanted to make the routine generic, so as to work with any button. And I set it up so it skips the button test if no button pin is passed in. This is what I've done:

//-------------------------------------------------------------------------------
// Function to pause by milliseconds for time or button...
bool delayMs(unsigned long ms, int thisButtonPin) {
  unsigned long startTime = millis();  // Get current time
  while (millis() - startTime < ms) {
    if (thisButtonPin !=0 && digitalRead(thisButtonPin)  == LOW) {
        return true; // bust out of this wheel spinning
    }
  }
  return false;
}

//-------------------------------------------------------------------------------
void sunrise() {
  if(alarmOn==true) {
     ring.clear();
     lightLev = 0;
     for (int j = 0; j <20; j++){
         ring.setBrightness((bright+1)*j); 
         for(int i = 0; i < ring.numPixels(); i++){
             ring.setPixelColor(i, red[j], green[j], blue[j]);
             ring.show();
             showTime();
             // if(delayMs(125, 5)) {; // For testing purposes, uncomment this line and comment the next line. The routine will take 2 minutes instead of 20.
             if(delayMs(1250, 5)) { // If button on pin 5 is pushed, break out.
                alarmOff();
                return;
             }
         }
     }
  }
}

The alarmOff() function does a bunch of housekeeping...sets the bool alarmOn variable to false, clears the ring, turns off the buzzer, and turns off the alarmLed.

Thanks, -s

I'll look closer when I am in the lab.

It is painful to work on stuff like this that takes hours if run literally, and by making arrangements for time to go faster (!) you risk introducing things that mask errors.

I've never managed to make time go faster except through tricks; if I do a big RTC project where it would be useful I would have this in mind from day zero.

a7

I put an update on the Arduino forum page for this. Here's a picture of the project...

-s

(attachments)

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.