Go Down

Topic: Which timer library to use for several seconds of delay (Read 2686 times) previous topic - next topic

allanonmage

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: http://arduino.cc/forum/index.php/topic,117414.msg883388.html#msg883388
http://arduino.cc/forum/index.php/topic,116616.msg878736.html#msg878736
http://arduino.cc/forum/index.php/topic,114733.msg863416.html#msg863416

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?

PaulS

Quote
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.

Quote
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().


CrossRoads

Why use a library at all?
all time related variables are unsigned long
Code: [Select]

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


Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

marco_c

#3
Oct 08, 2012, 03:28 am Last Edit: Oct 08, 2012, 06:05 am by marco_c Reason: 1
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.
Arduino libraries http://arduinocode.codeplex.com
Parola for Arduino http://parola.codeplex.com

allanonmage

#4
Oct 08, 2012, 04:16 am Last Edit: Oct 08, 2012, 04:22 am by allanonmage Reason: 1
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?

Code: [Select]
#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



allanonmage

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.

marco_c

#6
Oct 08, 2012, 06:02 am Last Edit: Oct 08, 2012, 07:53 am by marco_c Reason: 1
Quote
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.

Quote
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:
Code: [Select]
  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
      exitThis = true;
    } // end if
  } // end while()


needs to change to this:
Code: [Select]
    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
Arduino libraries http://arduinocode.codeplex.com
Parola for Arduino http://parola.codeplex.com

guix

#7
Oct 08, 2012, 07:06 am Last Edit: Oct 08, 2012, 04:55 pm by guix Reason: 1
Not sure, but maybe this will help you?
Code: [Select]

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.. :)

Coding Badly

[font=Courier New]unsigned int elapsed;
...
void InvertState( unsigned int duration )
...
[/font]

PaulS


[font=Courier New]unsigned int elapsed;
...
void InvertState( unsigned int duration )
...
[/font]


Actually, int should be long.

allanonmage


Quote
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:

Code: [Select]

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().


Quote
Quote
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:
Code: [Select]
  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
      exitThis = true;
    } // end if
  } // end while()


needs to change to this:
Code: [Select]
    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() (http://arduino.cc/forum/index.php/topic,117414.msg883388.html#msg883388)  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?

Quote

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.

dxw00d

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.

guix

#12
Oct 08, 2012, 04:48 pm Last Edit: Oct 08, 2012, 09:21 pm by guix Reason: 1

Actually, int should be long.


Edit: OK

Edit2: You may be interested studying the code of the SimpleTimer library http://www.arduino.cc/playground/Code/SimpleTimer#GetTheCode , especially the function setTimeout :)

Go Up