Go Down

Topic: timing without using delay (Read 1 time) previous topic - next topic

CTP

I wrote the code for a piece of art I just built in a big hurry (i.e. just hours before the gallery doors opened).  The easiest way for me to create the timings I wanted between events was with delay statements.  Trouble is I really need to implement interrupts in the code, and I can't use delay in my interrupt handling functions.  So what I am looking for is general advice in the ways one could create the timing they desire without the use of the delay command.

Thanks in advance for any advice.

-CTP

mrmeval

I can point here http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay

You may get better results if you post your code.
If it was designed by man it can be repaired by man.

CTP

#2
Dec 06, 2007, 02:29 am Last Edit: Dec 06, 2007, 02:30 am by CTP Reason: 1
yea, but I'm looking more for how to fish than people re-writing my fish.  How's that for a mixed metaphor? :-D

It's more a generic question that's been on my mind, i.e. learning all the possible ways to create delays without using delay(), than just this one case on this one piece.

Oh and in that example you link to - it uses millis...Can't use millis with interrupts either :-(

mem

The arduino atmega chip has a mind bending number of permutations for using  timers and interrupts to create delays. The following tutorial may help you see some of the possibilities: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106

Knowing a little more about the kind of application you want would help pointing you in the right direction. If you say a little more about the functionality you require, it would be easier to provide relevant guidance.

westfw

Quote
Can't use millis with interrupts either

Why do you think that?

mellis

When you're an interrupt handler (i.e. one you registered with attachInterrupt()) other interrupts are disabled (since the core doesn't enable nested interrupts) so the timer 0 overflow interrupt that increments the value returned by millis() stops.  You can still call millis(), but it will always give the same answer as long as you're in the interrupt handler.  I think.  :)

mem

Because well behaved interrupt handler code should only be active for a relatively short period of time, the other interrupts will still function. In the case cited, as long as other interrupt handlers use less then a millisecond, the millis() handler will be ready to do its stuff when the timer0 reaches one millisecond.

Typically, interrupt handlers used for timing will do little more then checking and perhaps changing a few clock registers and updating a variable or two that is shared with the non interrupt code. Think of the interrupt as little more then a trigger with most of the time intensive work done outside the handler.

If the OP wants to do a lot timed processing that overlaps, then he may want to look at using something like a state machine that is sequenced by timer events.

kg4wsv

Interrupt service routines (ISRs) should be very short chunks of code, to avoid problems like those being discussed now.

For example, I use interrupts in my DTMF decoder sketch.  The entire ISR is

Code: [Select]
void dtmf_isr()
{
 tone_detected = 1;
}


In my loop() function, I check this variable each time through and act on it if needed.  This way, other interrupts will still function correctly.

-j


CTP

#8
Dec 07, 2007, 12:08 am Last Edit: Dec 07, 2007, 12:24 am by CTP Reason: 1
You all make some good points, so here is my code (up until now).  Before you read it let me describe what is going on - the viewer approaches the piece (a "genie") and places their beverage into its hand.  The weight of the drink activates a lever switch and the Arduino runs the main routine.  Because void loop is running in a loop the piece doesn't react immediately - that is where I want to use the first interrupt.  Once the "main show" function is running, I want the removal of the drink to send execution back to void loop.  The code you see here is what I wrote in the gallery with little time to spare before the show opening - and having been awake for 28 hours to boot.  The eventual program will have more action - but I want to try to incorporate those interrupts before I head down that path.  Thanks for looking!  

Pix to help explain the thing - http://www.flickr.com/photos/shifzr/2055471345/ - http://www.flickr.com/photos/hackaday/2055635032/


int inPin = 7;   // lever switch connected to pin 7
int val = 0;     // switch read value

/*
Lynxmotion channels:
2 - fortune card servo
7 - eyes servo
11 - head servo
15 - jaw servo
16 - chamber LED
17 - status check LED
19 - status check LED
22 - panel LED
24 - panel LED
26 - panel LED
28 - hand LED green
29 - hand LED red
serial Rx

Arduino channels:
1 - serial Tx to Lynxmotion
7 - arm switch
*/


void setup()
{
 Serial.begin(9600);
 pinMode(inPin, INPUT);      // sets the digital pin 7 as input
}


void loop()  //loop contains default resting behavior
{
 if (!digitalRead(inPin))   // read the arm switch, if not down run default
 {
 Serial.println("#17H #19L #24L #22L #25H #26L #28H #29L"); //initialize LEDs
 Serial.println("#2 P1900 S350"); //slowly return fortune servo
 Serial.println("#7 P1500 #15 P1500 #16L"); //eyes slow, mouth closed
 Serial.println("#11 P600 S200 #17H #19L"); //head slow
 delay (2000);
 Serial.println("#11 P1600 S150"); //head slow
 delay (2000);
 }
 else //if arm is down run the following function
 {
 run_main_show();
 }
}


void run_main_show()
 {
 Serial.println("#7 P1520 #15 P600 #11 P600 #17L #19H #28L #29H");  //eyes stop, head back, mouth closed
 delay (3000);
 Serial.println("#7 P1400"); //move eyes fast
 Serial.println("#11 P700 S300"); //head faster
 //Serial.println("#15 P600"); //mouth open
 delay (2000);
 Serial.println("#11 P1300 S300"); //head faster
 Serial.println("#15 P1500"); //mouth closed
 delay (2000);
 Serial.println("#7 P1520 #15 P1500 #11 P600 #24H");  //eyes stop, head back, mouth closed
 delay (4000);
 Serial.println("#15 P600"); //mouth open quick
 delay (150);
 Serial.println("#15 P1500"); //mouth closed quick
 delay (150);
 Serial.println("#15 P600"); //mouth open quick
 delay (150);
 Serial.println("#15 P1500"); //mouth closed quick
 delay (150);
 Serial.println("#15 P600"); //mouth stays open 2 seconds
 delay (2000);
 Serial.println("#16H #24L"); //chamber lights on, panel light off
 Serial.println("#15 P1500"); //mouth closed
 delay (6000);
 Serial.println("#2 P600 S500 #22H #16L"); //fortune kicks first time, fortune panel light on, chamber light off
 delay (1500);
 Serial.println("#2 P1500"); //slowly return fortune servo part way back
 delay (1500);
 Serial.println("#2 P600 S500 #22H"); //fortune kicks second time, fortune panel light on
 delay (2000);
 Serial.println("#2 P1900 S350"); //slowly return card servo
 Serial.println("#26H #22L"); //remove drink light on, fortune light off
 delay (10000);
 
}





mellis

You could replace the calls to delay() with something like:

start = milliis();
while (millis() - start < 2000) {
 if (digitalRead(drinkPin) == LOW) {
   return;
 }
}

which you could #define to something like:

delayReturningIfDrinkRemoved(2000);

What you really want, though, is to define the actions in data, not code, e.g.:

char *main_show_actions[] = {
 "#7 P1520 #15 P600 #11 P600 #17L #19H #28L #29H",
 "#7 P1400",
 // ...
 NULL
};

int main_show_delays[] = {
 3000,
 1000,
 // ...
};

char *loop_actions[] = {
 // ...
};

int loop_times[] = {
 // ...
};

And then have a pointer:

char ***current_actions = &loop_actions;
int **current_times = &loop_times;
int index = 0;

Then, your loop is something like:

void loop() {
 Serial.println((*current_actions)[index]);
 start = millis();
 while (millis() - start < (*current_times)[index]) {
   drinkPinReading = digitalRead(drinkPin);
   if (drinkPinReading != previousDrinkPinReading) {
      previousDrinkPinReading = drinkPinReading;
      index = -1;
      if (drinkPinReading == LOW) {
        current_actions = &loop_actions;
        current_times = &loop_times;
      } else {
        current_actions = &main_show_actions;
        current_times = & main_show_times;
      }
      break;
   }
 }
 index++;
 if ((*current_actions)[index] == NULL) {
   index = 0;
 }
}

CTP

#10
Dec 07, 2007, 02:01 am Last Edit: Dec 07, 2007, 02:20 am by CTP Reason: 1
wow....well that there is a *WHOLE* lot of Arduino for me to chew on, that's for sure.

Good thing it's nearly the weekend :-)

As I read through some of the other responses I get the feeling that I was misinterpreting how attachInterrupt and delay and millis may or may not interact...I will check those assumptions this weekend.


btw - I'm starting to have flashbacks to 20 years ago when I decided to be a sculptor instead of a programmer - and now they are merging...merging like an egg and the sidewalk from 10 stories up ;-)

CTP

Okay, after reading thru I get how (and why) to keep my various actions and such in arrays.

What do these lines do and what is the significance of the splats and ampersands?

char ***current_actions = &loop_actions;
int **current_times = &loop_times;

westfw

"char ***current_actions" is a pointer to the currently used array of actions.  Each action is a string (char *), and an array is pointer to strings (char **), so the pointer to the array is a triple-removed pointer (char ***)  current_actions points to an array, array members point to string, strings point to chars.   This is about the level of indirection where I give up an start defining more complex data structures (action_table *current_actions), but that'll add an additional level of coding complexity if you're not fully C-literate.

Similarly , current_times is a point to an array of ints.

I think the amperstands are redundant (perhaps not in C++); they ensure that you pass the address of the arrays rather than something else.

CTP

Okay - first off this is great stuff, and thanks for all the advice.  

Secondly, though, I have to mention that my niche in teaching microcontrollers is teaching beginners how to program them.  While some of my students could be shown arrays, and how they work, I don;t have the majority of my students for a long enough stretch of time to go there.  Any chance any of you feel like having a crack at this one from other angles?  And not necessarily the tersest or most efficient ways, but some that are easier for the relative newcomer to get.  I'll keep all the examples together to show them of course...options are important.

Thanks again for the help btw!

mellis

Yea, I think westfw is right and I added one level of indirection that didn't need to be there.  Still, this would probably still look confusing.  There's a String library that I've been hoping to add to the distribution that might simplify things a bit.

Go Up