Call function in parallel of loop

Status
Not open for further replies.

guiguibud

Member
Hello Guys,

I just received my teensy 2 and I'm super excited to experience with it! This is really cool stuff...

I have a small questions. I'm trying to use the T2 as a keyboard with two buttons. Every time a button is pressed, it triggers an action on the computer (I already have that part working through vb .net).

All I need now is two send a specific set of keys for each button pressed. That works well through the following code :

Code:
int ledPin1 =  11;
int ledPin2 =  11;
int buttonPin1 = 0;
int buttonPin2 = 1;


void setup() {
    pinMode(ledPin1, OUTPUT);       // LED
    pinMode(ledPin2, OUTPUT);       // LED
    pinMode(buttonPin1, INPUT_PULLUP); // Pushbutton
    pinMode(buttonPin2, INPUT_PULLUP); // Pushbutton
  }


void loop()
{
  if (digitalRead(buttonPin1) == LOW) {
    Keyboard.set_modifier(MODIFIERKEY_SHIFT | MODIFIERKEY_CTRL | MODIFIERKEY_ALT);
    Keyboard.set_key1(KEY_N);
    Keyboard.send_now();

    // release all the keys at the same instant
    Keyboard.set_modifier(0);
    Keyboard.set_key1(0);
    Keyboard.send_now();
    
    //Led on
    digitalWrite(ledPin1, HIGH);   // LED on
    delay(1000);                   // Fast blink
     // Led off
    digitalWrite(ledPin1, LOW);
  } 

 if (digitalRead(buttonPin2) == LOW) {
    Keyboard.set_modifier(MODIFIERKEY_SHIFT | MODIFIERKEY_CTRL | MODIFIERKEY_ALT);
    Keyboard.set_key1(KEY_P);
    Keyboard.send_now();

    // release all the keys at the same instant
    Keyboard.set_modifier(0);
    Keyboard.set_key1(0);
    Keyboard.send_now();
    
    //Led on
    digitalWrite(ledPin2, HIGH);   // LED on
    delay(1000);                   // Fast blink
     // Led off
    digitalWrite(ledPin2, LOW);
  } 


}

As you can see, I'm instructing to light a led for 1 second when a press is detected and I send a combination of keys to the computer.

Now, if the second button is pressed during that second, then nothing will happen.

Thus the question, is it possible to call an outside function for the led to light for one second, but in parallel continue the loop?

Thanks a lot for your help!


Cheers
 
Hi there,

I think what you want to do is handle the button presses with interrupts rather than by polling for status. With interrupts, a button press can trigger a function (interrupt handler) to run, which can either turn your LED on/off itself OR set some variable(s) for your main loop to act on. See this relevant guide from Sparkfun for more details:

http://www.sparkfun.com/tutorials/326

The Teensy 3 pin diagram says all digital pins have interrupt capability, so you should be able to correspond each button you want to handle to a pin with an interrupt handler attached, but I haven't tested this. It seems you aren't doing any software debouncing of the button presses, so if you're not already doing it on the hardware side, you might want to especially pay attention to the details of how they do it in software.

Hope that helps.
 
Sorry, I've just noticed you said Teensy 2, not 3. If that's the case, I believe you'll only be able to attach interrupts to one of the pins labeled INTn in the diagram for whichever Teensy (2 or 2++) you are using:

http://www.pjrc.com/teensy/pinout.html

Thank you so much! I'm really impressed, such a quick reply!

I've done exactly has you suggested and it works wonders...

For reference and in case somebody else is wondering, here's my code...

Cheers


Code:
int ledPin =  11;

int buttonPin = 7;
int lighton = 0; //used to monitor multiple interrupts

int leddelay = 1000; //delay to keep the light on


//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;  
unsigned long last_button_time = 0; 


void setup() {                
  //enable interrupt 0 (pin 2) which is connected to a button
  //jump to the increment function on falling edge
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(2, sendkeys, FALLING);
  pinMode(ledPin, OUTPUT);
  
}

void loop() {
  //digitalWrite(ledPin, LOW);
 
  if (lighton > 0) {
     digitalWrite(ledPin, HIGH);
     lighton=0;
     delay(leddelay);

     }
  
  if (lighton == 0) {
     digitalWrite(ledPin, LOW);
      }
     

  
}

// Interrupt service routine for interrupt 2
void sendkeys() {
  button_time = millis();
  //check to see if increment() was called in the last 250 milliseconds
  if (button_time - last_button_time > 250)
  {
    //Keyboard.set_modifier(MODIFIERKEY_SHIFT | MODIFIERKEY_CTRL | MODIFIERKEY_ALT);
    Keyboard.set_key1(KEY_P);
    Keyboard.send_now();
     // release all the keys at the same instant
    Keyboard.set_modifier(0);
    Keyboard.set_key1(0);
    Keyboard.send_now();
 
    lighton++;
    last_button_time = button_time;
  }


    
}
 
Glad that worked. However, it looks like you haven't yet extended this to two buttons/keys. You may encounter issues doing that since you are still using a delay() in your main loop, assuming you want a separate LED for each button. For example, consider what will happen if press two buttons in quick sequence - you'll notice the first LED turn on for a second, and then the second LED turn on a second later even though you pressed the buttons nearly at the same time. Even if you move the delay() to the end of loop(), the basic problem is that you are still using it. A common trick is to convert your delays into a counter of how much time has elapsed to determine when to perform an action, aka BlinkWithoutDelay. Here's pseudocode for what might work better:

Code:
pins = [p1, p2, ...] // pins handling presses for each button
ledOn = [t1, t2, ...] // LED on times corresponding to the buttons you have
lastPressed = [0, 0, ...] // one for each button you have

setup() {
  attachInterrupt(p1, f1);
  attachInterrupt(p2, f2);
  ...
}

loop() {
  t = millis();
  for each button b in 1..n:
    if (t - lastPressed[b]) > ledOn[b]:  // the led has been on long enough
       turn led b off
}

f1() { turn led 1 on; lastPressed[0] = millis(); }
f2() { turn led 2 on; lastPressed[1] = millis(); }
...

I wrote the global variables as arrays for conciseness, but you may want to break them out into separate variables if you just have a few buttons. I may not have thought this through 100%, but in principle I think getting rid of delays is your best bet. They will block other things from happening when you want them to.
 
Last edited:
Glad that worked. However, it looks like you haven't yet extended this to two buttons/keys. You may encounter issues doing that since you are still using a delay() in your main loop, assuming you want a separate LED for each button. For example, consider what will happen if press two buttons in quick sequence - you'll notice the first LED turn on for a second, and then the second LED turn on a second later even though you pressed the buttons nearly at the same time. Even if you move the delay() to the end of loop(), the basic problem is that you are still using it. A common trick is to convert your delays into a counter of how much time has elapsed to determine when to perform an action, aka BlinkWithoutDelay. Here's pseudocode for what might work better:

Code:
pins = [p1, p2, ...] // pins handling presses for each button
ledOn = [t1, t2, ...] // LED on times corresponding to the buttons you have
lastPressed = [0, 0, ...] // one for each button you have

setup() {
  attachInterrupt(p1, f1);
  attachInterrupt(p2, f2);
  ...
}

loop() {
  t = millis();
  for each button b in 1..n:
    if (t - lastPressed[b]) > ledOn[b]:  // the led has been on long enough
       turn led b off
}

f1() { turn led 1 on; lastPressed[0] = millis(); }
f2() { turn led 2 on; lastPressed[1] = millis(); }
...

I wrote the global variables as arrays for conciseness, but you may want to break them out into separate variables if you just have a few buttons. I may not have thought this through 100%, but in principle I think getting rid of delays is your best bet. They will block other things from happening when you want them to.

OK, you're in my living room right??? You have to be looking above my shoulder! that's exactly the problem I ran into!

Thanks for anticipating. I will try to implement your solution (which makes a lot of sense!)
 
You might also look at the example in File > Examples > Teensy > USB_Keyboard > Buttons. It reads 10 buttons and prints messages when they change.

There's a data type called "elapsedMillis" which is basically the same as storing the previous millis(), but it's much easier to use. It just appears as a variable which automatically increments. You could use that to track how long the LED has been on. Details on this page:

http://www.pjrc.com/teensy/td_timing.html
 
Thanks a lot guys. I'm going to try to implement both elapsedMillis and arrays to make the code more simple.

For elapsedMillis, I'm fairly clear and my inital tests work perfectly.

Now, I still have one question that is not vital (basically the code works without the arrays): How do you create an array of elapsedMillis? It's a shame that I put everythin in arrays, but still need to reference elapsedMillis1, elapsedMillis2...

Thanks again for your help. Once I have this sorted, I'll post the code here... I'm the first one to build on code portions found through our friend google!

Cheers
 
Thanks to all of you. Everything is working well now!

I just have one question, but that may be build in functionality. I setup interrupts on INT2 and INT3 on the teensy 2. The problem is, every interrupt on INT2 also triggers the one on INT3. Is that related to RX / TX being linked?

For reference and in case anybody is interested, here's my code. Fell free to use it! It's a bit complicated as I have several timers to allow for two things :
- I only record one button push every 5 seconds (to avoid people from pushing the button like crazy
- I have 250ms between interrupts to prevent from launching it several times
- Everytime an interrupt triggers, it will turn on the led. The idea is that if someone presses a button like crazy, then the led will stay one for 1 second after the last push. And given my first point, I will only record it once...

All working well except this INT2 / INT3 thing mentioned above!

Code:
int ledPin[]  = {16, 15, 14, 12}; //arduino led pin
int intPin[]  = {0, 1, 2, 3}; //interrupt code, not arduino pin
int buttonPin[] = {5, 6, 7, 8}; //button code, actual arduino pin
int pinCount = 4; //number of pins and buttons
int keystopress[] = {KEY_P, KEY_N, KEY_B, KEY_C}; // keys pressed in computer
int buttonnames[] = {100, 200, 300, 400}; // name to give to buttons while sending info
//delay to keep the light on after button pressed
int leddelay = 1000; 



//variables to keep track of the timing of recent interrupts and time between interrupts to ignore
// this is to avoid having interrupts multiple times for one single press
elapsedMillis sincepressed[4];
int interrupt_time = 250;  

//variable to allow only one press every 5 seconds
int mintimebetween = 5000;

// create elapsedMillis outside loop(), to
// retain its value each time loop() runs.
int since_led_on[] = {0, 0, 0, 0}; 
int t = 0;




void setup() {                
 //init serial
 Serial.begin(9600); // USB is always 12 Mbit/sec
 

 
  //setup pins and buttons
 for (int thisPin = 0; thisPin < pinCount; thisPin++) { 
  pinMode(buttonPin[thisPin], INPUT_PULLUP);
  
  pinMode(ledPin[thisPin], OUTPUT);
 }
 attachInterrupt(intPin[0], sendkeys0, FALLING);
 attachInterrupt(intPin[1], sendkeys1, FALLING);
 attachInterrupt(intPin[2], sendkeys2, FALLING);
 attachInterrupt(intPin[3], sendkeys3, FALLING);

Serial.println(ledPin[2]);

}



// Interrupt service routine for interrupt of button 1
void processkeys(int val) {
  //check to see if increment() was called in the last x milliseconds (defined by variable interrupt_time)
  if (sincepressed[val] > interrupt_time)
  {
     
    //Can't press a button several times in less than 5 seconds
    if (sincepressed[val] > mintimebetween)
      { 
    
    
    //if this is true (actual button push)
    //then we send keys and turn on the led
    
    Keyboard.set_modifier(MODIFIERKEY_SHIFT | MODIFIERKEY_CTRL | MODIFIERKEY_ALT);
    Keyboard.set_key1(keystopress[val]);
    Keyboard.send_now();
    // release all the keys at the same instant00
    
    Keyboard.set_modifier(0);
    Keyboard.set_key1(0);
    Keyboard.send_now();
    
     
    //send to serial
    Serial.print("hop;");
    Serial.println(buttonnames[val]);
    
    //reset interrupt timing
    sincepressed[val] = 0;

    }
    //turn on led
    digitalWrite(ledPin[val], HIGH);
    //reset led timer to make sure it stays on for the delay after the last push
    since_led_on[val] = millis();
    
  }
}


void sendkeys0() {
  processkeys(0);
}
void sendkeys1() {
  processkeys(1);
}
void sendkeys2() {
  processkeys(2);
}
void sendkeys3() {
  processkeys(3);
}


//Main loop
void loop() {


  //Check how long led 2 has been on
 t = millis();
 for (int thisPin = 0; thisPin < pinCount; thisPin++) { 
 if ((t - since_led_on[thisPin]) > leddelay) {
   since_led_on[thisPin] = since_led_on[thisPin] - leddelay;
    digitalWrite(ledPin[thisPin], LOW);
  }
  }
  
  //protect overflow of elapsedMillis just set it to a lower value
   for (int thisPin = 0; thisPin < pinCount; thisPin++) {
     if (sincepressed[thisPin] > 999999) {
      sincepressed[thisPin] = mintimebetween + 1;
     }
   }
}
 
Status
Not open for further replies.
Back
Top