Which timer library to use for several seconds of delay

Hi there! It's been a few months since I last worked on my project, you can see my previous posts on the project here:
most recent: Did I reinvent delay() w/ a while() loop while trying to make a pulse function? - Programming Questions - Arduino Forum
Writing output values to pins back to back seems buggy, digitalwritefast() too - #16 by system - Programming Questions - Arduino Forum
How to pulse a digital output, hardware timers, software timers, delay, mills() - Programming Questions - Arduino Forum

I've been reading how-to's and scouring the web and getting frustrated with what I feel is such a simple thing that I want to do. I'm being bombarded with PWM and LEDs to the point I can't remember what it is I'm looking for (Is that all people do with arduinos???).

I want to latch an output for 2 - 10ish seconds. The amount of time doesn't have to be very precise, but it has to fire off every time for the same amount of time. It seems that timer interrupts will do what I want. I understand the way they work in a general sense; the syntax I'm not sure on. I've also found some libraries that deal with timing, but can't figure out their documentation because I have no idea what they are saying, either because I'm not famaliar with their vocab choices or i can't read their code.

What I would like from this post is a recommendation of what library to use. I've been trying to figure out how to do this for months (off and on) and don't see the need to reinvent the wheel unless I have to. Once I know what library will do what I want I can start to try to figure it out and/or ask any specific questions. Right now I'm just really frustrated over all this searching and reading for it to be as clear as mud on how to proceed.

I'm using servodecode on an arduino uno, so I only have timer2 left. What library should I use for this project?

It seems that timer interrupts will do what I want.

It also seems like polling is going to more than accurate enough. There is no reason to drag in timers and interrupts and interrupt handlers when a simple watch and some variables is more than adequate.

What I would like from this post is a recommendation of what library to use.

I'd recommend that you forget about timers and libraries, and read up on millis().

Why use a library at all?
all time related variables are unsigned long

void loop(){
if (timer_running == 0 && event1_start_trigger == 1){
event1_start_time = millis();
timer1_running = 1;
event1_end_time = event1_start_time + event1_duration;
// start timed action
}
if (timer_running == 1 && (millis() - event1_end_time ) >=0){
timer1_running = 0;
// end timed action
}
} // end void loop

For the length of time you need (a very long time for the AVR processors), the easiest way to do this is as already suggested, but here is an explanation of the logic behind how it is done.

The loop() function is called continuously - conceptually once it is completed it starts again. What you need to do is remember when you need to do something (like a calendar entry reminds you to go somewhere or be at a meeting, for eacmple) but in the case of Arduino thge 'next' time is simply when the millis() function returns a value that is greater than or equal to the target time (appointment) that you set earlier.

Interrupts are meant for situations where your code has to react NOW to fast inputs and has to do something about it. As PaulS pointed out, polling is all that is required in this case and CrossRoads has prety much set up the code needed.

The key for all this, btw, is not to create delays in the code (using the useful but misused delay() function) but keep running as fast as possible so you can get to everything in time. Multitasking is all about juggling the balls at the same time and you do this by knowing when you need to eecute something next or be fast enough to respond to an input when it happens.

Can you create a function inside of loop() & reference that function within loop()? I suspect not, & I'd rather abstract these functions out of the main loop for readability & ease of use. I'm only doing 3 things, but which I do & when are determined after a few criteria, so it might be 1 thing, it might be 1 set of 2 or it might be the other set of 2, so I'd rather not just copy/paste the code & risk a botched copy/paste later on down the road.

The loop I am using in the functions pulseOutHigh and -Low winds up being something I came up with after I realized that polling millis() didn't really work outside the main loop the same way as inside the loop. Turns out it stops the program just like a delay(), which is not what I want to do.

Is there a way to modify pulseOutHigh and -Low allow for the polling timer?

#include <ServoDecode.h>

char * stateStrings[] = {"NOT_SYNCHED", "ACQUIRING", "READY", "in Failsafe"};
  
// Define readable non-backwards states for relays.
// This is because relays are active LOW.  That means you can ground the pin and activate the relay.
const byte relayOn = 0;
const byte relayOff = 1;

// Give each relay friendly name and map to Arduino Digital I/O pin number
const byte relay1 = 2;  // wire 1 of the engine kill actuator.  Pulse to allow engine to run.
const byte relay2 = 3;  // wire 2 of the engine kill actuator.  Pulse to kill engine.
const byte relay3 = 4;  // brake controller
const byte relay4 = 5;  // currently unused

// For reading/feedback purposes, compare to the on off constants above
byte relay1State;
byte relay2State;
byte relay3State;
byte relay4State;
                                    // seconds * milliseconds, for readibility
const unsigned long idleEngineKill = 10 * 1000; // delay to kill the motor 
const unsigned long idleBrakesEngage = 2 * 1000;
const unsigned long engineStartPeriod = 15 * 1000; // length of time allowed on a new connection to allow for starting the mower engine
long whatTimeIsIt = 0;
long userInputTimer = 0;
boolean timedOut = false;

// Initialize to zero to give it an obvious out of range starting value
// valid values will be from 1000 - 2000 with maybe some spillage in either direction

int curVal[7] = {0, 0, 0, 0, 0, 0, 0}; // Current values of the channel signals
int prevVal[7] = {0, 0, 0, 0, 0, 0, 0}; // Previous values of the channel signals
int arraySize = 7;
int numChannels = 6; // size of both arrays, used for feedback purposes

const int chChangeThreshold = 15; // Used to determine if there was user input on a given channel
boolean firstTimeOn = true;
boolean firstTimeOff = true;
boolean firstInput = true;
boolean engineShouldBeOff = true;
boolean brakesShouldBeOff = false;
byte throttleChannel = 3; // sets the throttle channel to remove it from idle calculations.  Set to 0 to include all channels in idle calculations.  This is selectable as the transmitter is programmable.


void setup(){
  
  //ServoDecode setup
  ServoDecode.begin();
  ServoDecode.setFailsafe(3,1234); // set channel 3 failsafe pulse width
  
  // Relay Setup Initialize Pins so relays are inactive at reset
  digitalWrite(relay1, relayOff);
  digitalWrite(relay2, relayOff);
  digitalWrite(relay3, relayOff);
  digitalWrite(relay4, relayOff);
  relay1State = relayOff;
  relay2State = relayOff;
  relay3State = relayOff;
  relay4State = relayOff;
  
  // THEN set pins as outputs
  pinMode(relay1, OUTPUT);   
  pinMode(relay2, OUTPUT);  
  pinMode(relay3, OUTPUT);  
  pinMode(relay4, OUTPUT);    
  delay(1000); // Check that all relays are inactive at Reset
  
  // Prevent motor from starting and engage breaks
  engineKill();
  brakesEngage();

} // end setup()


void loop(){

  if(ServoDecode.getState()!= READY_state) { // transmitter is off or out of range
    if(firstTimeOff){
      engineKill(); 
      brakesEngage(); 
      whatTimeIsIt = 0; 
      timedOut = false;
      firstTimeOff = false;
      firstTimeOn = true;
    } // end if(firsTimeOff)
  } // end if() transmitter not ready
  
  if(ServoDecode.getState()== READY_state) { // transmitter is on
    
    if (firstTimeOn){
      firstTimeOn = false;
      firstTimeOff = true;
      engineAllow();
      brakesEngage();
      timedOut = false;
    } //enf if() firstTimeOn
    
    if(!userInput() && !timedOut){
      if(firstInput){// test if first entry into this path
        userInputTimer = millis(); // engage a timer
        firstInput = false;
      } //end firstInput
      
      if ( (millis() >= (userInputTimer + idleBrakesEngage)) && !firstTimeOn ){ // if idleBrakesEngage seconds has passed since no user input engage the brakes
        brakesEngage();
        // userInputTimer = 0;
      } // end  
      
      if ( (millis() >= (userInputTimer + idleEngineKill)) && !firstTimeOn ){ // if idleEngineKill seconds has passed since no user input, kill the motor and engage the brakes
        engineKill();
        brakesEngage();
        userInputTimer = 0;
        firstInput = true;
        timedOut = true;
      } // end 
      
    } // end if no input
    
    if(userInput()){
      userInputTimer = 0;
      firstInput = true;
      brakesDisengage();
      timedOut = false;
    } // end if() userInput()
    
  } // end if() transmitter ready
  
} // end main loop()

void engineKill(){
  
  if(relay1State == relayOn){ // just in case it was on for some random reason
    digitalWrite(relay1, relayOff); // prevents frying relays and/or actuators
    relay1State = relayOff;
  } // end safety check
  
  if(relay2State == relayOff){ // if it's already on, no need to do anything, so only do something if its off
    relay2State = relayOn;
    pulseOutLow(relay2, 500);
    relay2State = relayOff;
  } // end if()
  engineShouldBeOff = true;
  //digitalWrite(relay2, relayOn); // eventually pulse it, but for now just latch it
} // end engineKill

void engineAllow(){
  if(relay2State == relayOn){
    digitalWrite(relay2, relayOff); // just in case it was on for some random reason; prevents frying relays and/or actuators
    relay2State = relayOff;
  } // end safety check
  
  if(relay1State == relayOff){ // if it's already on, no need to do anything, so only do something if its off
    relay1State = relayOn;
    pulseOutLow(relay1, 500);
    relay1State = relayOff;
  } // end if()
  engineShouldBeOff = false;
  //digitalWrite(relay1, relayOn); // eventually pulse it, but for now just latch it
} // end instantEngineKill

void brakesEngage(){
  
  if(relay3State == relayOn){ // if it's already onn, no need to do anything, so only do something if its off
    digitalWrite(relay3, relayOff); // engages the brakes by removing power, allowing the mower to move
    relay3State = relayOff;
  } // end if()
  brakesShouldBeOff = true;
} // end brakesEngage

void brakesDisengage(){
  
  if(relay3State == relayOff){ // if it's already on, no need to do anything, so only do something if its on
    digitalWrite(relay3, relayOn); // disengages the brakes by applying voltage
    relay3State = relayOn;
  } // end if()
  brakesShouldBeOff = false;
} // end brakesDisengage

void pulseOutLow(int pin, long duration){ // pulses a digital output for a certain length of time, meant for active low relays
  unsigned long timeStart = millis();
  boolean exitThis = false;
  digitalWrite(pin, LOW);
  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
      exitThis = true;
    } // end if
  } // end while()
} // end pulseOutLow

void pulseOutHigh(int pin, long duration){ // pulses a digital output for a certain length of time
  unsigned long timeStart = millis();
  boolean exitThis = false;
  digitalWrite(pin, HIGH);
  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, LOW);
      exitThis = true;
    } // end if
  } // end while()
} // end pulseOutHIGH

boolean userInput(){ // tests for user input via the transmitter
  boolean returnVal = false;
  boolean curPrevChange[arraySize]; // used an array to test for ignore channel.  May not be needed but makes it simpler
  
  // move current values to previous array, sample current values, populate current array
  for ( int i = 1; i <= numChannels; i++ ){
    prevVal[i] = curVal[i];
    curVal[i] = ServoDecode.GetChannelPulseWidth(i); // GetChannelPulseWidth technically requires a byte, but the number won't get high enough to cause problems
  } // end move old values to proper variable
  
     // Compute if there was a change
  for ( int i = 1; i <= numChannels; i++ ){
    if(i==throttleChannel){
      curPrevChange[i] =  abs(curVal[i] - 1500) > chChangeThreshold;
      // curPrevChange[i] = false; // set to false b/c this is excluded from calculations
    } // end if      
    else{ curPrevChange[i] =  abs(curVal[i] - prevVal[i]) > chChangeThreshold;  }
         //end else
    returnVal = returnVal | curPrevChange[i]; // if anything changes, the value will go true
    } // end for() of compare channel values from each array and populate curPrevChange array
  return returnVal;
} // end userInput

It had been a while since I last worked on this, so my statement of activating a timer for 2 - 10 seconds was kind of misleading. I need to pulse the outputs for about 500 ms. The 2 - 10 second measurements are made using a polling timer as suggested.

Can you create a function inside of loop() & reference that function within loop()?

Yes you can. No matter where you are the value of millis() returned comes from the same place.

Turns out it stops the program just like a delay()

Only if you are (locally) looping to wait for the time to expire in the loop, which is what you are doing. Then you may as well use delay().

To state the obvious, your code:

  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
      exitThis = true;
    } // end if
  } // end while()

needs to change to this:

    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
    } // end if

The while statement blocks you so that you cannot do anything else. The whole while with millis() thing is just like delay().

The structure of your code looks like it could benefit from you getting familiar with Finite State Machines, as your application seems to be based on doing a sequence of things followed by waiting for an external input that changes the current state and what needs to be 'done' before moving to a new state. The easiest way to implement these are with case statements and you should be able to reuse a lot of your existing code. Google the term or search these forums as there is always a lot of discussion on FSM.

Edit:
Here is a link by majenko http://hacking.majenko.co.uk/finite-state-machine

Not sure, but maybe this will help you?

unsigned long elapsed;
bool pinstate;

void InvertState(unsigned long duration)
{
  if (millis() - elapsed >= duration)
  {
    pinstate = !pinstate;
    elapsed = millis();
  }

  print(pinstate ? ("I'm HIGH") : ("I'm LOW"));
}

void loop()
{
  InvertState(500);
}

Edit: Thanks Coding Badly and PaulS, I have some difficulties with all those data types.. :slight_smile:

guix:
unsigned int elapsed;
...
void InvertState( unsigned int duration )
...

Actually, int should be long.

marco_c:

Can you create a function inside of loop() & reference that function within loop()?

Yes you can. No matter where you are the value of millis() returned comes from the same place.

Let me give a pseudocode example of what I meant; I'm not sure we're talking about the same thing:

loop(){
     myFunctionOne(){
          do something whiz-bang;
     }

     myFunctionTwo(arg1, arg2){
          do something else;
     }

     if(blah){
          myfunctionOne();
     }

     else myFunction2(arg1, arg2);

} // end loop()

If the above would work, that would be great, but I don't think it will. If it does, then that may nicely solve my problem. I could put my functions within the loop. I suppose I'd have to maintain the variables outside of the function though so they don't get overwritten on every new trip through the loop().

Turns out it stops the program just like a delay()

Only if you are (locally) looping to wait for the time to expire in the loop, which is what you are doing. Then you may as well use delay().

To state the obvious, your code:

  while(!exitThis){

if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
      exitThis = true;
    } // end if
  } // end while()




needs to change to this:


if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
    } // end if




The while statement blocks you so that you cannot do anything else. The whole while with millis() thing is just like delay().

At first I tried the code that you suggested (literally, no joke!) and it did not work the way I wanted. After thinking about it, I thought that since it was outside of loop(), it would not keep testing, it would only test once. Obviously the first test will not pass because enough time has not elapsed. Eventually I made it work with the while() loop, but it's not quite the way I want it to work. The while() loop is now a stopgap measure, and eventually I realized I had recreated delay() (Did I reinvent delay() w/ a while() loop while trying to make a pulse function? - Programming Questions - Arduino Forum) I think there might be a way to implement a poll for that function within loop(), but the function is 2 levels removed from loop(), so I'd have to pass variables through 2 functions, and that doesn't sound all that good in theory. I'll check out that article on state machines before I rewrite all of my code. Any idea why your suggestion might not have worked like we both thought it would?

The structure of your code looks like it could benefit from you getting familiar with Finite State Machines, as your application seems to be based on doing a sequence of things followed by waiting for an external input that changes the current state and what needs to be 'done' before moving to a new state. The easiest way to implement these are with case statements and you should be able to reuse a lot of your existing code. Google the term or search these forums as there is always a lot of discussion on FSM.

Edit:
Here is a link by majenko http://hacking.majenko.co.uk/finite-state-machine

Thanks for pointing me in that direction, I'll definitely check it out. I'm new to AVR/Arduinos; mostly programmed on PCs with a sprinkling of other systems.

I don't understand why you think you need to define the functions inside loop(). You can call them from loop(), but they should be defined outside of any function.

PaulS:
Actually, int should be long.

Edit: OK

Edit2: You may be interested studying the code of the SimpleTimer library Arduino Playground - SimpleTimer Library , especially the function setTimeout :slight_smile: