millis vs RTC for counters - turn something off after x seconds/minutes?

Status
Not open for further replies.

MrGlasspoole

Well-known member
millis vs RTC for counters / turn something off after x - RFM69 pump control

I always have my problems with the millis stuff.
Every time i need to do something with milis my head is spinning.

At the moment i want to turn on a relay (button) that goes off after 1 Minute.
Now i did set up the RTC for the first time and ask myself if its a good idea to use the real time for stuff like that?

I did not find examples for something like that based on RTC?
 
Last edited:
you can do it with millis(), i use it all the time without an RTC

Globals:

Code:
unsigned long myCounter;
uint8_t myCounterStop; // performs a 1 time trigger

in your function that is looped:

Code:
if ( myCounter - millis()>= 60000 && myCounterStop ) {
// this only triggers relay off once.
myCounterStop = 0; // turn off this trigger
// command here to turn off relay
}
}

using a button?

Code:
if ( digitalRead(buttonPin) == 0 ) {
myCounterStop = 1;
myCounter = millis();
}
 
Last edited:
You can obviously do something like this many different ways, including RTC. But for things like this I would probably just use millis.

On The Teensy, there is a convenient wrapper for this called elapsedMillis (elapsedMillis.h)

With this you can define an instance of this object like: elapsedMillis buttonTimeoutTime

Then when the button is pressed or the like: you can set a state that the button is pressed like (button_pressed = true) and set the buttonTimoutTime= 0;

Then in your code that checks for this, you could then simply test like: if (button_pressed && (buttonTimeoutTime >= 60000))

but you are constrained on how large a number you can test for, which is an unsiged 32 bit number, so you have something like: 4294967295 milliseconds, which should cover a period of several days...

Where I may use the RTC, is if I want to do something at some specific date and time, and also if I wish alarms to happen if something does not happen within some amount of time and I wish for this to work across reboots of the Teensy... But even in some of these cases I may internally then convert these types of alarms into millis. That is the setup will get the list of these and then convert these to elapsed millis...
 
@tonton81, sure you can do it with millis. But as i said it gives me headaches all the time.

@KurtE, is that elapsedMillis only available on the Teensy? Never saw a Arduino example using it.

I thought maybe with RTC it would be easier then this millis stuff because you can check what time it is and then 10 minutes later say: "do that"
 
Yes elapsedMillis is something brought to you by Paul as part of Teensydunio... But there is no magic in it other than c++ operator overloads...
For the most part it simply keeps an internal uint32_t value with millis() and then when you ask for the value it simply does another call to millis and subtracts its saved value...

But if you have Teensyduino installed, look in the avr\cores\teenys3 directory for it...

The fun of real time clocks is suppose the time is 11:55PM on Feb 28th of 2018, and you want 10 minutes from now, what is the proper date and time, for that. I assume that there are some functions to calculate this, which have to take into account is it leap year or not...
 
Hm it does not work like i have it yet:
Code:
elapsedMillis pumpTimeout;

void recirculationPump(){
  if (mcp2.digitalRead(0) == LOW) {         // Pump is already running
    Serial.println("Pump already running"); 
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
    pumpTimeout = 0;
    if (pumpTimeout >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
  }
}

recirculationPump is triggered by a RFM69 button i made. So this code is on the receiver (Teensy3.5).
I guess recirculationPump is not a loop and runs just once and that is the problem?
 
you would need to add
Code:
    if (pumpTimeout >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
in your loop. it's probably best to add a uint8_t variable also to disable the statement when not in used like i did above.

:)
 
Last edited:
It goes HIGH because if I push the button on the sender the relay turns on.

Ok it takes getting used to because in other forums they kill you posting such long stuff :D
But here it is:
Code:
/**************************************************************
* Connection                                                  *
***************************************************************
RFM69 MOSI <> 11
RFM69 MISO <> 12
RFM69 DIO0 <> 2
RFM69 NSS  <> 3
RFM69 SCK  <> 14
**************************************************************/

#include <RFM69.h>             //get it here: github.com/lowpowerlab/rfm69
#include <RFM69_ATC.h>         //get it here: github.com/lowpowerlab/rfm69
#include <SPI.h>               //comes with Arduino IDE
#include <Wire.h>              //comes with Arduino IDE
#include <Adafruit_MCP23017.h> //get it here: github.com/adafruit/Adafruit-MCP23017-Arduino-Library
#include <TimeLib.h>           //get it here: github.com/PaulStoffregen/Time

/**************************************************************
* RADIO SETUP                                                 *
**************************************************************/
#define NODEID        40   //unique for each node on same network
#define NETWORKID     40   //the same on all nodes that talk to each other
#define FREQUENCY     RF69_868MHZ
#define ENCRYPTKEY    "4+A8uQ)P<5e3U7f*" //exactly the same 16 characters/bytes on all nodes!
#define IS_RFM69HW    //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define ENABLE_ATC    //comment out this line to disable AUTO TRANSMISSION CONTROL
#define ATC_RSSI      -85 // target RSSI

#define ACK_TIME      20  // Number of milliseconds to wait for an ack
#define RETRY_PERIOD  2   // How soon to retry (in seconds) if ACK didn't come in
#define RETRY_LIMIT   2   // Maximum number of times to retry

bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network

/**************************************************************
* MISC SETUP                                                  *
**************************************************************/
#define SERIAL_BAUD   115200
#define DEBUG         1 // Set to 1 for serial port output

Adafruit_MCP23017 mcp1; // Create MCP 1
Adafruit_MCP23017 mcp2; // Create MCP 2

/**************************************************************
* TEENSY                                                      *
**************************************************************/
#define LED           13 // teensy
#define CLOCKPIN      14 // teensy alternative SCK since first SCK is the LED
#define T3_IRQ_PIN    2  // pin for irq on teensy
#define T3_IRQ_NUM    2  // number for IRQ on teensy
#define T3_SPI_CS     3  // pin for SPI CS on teensy

#ifdef IS_RFM69HW
  bool isRFM69HW = true;
#else
  bool isRFM69HW = false;
#endif

uint8_t irqPin = T3_IRQ_PIN;
uint8_t irqNum = T3_IRQ_NUM;
uint8_t csPin = T3_SPI_CS;

#ifdef ENABLE_ATC
  RFM69_ATC radio(csPin, irqPin, isRFM69HW, irqNum);
#else
  RFM69 radio(csPin, irqPin, isRFM69HW, irqNum);
#endif

/**************************************************************
* DATA STRUCTURE                                              *
**************************************************************/
typedef struct __attribute__((__packed__)) {
  uint16_t nodeId;         // Store this nodeId
  uint16_t vcc;            // Battery voltage
  byte tx;                 // Transmit level
  uint16_t secu = 7539;    // Security key
} Payload;
Payload theData;

/**************************************************************
* SETUP                                                       *
**************************************************************/
void setup() {
  SPI.setSCK(CLOCKPIN);   // Alternative SCK pin
  pinMode(irqPin, INPUT); // Enable INPUT mode on RFM69 IRQ pin

  radio.initialize(FREQUENCY, NODEID, NETWORKID);
  radio.encrypt(ENCRYPTKEY);
  radio.promiscuous(promiscuousMode);
  #ifdef IS_RFM69HW
    radio.setHighPower(); // Only for RFM69HW!
  #endif

  pinMode(LED, OUTPUT);

  mcp1.begin(0);             // Start MCP 1 on Hardware address 0x20
  mcp1.pinMode(0, OUTPUT);   // Define GPA0 on MCP1 as output
  mcp1.digitalWrite(0, LOW);

  mcp2.begin(1);             // Start MCP 2 on Hardware address 0x21
  mcp2.pinMode(0, INPUT);    // Define GPA0 on MCP2 as input
  mcp2.pullUp(0, HIGH);      // Activate Internal Pull-Up Resistor

  setSyncProvider(getTeensy3Time); // Set the Time library to use Teensy 3.0's RTC to keep time
  
  startupTasks(); // Run tasks

}

void startupTasks() {
  if (DEBUG) {
    char buff[50];
    Serial.begin(SERIAL_BAUD); // Open serial communications
    while (!Serial) {;}        // Wait for serial port to connect
    sprintf(buff, "\nThe frequency is %d Mhz, Kenneth!", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
    Serial.println(buff);
    #ifdef ENABLE_ATC
      Serial.println("RFM69_ATC Enabled (Auto Transmission Control)\n");
    #endif
    if (timeStatus()!= timeSet) {
      Serial.println("Unable to sync with the RTC");
    } else {
      Serial.println("RTC has set the system time");
      digitalClockDisplay();
      Serial.println();
    }
  }

  setRTC();
}

/**************************************************************
* TIME                                                        *
**************************************************************/
void setRTC() {
  if (Serial.available()) {
    time_t t = processSyncMessage();
    if (t != 0) {
      Teensy3Clock.set(t); // set the RTC
      setTime(t);
    }
  }
}

void digitalClockDisplay() {
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
}

time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

/*  code to process time sync messages from the serial port   */
#define TIME_HEADER  "T"   // Header tag for serial time sync message

unsigned long processSyncMessage() {
  unsigned long pctime = 0L;
  const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 

  if(Serial.find(TIME_HEADER)) {
     pctime = Serial.parseInt();
     return pctime;
     if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013)
       pctime = 0L; // return 0 to indicate that the time is not valid
     }
  }
  return pctime;
}

void printDigits(int digits){
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/**************************************************************
* RECIRCULATION PUMP                                          *
**************************************************************/
elapsedMillis pumpTimeout;

void recirculationPump(){
  if (mcp2.digitalRead(0) == LOW) {         // Pump is already running
    Serial.println("Pump already running"); 
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
    pumpTimeout = 0;
    if (pumpTimeout >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
  }
}

/**************************************************************
* LOOP                                                        *
**************************************************************/
void loop() {

  if (radio.receiveDone()) {
    if (radio.DATALEN != sizeof(Payload)) {
      Serial.print("Invalid Payload received!");
    } else {
      theData = *(Payload*)radio.DATA; // Assume radio.DATA actually contains our struct and not something else
      if ((theData.nodeId==41 && theData.secu==7539) || (theData.nodeId==42 && theData.secu==7539) || (theData.nodeId==43 && theData.secu==7539)) {
        recirculationPump();
      }
      if (DEBUG) {
        Serial.print("Node:");
        Serial.print(theData.nodeId);
        Serial.print(", Vcc:");
        Serial.print(theData.vcc);
        Serial.print(", TX Level:");
        Serial.print(theData.tx);
        Serial.print(", RSSI:");
        Serial.print(radio.RSSI);
      }
    }

    if (radio.ACKRequested()) { // When a node requests an ACK, respond to the ACK
      radio.sendACK();
      Serial.print(" - ACK sent.");
    }
    
    Serial.println();
  }

}
 
Last edited:
I don't know what I'm missing.
This also does not work:
Code:
void recirculationPumpRuntimeError(){
  if (mcp2.digitalRead(0) == LOW) {   // Pump was turned on
    Serial.println("Pump was turned on");
    elapsedMillis pumpRuntimeError;
    if (pumpRuntimeError >= 5000) {  // If pump is running longer then 5 seconds
      Serial.println("Pump running to long");
    }
  }
}
"Pump was turned on" is printed without pulling the MCP23017 low right after starting the Teensy - and it is printed slow?
If i pull low it gets printed fast...
As you can see in the code above i activate the internal pull-up resistor in setup...

Everything works until i try to use elapsedMillis in my code :(
 
you keep resetting that ellapsedmillis variable everytime that function is called, the if statement should be free running, not stuck behind a trigger

post #2 has the answer without the overhead of extra code and resources or complexity.
but you dont want to do millis.
the examples and macros your attempting to use are making it look more complicated than it is.
 
Last edited:
I am confused by your code:
Code:
void recirculationPump() {
  if (mcp2.digitalRead(0) == LOW) {         // Pump is already running
    Serial.println("Pump already running");
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
    pumpTimeout = 0;
    if (pumpTimeout >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
  }
}
I agree with Tonton81 here. But first things, you test to see if it is already LOW? to say it is on, but then set it HIGH to be on? Shouldn't it maybe be, checking for HIGH versus LOW. If you get a second message to turn HIGH and it is HIGH do you want to ignore or reset the timer to turn off? If it were me I would rework this into two functions. Maybe something like:
Code:
/**************************************************************
  RECIRCULATION PUMP
**************************************************************/
elapsedMillis pumpTimeout;

void recirculationPump() {
  if (mcp2.digitalRead(0) == HIGH) {         // Pump is already running
    Serial.println("Pump already running");
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
    pumpTimeout = 0;
  }
}

void checkRecirculationPumpTimeout() {
  if (mcp2.digitalRead(0) == HIGH) {         // Pump is running
    if (pumpTimeout >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
  }
}
And then add a call to checkRecirculationPumpTimeout and the start (or end) of your loop function.

The code above ignores a second start. If you instead wish to restart the timeout, the code might look like:
Code:
/**************************************************************
  RECIRCULATION PUMP
**************************************************************/
elapsedMillis pumpTimeout;

void recirculationPump() {
  if (mcp2.digitalRead(0) == HIGH) {         // Pump is already running
    Serial.println("Pump already running");
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
  }
  pumpTimeout = 0;
}

As you mentioned about maybe, elapsedMillis is confusing you. The above could be changed to simply use millis, like:

Code:
/**************************************************************
  RECIRCULATION PUMP
**************************************************************/
unsigned long pumpTimeout;

void recirculationPump() {
  if (mcp2.digitalRead(0) == HIGH) {         // Pump is already running
    Serial.println("Pump already running");
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
    pumpTimeout = millis();
  }
}

void checkRecirculationPumpTimeout() {
  if (mcp2.digitalRead(0) == HIGH) {         // Pump is running
    if ((millis()-pumpTimeout)  >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
  }
}
As I mentioned earlier it is simply a c++ sugar coating over using millis()
 
I am confused by your code:
But first things, you test to see if it is already LOW? to say it is on, but then set it HIGH to be on? Shouldn't it maybe be, checking for HIGH versus LOW.
There are two MCP23017's.
mcp1 is driving the relays.
mcp2 is 230VAC feedback.

If the pump is running (has 230VAC) then GPA0 on mcp2 is low.
That means "Pump already running" no need to set mcp1 GPA0 (pump relay) high.
That print is a placeholder. Later i want to send the pump state back to the RFM69 button LED that i know in the bathroom that warm water is already there.

The goal is:
1. run pump only one minute and only if it did not run the last 10 minutes

2. if feedback mcp2 low (pump already running/don't turn it on) = send RF to button blue LED on for 5 seconds (tells me warm water is there)

3. if feedback mcp2 was low the last 10 minutes (don't turn pump on) = send RF to button blue LED on for 5 seconds (tells me warm water is there)

4. if feedback mcp2 longer low than 1 minute = send error message (because something is wrong)

5. if mcp1 digitalRead HIGH and feedback mcp2 LOW in under 2 seconds = send RF to button red LED on for 5 seconds (tells me the pump is working after button press)

6. if mcp1 digitalRead HIGH and feedback mcp2 not LOW in under 2 seconds = send RF to button blink red LED fast + send error message (tells me something is wrong pump does not work)

Sure the blink/LED stuff is done in the buttons microcontroller.

Your code:
So i call "recirculationPump" with my button < this does not run in a loop.
I start "checkRecirculationPumpTimeout" in the main loop when the Teensy is powered < this loop is running all day long.

The code above ignores a second start. If you instead wish to restart the timeout
I guess that is something i need to work out when i start the timer based on the goals above.
1. I can start it if the pump is really running (has 230VAC).
2. I can start it if i call "recirculationPump".
3. I can start it if mcp1 GPA0 goes HIGH.
To me number 1 makes sense.
 
Last edited:
Sorry I missed mcp1 vs mcp2... But the previous code snippets should sort-of work once you change the tests like you mentioned.
1. run pump only one minute and only if it did not run the last 10 minutes
2. if feedback mcp2 low (pump already running/don't turn it on) = send RF to button blue LED on for 5 seconds (tells me warm water is there)
3. if feedback mcp2 was low the last 10 minutes (don't turn pump on) = send RF to button blue LED on for 5 seconds (tells me warm water is there)
4. if feedback mcp2 longer low than 1 minute = send error message (because something is wrong)
5. if mcp1 digitalRead HIGH and feedback mcp2 LOW in under 2 seconds = send RF to button red LED on for 5 seconds (tells me the pump is working after button press)
6. if mcp1 digitalRead HIGH and feedback mcp2 not LOW in under 2 seconds = send RF to button blink red LED fast + send error message (tells me something is wrong pump does not work)

But with the goals you mentioned above, it sounds like both of the functions I mentioned probably need to be fleshed out more. Maybe starting something like:
Code:
/**************************************************************
  RECIRCULATION PUMP
**************************************************************/
unsigned long pump_last_start_time = 0
unsigned long pump_request_start_time = 0;
unsigned long pumpTimeout;

void recirculationPump() {
  if (mcp2.digitalRead(0) == LOW) {         // Pump is already running
    Serial.println("Pump already running");
  else if ((mllis() - pump_last_start_time) < (1000*60*10)) {
    Serial.println("Pump ran within the last 10 minutes");   // rule 3
  } else {                                  // Pump is not running
    mcp1.digitalWrite(0, HIGH);             // Turn pump on
    Serial.println("Turned pump on");
    pump_request_start_time = millis();  // remember when we asked for the pump to turn on. 
  }
}

void checkRecirculationPumpTimeouts() {
  if (mcp2.digitalRead(0) == LOW) {         // Pump is running
    if (pump_last_start_time = 0) {
        pump_last_start_time = millis();   // remember when it actually turned on
    }
    if ((millis() - pump_request_start_time ) <= 2000) {
        // Send rf  to blue LED... // rule 5
    } 

    pump_request_start_time = 0;   // don't have any outstanding request anymore. 

    if ((millis()-pump_last_start_time )  >= 5000) {
      mcp1.digitalWrite(0, LOW);
      Serial.println("Turned pump off");
    }
  } else {
    // Pump not on, check for other timeouts. 
    if (pump_last_start_time) {
      if ((millis() - pump_request_start_time ) > 2000) {
        // Send rf to red led... 
        // May want to remember we sent it... 
      }
    }
}
Again I obviously did not try to do every condition here but something like this might work...
 
A big THANK YOU!
Your code was a good/big starting point.
It's almost working except error handling where i have little problems.
But first i want to make sure the electronic is not playing tricks on me and the circuit is ok.
Right side is the 230VAC detection. There are NE-2 neon lamps that shine on the phototransistors. That works great.
I'm unsure if its save what i did with the LEDs. They have 5v reverse voltage...
Then the internal pull ups of the MCP23017 are weak (100k). But since there is 5v anyway from the LM339 if no light i don't need external ones - do I?

230VAC_detection.jpg
 
Last edited:
You are welcome. Hope it works out.

As for the electrical hookup stuff, I only know enough to be dangerous ;) Maybe others might be able to give an opinion.

Side related question to myself and maybe others. In my own project I am keeping and recording real time clock information,
like when one of the wells turns on and when it turns off.

So when I detect the well turns on, I do something like: time_t time_on = now();
And later when it turns off, I do something like time_t time_off = now();

Actually these times are kept as part of a class, but... Question is, suppose I wish to know how long the well was on?

When I do a query in c++ on how to calculate the difference in time, I find the function: difftime (http://www.cplusplus.com/reference/ctime/difftime/)
is defined to do this, but I don't see this function defined or referred to as part of Teensyduino install...

However looking at the time library it appears in our case that time_t is a value which is actually seconds...

So you should be able to compute the delta in seconds by time_off-time_on...

Just wondering to myself if I should just go ahead and do it that way, or if we should add difftime to timelib?

Kurt
 
Just saw i did forget the circuit image in my last posting :mad:
Changed it and it's now there.

Is there a way to change the topic title? It seems like not :(
 
you complain when i give you millis() examples saying you rather NOT use it because you hate it and refuse help with it, yet kurt posted one and you like it?
incredible.
 
@tonton81 sorry, i did not complain and did not say i will never use it.
I'm still interested how to do it with "elapsedMillis". There is no reason not to know how to do it another way if there are more then one.
The main question was if it makes sense to use time from the RTC for something like that.
You both said no and the thread turned more into how to code that whole pump control <- the reason i ask if its possible to change the title.
Sorry if your feeling is i refused help :(
 
I'm still interested how to do it with "elapsedMillis".

When I wrote the elapsedMillis page, I created 3 examples. Do any of those speak to your needs?

If not, let's talk about what sort of example I ought to add to that page. Of course it should be something fairly general purpose to serve as large an audience as possible, but be able to speak (at a quick glance of the page) to the sort of usage you're needing. Hopefully others will find it useful too. Let me know what you think?
 
it looks like it simplifies the (var -millis()) --> class variable, either way the amount of lines of code is same regardless :)
 
either way the amount of lines of code is same regardless :)

The point of elapsedMillis isn't to necessarily reduce the size of your code, but to simplify the mental model and amount of time spent thinking and analyzing how the math and issues like numerical rollover will be handled. It's merit isn't measured in bytes of code, but in seconds of human attention required while writing and while maintaining their code.
 
Not that I want to jump back in here, in posting #12, I showed it first using elapsedMillis and then also showed it using millis()-var ...

In the last code sample I gave it was using the millis()-var approach as I felt he was more comfortable with it. But it would be pretty easy to switch to elapsedMillis()

Again just change the two unsigned long variables to elapsedMillis variables, replace the couple places where I set those variables = millis() to 0 instead and change some of the if statements from (millis()-variable) to just variable... One one maybe complication, is I initialize the unsigned long variables to zero and test against that to know I am not in some state. That may have to change to use a second variable or the like...
 
The point of elapsedMillis isn't to necessarily reduce the size of your code, but to simplify the mental model and amount of time spent thinking and analyzing how the math and issues like numerical rollover will be handled. It's merit isn't measured in bytes of code, but in seconds of human attention required while writing and while maintaining their code.
That is what i was talking about when i wrote: "I always have my problems with the millis stuff. Every time i need to do something with millis my head is spinning."
 
Status
Not open for further replies.
Back
Top