Author Topic: techniques for writing non blocking code  (Read 28511 times)

0 Members and 1 Guest are viewing this topic.

Offline DVXTopic starter

  • Contributor
  • Posts: 27
  • Country: gb
techniques for writing non blocking code
« on: July 31, 2017, 03:29:24 pm »
User button presses is one area my code which could be improved, I have been looking for a good way to detect the level changes when a user applies a button (push to make switch) input. Currently when a button input change is detected the program branches to a routine which then waits in a loop until the button is released. To keep things simple the code is blocking, it just waits until the button level changes to show it no longer pressed down. I read button level changes and do screen updates by calling routines with in the button waiting loop but there has to be a better way. I don't have any experience for using RTOS or scheduler and wonder how else this can be done and still detect fast button level changes. I poll the buttons as the external interrupt on change pins on my PIC32 are all used by LCD connections.
 

Offline joshtyler

  • Contributor
  • Posts: 36
  • Country: gb
Re: techniques for writing non blocking code
« Reply #1 on: July 31, 2017, 03:41:59 pm »
Is there anything stopping you from polling the button once on each processing loop, and remembering the previous value?

Something like this?

Code: [Select]
bool buttonState;
int main(void)
{
while(1) //Main processing loop
{
buttonState = readButton(); //Non blocking read of button state

//All the rest of your processing happens here
}
}
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
Re: techniques for writing non blocking code
« Reply #2 on: July 31, 2017, 03:53:14 pm »
1. Run-to-completion scheduler and the tasks/processes are implemented as finite state machines.

- Consumes very little RAM but splitting processes to state machines may require some effort.

2. Co-operative scheduler and each process shall yield to allow other tasks/processes run.

- Consumes more RAM as each process/task will have it own stack.

3. Protothreads are a mix of the 1 and 2.

- Requires typically static variables or similar mechanism as the process/task doesn't have its own stack frame.
 
The following users thanked this post: JPortici

Offline JPortici

  • Super Contributor
  • ***
  • Posts: 3452
  • Country: it
Re: techniques for writing non blocking code
« Reply #3 on: July 31, 2017, 03:55:47 pm »
what i usually do, leaving out debouncing
- sample the port
- xor with previous saved value
- for every "1" in the xor's value go to check if current port value is zero (depressed) or 1 (pressed). This will set flags depending on current state. Other state machines will use these flags
- update previous saved value
 

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
Re: techniques for writing non blocking code
« Reply #4 on: July 31, 2017, 04:20:14 pm »
3. Protothreads are a mix of the 1 and 2.

- Requires typically static variables or similar mechanism as the process/task doesn't have its own stack frame.

Protothreads can be interesting in C++. If you have a Thread class from which you derive application specific threads, you can put your state in there as member variables rather than the typical static/global variables.
 

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1663
  • Country: us
Re: techniques for writing non blocking code
« Reply #5 on: July 31, 2017, 04:42:07 pm »
Another technique is to connect the button to an external interrupt input and process the button press in the interrupt handler (or set a flag in the ISR and handle the event elsewhere).

If you do this, you'll need to debounce the button in hardware (unless your MCU has a glitch filter on its external interrupt inputs) to prevent entering the ISR multiple times as the contacts bounce.

If you don't have hardware interrupts available (you said they're all in use) and you can't free any up, a pre-emptive RTOS makes this very straightforward. You just use a dedicated task to poll the button periodically. Just set the priority of the button polling task lower than the important tasks and you won't have to worry about the polling interfering with everything else.
« Last Edit: July 31, 2017, 04:46:47 pm by Sal Ammoniac »
Complexity is the number-one enemy of high-quality code.
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
Re: techniques for writing non blocking code
« Reply #6 on: July 31, 2017, 04:53:09 pm »
If you don't have hardware interrupts available (you said they're all in use) and you can't free any up, a pre-emptive RTOS makes this very straightforward. You just use a dedicated task to poll the button periodically. Just set the priority of the button polling task lower than the important tasks and you won't have to worry about the polling interfering with everything else.

This can be implemented also in a non-pre-emptive system: Just poll and debounce the button in a periodic ISR and update the global flag containing the filtered button state. The main program can read the filtered button state and act accordingly.
 
The following users thanked this post: Someone

Offline DVXTopic starter

  • Contributor
  • Posts: 27
  • Country: gb
Re: techniques for writing non blocking code
« Reply #7 on: July 31, 2017, 05:08:41 pm »
I forgot to mention the port with the button sense press and release also has a rotary encoder connected to it, so JPORTci's sampling of the port and XOR's the previous value would not be hard to implement and would cover reading the encoder. Currently polling is via a timer set to 5ms rollover and placing the current and previous port values and XOR'ing then in the timer interrupt + setting flags for changes should give a fast response. I prefer to keep code in the interrupt routines short but sometimes its a good way to get a fast response to external changes.   
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 26755
  • Country: nl
    • NCT Developments
Re: techniques for writing non blocking code
« Reply #8 on: July 31, 2017, 07:33:27 pm »
Another technique is to connect the button to an external interrupt input and process the button press in the interrupt handler (or set a flag in the ISR and handle the event elsewhere).
Please don't do this! Worse advice ever! The occurence of interrupts has to be well defined and an external button is far from it. Think about  what happens when a button wears out, gets wet and/or is connected to a long cable which picks up noise: you'll get loads of interrupts which will screw up the timing of other processes. And things can be made far worse by a microcontroller with poorly defined logic input levels. The end result is that the device misbehaves in odd ways at customers and it is impossible to figure out why.

A method which works well is to increment a counter in a timer interrupt. Use this counter in the main loop to define an interval with which the buttons are polled. In each poll increment a counter (up to a limited number) when a button is pressed and reset (or decrement) when released. When the count hits a certain value mark the button as pressed.
« Last Edit: July 31, 2017, 08:08:17 pm by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Online Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11536
  • Country: my
  • reassessing directives...
Re: techniques for writing non blocking code
« Reply #9 on: July 31, 2017, 08:49:39 pm »
Another technique is to connect the button to an external interrupt input and process the button press in the interrupt handler (or set a flag in the ISR and handle the event elsewhere).
Please don't do this! Worse advice ever! The occurence of interrupts has to be well defined and an external button is far from it. Think about  what happens when a button wears out, gets wet and/or is connected to a long cable which picks up noise: you'll get loads of interrupts which will screw up the timing of other processes.
not necessarily the worse advice, interrupt is meant to screw process timing, and the process should be able to cope with that. the problem is not the method, but the external device itself (switch). not much difference if you put the misbehaving switch into a pool process. having said that, imho, interrupt is meant for non-predictable event, or pooling will not be fast enough for detecting it.. making switch (that can be pooled) will waste one interrupt pin that can be made usefull to other unpredictable event.

btw back to OP, the picture should be worth of many words... (attached) that is one way to do it (pooling method) sorry for my doctorate writing style...
Nature: Evolution and the Illusion of Randomness (Stephen L. Talbott): Its now indisputable that... organisms “expertise” contextualizes its genome, and its nonsense to say that these powers are under the control of the genome being contextualized - Barbara McClintock
 

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1663
  • Country: us
Re: techniques for writing non blocking code
« Reply #10 on: July 31, 2017, 11:19:43 pm »
Another technique is to connect the button to an external interrupt input and process the button press in the interrupt handler (or set a flag in the ISR and handle the event elsewhere).
Please don't do this! Worse advice ever!

This technique wouldn't be my first choice, but with proper hardware conditioning of the input it's not the worst way to implement a button.
Complexity is the number-one enemy of high-quality code.
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: techniques for writing non blocking code
« Reply #11 on: July 31, 2017, 11:29:26 pm »
I have used interrupts in the past to detect presses on a keypad, but to avoid the issue of screwing with the timing, or bad interrupts I did three things.

1) Debounced the interrupt input.
2) Buffered the interrupt input through a Schmidt trigger (not sure if this was warranted, but I had a spare gate)
3) In the interrupt handler I disabled the interrupt after setting a flag to process the keypad input. The interrupt was re-enabled after a timeout of 20ms, so worst case an interrupt could only occur once every 20ms which would not have any detrimental effect on the system.
 

Offline HackedFridgeMagnet

  • Super Contributor
  • ***
  • Posts: 2028
  • Country: au
Re: techniques for writing non blocking code
« Reply #12 on: August 01, 2017, 12:15:11 am »

Please don't do this! Worse advice ever! The occurence of interrupts has to be well defined and an external button is far from it.

Lol I thought interrupts where used when the occurrence of an event wasn't well defined.

I get your point though, you don't want lots of interrupts hogging the processor if the input is somehow suspect.

But you could interrupt on a button and reset the interrupt after a timeout. Also the interrupt could only trigger on a transition instead of a level.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 822
Re: techniques for writing non blocking code
« Reply #13 on: August 01, 2017, 01:11:16 am »
Spare uart rx? built-in debounce (baud rate as needed),  buffer a few presses, framing error to detect long presses, rx data would give additional info on press length if wanted
 :)
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 26755
  • Country: nl
    • NCT Developments
Re: techniques for writing non blocking code
« Reply #14 on: August 01, 2017, 06:33:23 am »
Another technique is to connect the button to an external interrupt input and process the button press in the interrupt handler (or set a flag in the ISR and handle the event elsewhere).
Please don't do this! Worse advice ever!
This technique wouldn't be my first choice, but with proper hardware conditioning of the input it's not the worst way to implement a button.
You have to define 'proper hardware conditioning'  8) It will take several extra parts to filter unwanted signals (if you take EMC immunity tests in mind you'll see it isn't that trivial) and you will lose the flexibility to change the response time if necessary. Filtering can be done in software just as easely if you poll the buttons at a certain interval. It isn't even a fast process with critical timing constraints (compared to handling a UART for example).
« Last Edit: August 01, 2017, 06:38:18 am by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline poorchava

  • Super Contributor
  • ***
  • Posts: 1672
  • Country: pl
  • Troll Cave Electronics!
Re: techniques for writing non blocking code
« Reply #15 on: August 01, 2017, 06:50:00 am »
My standard solution, which works well unless you have a very large number of buttons:
-assign a press time counter and 2 flags to each button
-call readButtonState periodically (eg. 100Hz)
-if button is pressed, increment the press time counter, otherwise zero the counter
-if counter exceeds the press time limit, set the 'pressed' flash, if it exceeds time required for hold set 'hold' flag
-in ui part of software reset or not the button press/hold flag after it has been serviced

Perhaps it could be easier done with RTOS, but I have little experience with those as I prefer to write on bare metal
I love the smell of FR4 in the morning!
 

Offline John Coloccia

  • Super Contributor
  • ***
  • Posts: 1208
  • Country: us
Re: techniques for writing non blocking code
« Reply #16 on: August 01, 2017, 08:06:52 am »
Another technique is to connect the button to an external interrupt input and process the button press in the interrupt handler (or set a flag in the ISR and handle the event elsewhere).
Please don't do this! Worse advice ever! The occurence of interrupts has to be well defined and an external button is far from it. Think about  what happens when a button wears out, gets wet and/or is connected to a long cable which picks up noise: you'll get loads of interrupts which will screw up the timing of other processes. And things can be made far worse by a microcontroller with poorly defined logic input levels. The end result is that the device misbehaves in odd ways at customers and it is impossible to figure out why.

A method which works well is to increment a counter in a timer interrupt. Use this counter in the main loop to define an interval with which the buttons are polled. In each poll increment a counter (up to a limited number) when a button is pressed and reset (or decrement) when released. When the count hits a certain value mark the button as pressed.


Putting a button on an interrupt is perfectly fine. Assuming the input hasn't already been filtered, after you get the interrupt disable that interrupt, debounce in the program, and then reenable the interrupt. Basically, every pass through my main loop I'll call a debounce routine that goes out and debounces anything that needs it. I've also done it in a timer interrupt, which is maybe the slickest solution...debounced I/O just magically appears and the main program never needs to worry about it.

If the button is broken to the point that it's signaling it's pushed when it's not, then the button needs to be replaced. Regardless, you either spend the time handling a noisy button in the interrupt, or you spend the time handling it in the main program. It really doesn't matter. Either way something will detect that the button has been pressed, and then the button is out of commission until the debounce time is up.

I'm not sure what the "button on a long cable" thing is all about either. If you have something that susceptible to noise, why would you dump it directly into a micro? It would make far more sense to have a slightly more sophisticated circuit which was not as susceptible to noise. I can probably make up all sorts of problem scenarios, but I'm pretty sure the OP is asking about a normal button.
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: techniques for writing non blocking code
« Reply #17 on: August 01, 2017, 08:10:49 am »
I'll just do some coming out with the code I use whenever I need buttons.

It is based on keeping a simple FSM for each button.
The debouncing time, the minimum time to register a short or a long press are defined by the constants CLICKDEBOUNCE, CLICKSHORT, and CLICKLONG.
A short press will register when the button is released, a long one when its time has elapsed.

The ServeButton() routine must be invoked for each button at a sensible interval (some ms).
It returns a key value (enum) associated with the defined events (long and short press) or NoKey if nothing happened.

The routine can be called in the main loop, or in a periodic ISR (it's lightweight enough, if the number of key is limited, but YMMV).
In the last case, the key press can be passed to main loop in the usual ways (e.g. in a volatile variable, or enqueued in an input queue if needed).

The specific example is from my PSU controller, for encoders' shaft clicks, but the code is generic enough: change the Key enum to suit your needs (hence not shown here).
It uses the ST HAL, but it's quite portable: the only requirements are reading a GPIO pin (duh!) and having a tick counter.

Code: [Select]
typedef enum
{
  Idle       = 0,
  Debouncing,
  Pressed,
  Waiting
} BtnState;


typedef struct
{
  uint16_t      pin;     /* GPIO pin */
  GPIO_TypeDef *port;    /* GPIO port */
  GPIO_PinState pol;     /* polarity: SET active high, RESET active low */
  BtnState      state;   /* current state */
  uint32_t      tick;    /* time of press */
  Key           kShort;  /* key enum value for short press */
  Key           kLong;   /* key enum value for long press */
} Button;

#define CLICKDEBOUNCE 30    /* 30ms for debouncing   */
#define CLICKLONG     1000  /* 1s for long click     */
#define CLICKSHORT    75    /* 75ms for short click */

#define BUTTONS 2

Button buttons[BUTTONS] =
{
  {
    .pin    = ENC1_BTN,
    .port   = ENC1_BTN_GPIO,
    .pol    = GPIO_PIN_SET,
    .kShort = ClickVKey,
    .kLong  = LongVKey
  },
  {
    .pin    = ENC2_BTN,
    .port   = ENC2_BTN_GPIO,
    .pol    = GPIO_PIN_SET,
    .kShort = ClickIKey,
    .kLong  = LongIKey
  }
};

Key serveButton( Button *b )
{
  Key key      = NoKey;
  uint32_t now = HAL_GetTick(); // What's the time?
  bool btn     = b->pol == HAL_GPIO_ReadPin( b->port, b->pin );

  switch( b->state )
  {
  case Idle:
    if( btn )
    {
      /* The button has been pressed */
      /* Remember when */
      b->tick  = now;
      /* and move to Debouncing state */
      b->state = Debouncing;
    }
    break;

  case Debouncing:
    if( btn )
    {
      if( now - b->tick > CLICKDEBOUNCE )
        /* Real press */
        b->state = Pressed;
    }
    else
    {
      /* It was a bounce, start over */
      b->state = Idle;
    }
    break;

  case Pressed:
    if( btn )
    { /* Still pressed, is this a long press? */
      if( now - b->tick > CLICKLONG )
      {
        /* A long press has been detected */
        key      = b->kLong;
        /* Wait for release */
        b->state = Waiting;
      }
    }
    else
    { /* Released, is it a short press? */
      if( now - b->tick > CLICKSHORT )
      {
        /* A short press has been detected */
        key      = b->kShort;
      }
      /* Return to idle */
      b->state = Idle;
    }
    break;

  case Waiting:
    if( !btn )
    {
      b->state = Idle;
    }
    break;
  }
  return key;
}
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline Yansi

  • Super Contributor
  • ***
  • Posts: 3893
  • Country: 00
  • STM32, STM8, AVR, 8051
Re: techniques for writing non blocking code
« Reply #18 on: August 01, 2017, 08:28:38 am »
1) Sample buttons at 10-20ms interval using tim isr.
2) make a lock bit variable and event bit variable for each button
3) compare actual sampled button state to the locked state. If not locked and pressed, set the event variable and lock it. If locked and not pressed, then unlock (optionally clear the event bit).
4) check the event bit in main loop, if set, clear it and you know the button was pressed.

You can even use two event bits, one for press event, other for depressed event.
Key repeat functionality can be easily implemented in this.
 

Online tggzzz

  • Super Contributor
  • ***
  • Posts: 19280
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: techniques for writing non blocking code
« Reply #19 on: August 01, 2017, 11:46:35 am »
Putting a button on an interrupt is perfectly fine.

You can do it, just as you can use asynchronous monostables (like a 74123) in clocked logic circuits. Occasionally there are good reasons for those techniques.

But both techniques smell because they introduce subtle failure mechanisms (analogue and digital), can make it difficult to reason about "edge case" operation of the complete system, and difficult to test in production.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline David Hess

  • Super Contributor
  • ***
  • Posts: 16545
  • Country: us
  • DavidH
Re: techniques for writing non blocking code
« Reply #20 on: August 01, 2017, 12:22:32 pm »
Here is what I do:

1. One of the timer interrupts (1) regularly executes a routine which scans the inputs and sends display outputs if the outputs are on the same interface.  If interrupt on change is supported, then it could also call this routine although this complicates debouncing.
2. The keyboard inputs are processed to detect change of state which is stored in shadow registers.
3. The main routine only accesses the shadow registers.

The shadow registers include the last key state which is used by the interrupt routine to detect state changes and a pair of other registers which indicate key press and key release.  When the main routine acts on a key press or key release, it resets that bit.  Key state might be used for press and hold functions but the main routine never alters it.

(1) A divide chain is used to create multiple timer interrupts with different rates so the fastest timer interrupt chains to the others to produce slower timer interrupts for tasks which do not need to operate at the fastest rate.
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 26755
  • Country: nl
    • NCT Developments
Re: techniques for writing non blocking code
« Reply #21 on: August 01, 2017, 07:22:44 pm »
I'm not sure what the "button on a long cable" thing is all about either. If you have something that susceptible to noise, why would you dump it directly into a micro?
People do that AND use interrupts without masking. I've seen it myself otherwise I wouldn't believe it either.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1663
  • Country: us
Re: techniques for writing non blocking code
« Reply #22 on: August 01, 2017, 08:25:07 pm »
But both techniques smell

Define smell. Roses smell. Freshly baked bread smells.
Complexity is the number-one enemy of high-quality code.
 

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
Re: techniques for writing non blocking code
« Reply #23 on: August 01, 2017, 08:43:28 pm »
 

Offline Yansi

  • Super Contributor
  • ***
  • Posts: 3893
  • Country: 00
  • STM32, STM8, AVR, 8051
Re: techniques for writing non blocking code
« Reply #24 on: August 01, 2017, 09:03:32 pm »
Hence why I suggest sampling the buttons at 10 to 20ms intervals.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf