Josh Wisotzkey
Josh Wisotzkey

Reputation: 108

Arduino interrupt alternatives

From what I've read, the solution to my problem is to use an interrupt, but if I understand them correctly, I can't use a delay in the routine that gets called by the interrupt. I've got a large pushbutton LED switch. I want it to have a heartbeat while sitting idle, but once it's pushed, stay green and execute code.

I can break the heartbeat() if I push the button enough times (I assume getting the state change at just the right time as it finishes a loop of the heartbeat), but I'm stuck on how to make it work on the first click. Is there an alternative way to do what I'm attempting?

void loop(){

heartbeat();                                    //Make LED beat.
buttonVal = digitalRead(buttonPin);             //Check the button.
    if (buttonVal != buttonState) {               //If the button state changed.
        if (buttonVal == HIGH){                   //Check if the button is pressed.
            analogWrite(greenPin, 255);         //Button stays green once pushed.
            functionA                           //Has some delays in it.
            functionB                           //Has some other delays.
        }
    }
}

void heartbeat(){
    for(i = 0; i < pmw; i++) {
        analogWrite(greenPin,i);
        delay(((60000/rate)*.1)/pmw);
    }

    for (i = pmw; i > 0; i--){
        analogWrite(greenPin,i);
        delay(((60000/rate)*.2)/pmw);
    }

    for(i = 0; i < pmw; i++) {
        analogWrite(greenPin,i);
        delay(((60000/rate)*.1)/pmw);
    }

    for (i = pmw; i > 0; i--){
        analogWrite(greenPin,i);
        delay(((60000/rate)*.6)/pmw);
    }
 }

Upvotes: 1

Views: 5907

Answers (3)

hosenlander
hosenlander

Reputation: 1

the Arduino can only have 2 interrupts using attachInterrupt indeed but other boards like the Arduino Mega / Due / ... have many more pins avaliable for interrupts with attachInterrupt. On top of that, if you use direct register writes (low level operations) instead of attachInterrupts, you can use any digital input on the Arduino Uno for interrupt handling. See https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/ for a good explanation of the pin change interrupts and https://ukmars.org/projects/ukmarsbot/developer-notes/encoders-on-ukmarsbot/ for info on the external interrupts. This direct method is also faster than attachInterrupt so I could use it with an Uno to count up to 100kcounts/sec for a quadrature encoder input while doing other stuff like pid loops / serial communication at the same time. That would not be possible with polling digital lines in the main loop.

Upvotes: 0

Sergio Internicola
Sergio Internicola

Reputation: 21

I'm a professional programmer, but I'm new to Arduino world. I noted that many Arduino owners don't know the fundamentals of programming, and cannot achieve good results in using this amazing piece of technology.

In particular, I want to suggest to not use Arduino interrupts if it's not necessary, because there are only two of them, and even if you can write a "beautiful" code for a single sensor, or actuator, when you have to implement a more complex project, you cannot use them. Surely you've been correct in using the interrupt to deal with a single button, but what if you have four buttons to manage?

For this reason I rewrote the "heartbeat" sketch using the technique of "time slice" or "single step". Using this technique, each time the loop function is executed, you run only a "single step" of your control functions, so you can insert as many as you need, and be equally quick and responsive to all the sensors you're using. To do so, I use global counters for each control function I have to implement, and each time the loop function is executed, I execute a single step of each function. This is the new sketch:

// Interrupt.ino - this sketch demonstrates how to implement a "virtual" interrupt using
// the technique of "single step" to avoid heavy duty cycles within the loop function.

int maxPwm = 128;   // max pwm amount
int myPwm = 0;      // current pwm value
int phase = 1;      // current beat phase
int greenPin = 11;  // output led pin
int buttonPin = 9;  // input button pin
int buttonFlag = 1; // button flag for debounce

int myDir[] = {0,1,-1,1,-1}; // direction of heartbeat loop
int myDelay[] = {0,500,1000,500,3000}; // delay in microseconds of a single step

void setup()
{
   pinMode(buttonPin, INPUT); // enable button pin for input
   // it's not necessary to enable the analog output
}

void loop()
{
   if(phase>0) heartbeat(); // if phase 1 to 4 beat, else steady
   buttonRead(); // test if button has been pressed
}

// heartbeat function - each time is executed, it advances only one step
// phase 1: the led is given more and more voltage till myPwm equals to maxPwm
// phase 2: the led is given less and less voltage till myPwm equals to zero
// phase 3: the led is given more and more voltage till myPwm equals to maxPwm
// phase 4: the led is given less and less voltage till myPwm equals to zero
void heartbeat() 
{
   myPwm += myDir[phase];
   analogWrite(greenPin, myPwm);
   delayMicroseconds(myDelay[phase]);
   if(myPwm==maxPwm||myPwm==0) phase = (phase%4)+1;
}

// buttonRead function - tests if the button is pressed; 
// if so, forces phase 0 (no beat) and enlightens the led to the maximum pwm
// and remains in "inoperative" state till the button is released
void buttonRead()
{
   if(digitalRead(buttonPin)!=buttonFlag)   // if button status changes (pressed os released)
   {
      buttonFlag = 1 - buttonFlag; // toggle button flag value
      if(buttonFlag) // if pressed, toggle between "beat" status and "steady" status
      {
         if(phase) myPwm = maxPwm; else myPwm = 0;
         phase = phase==0;
         analogWrite(greenPin, myPwm);
      }
   }
}

As you can see, the code is very compact and fast to execute. I divided the heartbeat loop into four "phases", regulated by the myDelay array, and whose direction of counting is regulated by the myDir array. If nothing happens, the voltage of the pwm led pin is incremented at each step until it reaches the maxPwm value, then the loop enters phase 2, in which the voltage is decremented until zero, and so on, implementing the original heartbeat.

If the button is pressed, the loop enters in phase zero (no heartbeat), and the led is alimented with maxPwm voltage. From now on, the loop maintains a steady led, until the button is released (this implements the "debounce" algorithm). If the button is pressed again, the buttonRead function restarts the heartbeat function, making it enter the phase 1 again, so the heartbeat is restored.

If you press the button again, the heartbeat stops, and so on. All without any exhitation or bouncing at all.

Upvotes: 2

embedded.kyle
embedded.kyle

Reputation: 11466

You are correct in most of your assumptions. The proper way to handle this is using an interrupt and it is not a good idea to have delays in your interrupt service routines (ISR). So what you want to do is set a flag in your ISR and check that flag in your main loop.

// Flag needs to be volatile if used in an ISR
volatile int buttonFlag = 0; 

void loop()
{
    if (buttonFlag == 0)
    {
        heartbeat();                        //make led beat
    }
    else
    {
        analogWrite(greenPin, 255);         //button stays green once pushed
        functionA                           //has some delays in it
        functionB                           //has some other delays
        buttonFlag = 0;                     //clear flag after executing code
    }

}

// Interrupt Service Routine attached to INT0 vector
ISR(EXT_INT0_vect)
{
    buttonFlag = digitalRead(buttonPin);    //set flag to value of button
}

Since the interrupt will only trigger on a change in the state of the button, you don't need to check for that.

Make sure that your flag variable is global and is declared volatile for use in the ISR. And make sure you are using the correct interrupt vector for use with the pin you are using.

Here is a good tutorial on Arduino interrupts. And here is another good example of what you're trying to do.

You may also want to look into debouncing your switch presses depending on what type of switch you're using. If instead of missing the first press, you're getting too many presses, you will need to implement some type of debouncing.

Upvotes: 5

Related Questions