0

i want to program in C a simple sequential control with the raspberry. The current task is: If a button is pressed, something (e.g. a LED) shall be switched on for 5 seconds and then go off itself. But if the button is pressed within the 5s window the LED should shut off immmediatley.

Because I need to do this for about 10 buttons, while also doing other stuff, my first idea was to create a thread for each waiting. This seems fairly easy for the 5second waiting part. But how do I achieve the stopping on the next press of the button?

So far the code is roughly as follows:

#include "wiringPi.h"

#define BUTTON_PIN    15
#define OUTPUT_PIN    7

void buttonInterrupt(void){
    // do some anti jitter

    if (buttonPressedFirstTime){
        piThreadCreate(waitingThread);
    }

    if (buttonPressedSecondTime){
        // do what???
    }
}

PI_THREAD (waitingThread){
    digitalWrite(OUTPUT_PIN,HIGH);
    delay(5000);
    digitalWrite(OUTPUT_PIN_LOW);
    return 0;
}

int main(Void){
    wiringPiSetup();
    pinMode(BUTTON_PIN,INPUT);
    pinMode(OUTPUT_PIN,OUTPUT);

    wiringPiISR(BUTTON_PIN,INT_EDGE_RISING,buttonInterrupt);

    while(1){
        //do other regular stuff
    }

    return 0;
}

But how do I handle the second press of the button?

I also plan to get away from the wiriingPi pthread interface. I will use pthread myself, because this enables me to provide arguments to the started thread. So I could start the thread with the PIN it should use as argument.

But interrupting the thread on the second press of the button and switching the corresponding pin off is not solved, yet. I was thinking of maybe using libev, replacing the delay by a ev_timer. But I am not sure, whether I could skip the rest of the timer on the second button press.

So do you have an idea how to achieve this very simple sequential control? Thank you in advance.

Gunter
  • 101
  • 2
  • 1
    I think using delay is problematic in your app. You should look into using interrupts. – jogco Jan 14 '16 at 07:39
  • Can you give me a reason why? – Gunter Jan 14 '16 at 07:40
  • 1
    First of all, delay stalls the thread, and nothing else happens during that time. You can't detect the second key press. Second, as Linux is a preemptive multitasking system, your app can be switched out and you can not guarantee that light will stay on for 5s. It might be longer. – jogco Jan 14 '16 at 07:43
  • Try to use pthreads. For example, to manage stepper motors I run separate thread that controls timings (code). To make precise pause it's better to use select – Eddy_Em Jan 14 '16 at 08:20
  • And more: you'd better add anti-jitter support into your code. For example, after button press detected make a pause for 30-50ms and after it check button state again. If all OK, run main code. If you don't need precise pauses, you can write code without any threads: just make state machine that would manage N buttons state, store times when LEDs were on, and if current time is more than 5 seconds that some LED time, turn that LED off. – Eddy_Em Jan 14 '16 at 08:25
  • Please, no pthreads for this. As @jogco says you can do this with interrupts (see my answer). WRT jitter/bounce YMMV. – goldilocks Jan 14 '16 at 12:57
  • thanks for the comments. anti jitter is planned, but I skipped it here for the sake of brevity. Stalling the thread with delay is actually, what I planned to do. I want to give a way the waiting into another task, which then is stalled and activated, when needed. @jogco: the second part with switching out, I must admit, I do not understand. – Gunter Jan 14 '16 at 18:19
  • @Eddy_EM: I will think over the state machine part. For sequential control actually an obvious choice, but I thought there might be another option – Gunter Jan 14 '16 at 18:22
  • @Gunter: Maybe "switched out" is a nonstandard term. What I mean is when your process is put on hold by the task scheduler in the kernel in favour for other, perhaps prioritised processes. delay in WiringPi uses nanosleep() for longer delays, read the notes section of "man nanosleep". – jogco Jan 15 '16 at 08:43
  • @jogco thanks for the hint about nanosleep(). I will take this into account and have a look, whether I get any problems with that. As long as the drift is smaller than 1s, it should be fine for my application (the actual delay is longer than 5s...maybe 30 to 60, but I do not know this yet) – Gunter Jan 15 '16 at 19:01
  • @jogco If the light must be on for exactly 5000000 microseconds, that might be an issue. If it simply needs to be on for very close to 5 seconds, it will be fine. Latency with sleeps generally won't exceed 10ms. If you have ever used a multi-tasking OS with a GUI on a single core system, you will notice the interface does not constantly jitter and jerk; everything is smooth most of the time. – goldilocks Jan 15 '16 at 19:31

1 Answers1

1

Yikes, another busy loop for buttons.

This is not the way to deal with them. Button events can be responded to as hardware interrupts. You do not need threads although depending on what else you are doing you may like to use them. I think the best practice it to first consider a single threaded, event based model before you go with the more complex and clumsy multi-threaded one, which often does not solve the "problems" people think it is required to solve.

This is not to say multi-threading is bad, just it is overused.

I also plan to get away from the wiriingPi pthread interface.

You not need a pi specific library such as WiringPi to deal with buttons and LEDs, except to the extent that you want to do slick PWM effects with the latter. I've written an introduction to using the sysfs interface programmatically; although the example is in perl, the original documentation it refers to is for C. It is complete and easily understood if you are familiar with the poll(2) or select(2) system call interfaces.

If you are not familiar with poll() or select() you should be. This is a fundamental feature of the language required for many programming tasks. It is certainly more fundamental than the POSIX threads extension.

As described with reference to the perl example in that other question, the basic idea is you have an infinite loop centered on poll or select which are passive waits. They literally put the process to sleep until one of the things they are listening for happens, then the process is woken up and can deal with the event. You do not need libev or anything other than standard C for this. This is how system daemons and application servers work. It is probably the most widely used programming model of all time.

I mentioned that you cannot do fancy PWM effects with an LED (because the generic sysfs interface does not include hardware PWM), however, you can obviously turn them on or off. You can do simple software PWM well enough to dim an LED, although this would require a separate thread. In this case I would instead recommend WiringPi, pigpio, or libbcm2835 to control the LED, and hardware PWM will again get you out of the extra thread. Note however that the number of hardware PWM channels is limited.

But if what you want to do is wait for buttons and turn LEDs on and off, it is fairly simple to do with standard C in a single thread.

goldilocks
  • 58,859
  • 17
  • 112
  • 227
  • 1
    Yikes, another busy loop for buttons. Are we there yet? Are we there yet? ARE WE THERE YET!? – Jacobm001 Jan 14 '16 at 16:37
  • Actually I do not plan a busy loop for buttons. The original code showed this in a simple comment. But I now made it clear, using wiringPiISR. Busy loops, busy waiting, that's what I want to avoid with the whole structure. @goldilocks: thanks for the hints with the standard sysfs interface. I have to admit, that I still prefer to use wiringPi, because I e.g.also use it for I2C, so it's there anyway. But I will definitely have a look into poll and select...never heard of them in my microcontroller world – Gunter Jan 14 '16 at 18:28
  • If you are using multiple interrupt based callbacks in a single thread this should be pretty straightforward -- I don't know about the potential for one to interrupt another via WiringPi, but in that case you can use mutex locks as if it were multithreaded (looking at the big picture there obviously are concurrent things occurring with the hardware). Except you may want to check that holding a mutex will apply in that case, since it is still the same thread. Perhaps better to look for something explicitly atomic and not thread oriented. – goldilocks Jan 14 '16 at 18:33
  • @goldilocks if I were using mutex locks, I would have to repeatedly check the locks during the waiting time, don't I? – Gunter Jan 15 '16 at 19:04
  • I meant on any variables that are accessed or changed in a callback that are also accessed or changed somewhere else (another callback, the main procedural code). That might not apply to anything, of course. If it does, an advantage of the poll/select method is that you would be handling the interrupts synchronously -- handling one will not be interrupt by another. But using independent callbacks makes them more like signal handlers; your SIG X callback may be halfway through executing when the SIG Y one fires. – goldilocks Jan 15 '16 at 19:24