Blocking vs Non-blocking Code
A very significant portion of programming in embedded systems is recognizing and avoiding blocking code.
This also extends into operating systems and programming languages development, where we try to keep the same ideals.
First of all, lets go over what is blocking code.
Blocking code is code that prevents any other code from running.
The most common types of blocking code are:
for/while loops with a condition
No other code will be executed until condition is satisfied
Infinite Finite State Machines
FSMs that never change state, or are unable to reach an END state
Understanding Blocking
Consider the following code, no need to learn how to compile or run this, just understand what happens.
// basic-blocking.c
#include <stdio.h>
int main() {
int outer = 0;
while (outer < 2) {
printf("outer = %d\n", outer);
outer++;
int inner = 0;
while (inner < 3) {
printf("inner = %d, blocked\n", inner);
inner++;
}
printf("outer = %d, unblocked\n", outer);
}
return 0;
}
Then we compile:
> clang basic-blocking.c
> ./a.out
outer = 0
inner = 0, blocked
inner = 1, blocked
inner = 2, blocked
outer = 1, unblocked
outer = 1,
inner = 0, blocked
inner = 1, blocked
inner = 2, blocked
outer = 2, unblocked
As we can see the outer loop cannot increment, until the inner loop is done!
The outer-loop is parallel to functions like sleep and HAL_refresh in our code, which can be affected if you use while loops!
Example: Non-Interrupt Based
Generally, the main-loop design of our code involve the following pseudo-logic:
int main(void) {
InitNonBlockingLED(); // init non-blocking check
while(1) { // same as while(true)
PollNonBlockingLED(); // toggle LED if button-pressed
HAL_refresh(&hal) // refresh peripherals from HAL
// ERR : Two lines above will NOT run if you stay in
// Application_loop!
Application_loop(&app, &hal); // FSM
// WARN: what do you think happens here if we block /
// stuck here?
}
}
Please read through the comments, and then read the comments in bold.
If we are to have blocking code in our FSM function (for example, a while loop w/ condition inside the Application_loop), just like the earlier example, HAL_refresh cannot be run until the FSM is done, leading to delays or completely frozen in refreshing our peripherals.
Because PollingNonBlockingLED also resides on the same level as HAL_refresh the button to check for blocking will also be non-responsive.
Example: Interrupt-Based
Generally, the main-loop design of our interrupt-based code involve the following pseudo-logic:
int main() {
construct(); // initialize
while(1) {
main_loop(); // FSM
sleep(); // ERR : If we block in the FSM above, will this run?
}
}
Please read through the comments, and then read the comments in bold.
If we are to have blocking code in our FSM function (for example, a while loop w/ condition inside the main_loop), just like the earlier example, sleep cannot be run until the FSM is done, which prevents the processor from sleeping / going into low-power mode, defeating the purpose of interrupt-driven code.
Avoiding Blocking Code
Ideally, avoid while and for loops you don't understand.
Obviously, ideally, you should avoid any code you don't understand.
If you must use them, or don't understand where your code is blocking, use the following steps to debug:
Run your code, while running, think about how your code works
While thinking how your code "flows", press the blocking-check LED button, or watch the blocking-check LED in interrupt-based code
If you find that the light is unresponsive, or off in interrupt-based code, continue.
Look through the state / segment of code where the light is unresponsive
If you haven't figured it out, continue.
Set a breakpoint at the beginning of the state / code segment
Step Over your code
Step Into nested functions and loops!
Check your conditions and state transitions