PDA

View Full Version : Call function in parallel of loop



guiguibud
11-05-2012, 07:03 PM
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 :


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

indraastra
11-05-2012, 07:42 PM
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.

indraastra
11-05-2012, 07:48 PM
I just received my teensy 2 and I'm super excited to experience with it! This is really cool stuff...


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

guiguibud
11-05-2012, 08:52 PM
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



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;
}



}

indraastra
11-05-2012, 09:35 PM
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 (http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay). Here's pseudocode for what might work better:


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.

guiguibud
11-05-2012, 10:56 PM
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 (http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay). Here's pseudocode for what might work better:


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!)

Paul
11-06-2012, 01:55 AM
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

indraastra
11-06-2012, 07:51 PM
Wow, I didn't know about that but it will make life simpler. Thanks!

guiguibud
11-06-2012, 08:25 PM
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

indraastra
11-06-2012, 10:42 PM
You can instantiate an array of them just like any other array: elapsedMillis foo[10]; (I just tested this to be sure)

guiguibud
11-09-2012, 07:25 PM
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!


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;
}
}
}