SINGLE_THREADED_BLOCK without a call to SYSTEM_THREAD(ENABLED)


#1

If I do not call

SYSTEM_THREAD(ENABLED);

What is the effect of SINGLE_THREADED_BLOCK and ATOMIC_BLOCK()? The documentation seems to imply they are only relevant if one makes that call first. My understanding is that timers, for example, are always handled in a separate thread which would make these functions still useful if one was trying to coordinate multiple timers.

Adding on to that, if I am manipulating a piece of data that may also be changed in a ISR in the main loop, what is the best way to protect those accesses assuming I am not making a call first to SYSTEM_THREAD(ENABLED)? It seems like making a call to ATOMIC_BLOCK would be safest, since it ensures an interrupt will not come along and mangle the data before it is finished processing.


#2

@jls, SINGLE_THREADED_BLOCK is used to stop the FreeRTOS scheduler from context switching within the defined block while ATOMIC_BLOCK disables interrupts entirely. Though disabling interrupts is a good way to “protect” data shared with an ISR, this method can cause havoc if the processing of data takes a long time. For this reason, it is not usually recommended.

Another approach is to use you own “data ready” flag which the ISR sets when data is available. The ISR would skip getting more data until 'loop() processes the data and resets the flag. However, this means you may be losing sampled data during the processing window which may not be desirable.

A very common method is to double-buffer the data, toggling between the two. The ISR can stuff data into the non-processing buffer while loop() is processing the other buffer. Once finished processing, loop() waits for the “data ready” flag from the ISR, resets the flag and processes the buffer with the new data. With the flag reset, the ISR also switches to the previously processed buffer to grab new data. This works best when processing time is less than the time for the ISR to fill a buffer. Otherwise, small windows of data may be missed by the ISR, waiting on loop().


#3

@peekay123 thanks for that information! Maybe a small example could help clarify my question. Suppose we had the following program:

int aFlag;
int iterations = 0;

loop(){
    
    if(iterations == 1000){
        aFlag = 1;           // ensure that, if I set this value
        if(aFlag){           // the next statement executes. 
            // do something. // otherwise, it is possible to get an overflow! 
            iterations = 0;
        }
    }
        
    iterations++;
    
}

void isrRoutine(){
    aFlag = 0; // I can be called at any time.
}

The issue in this code is that the isrRoutine can be called at any time so it is possible the main loop will stop execution after the assignment of aFlag=1. In this scenario, the branch would never get executed, which means iterations could overflow. I realize this is a very synthetic example, but it illustrates my question. In this situation, what would be the best approach to ensure that region of code would execute correctly?

In this case, it would be possible to rewrite the program like this, and an overflow would never occur, although it would require an extra cycle. In this case this sort of thing is obvious, but it will be less so in a “real” program:


int aFlag;
int iterations = 0;

loop(){
    
    if(iterations == 1000){
        aFlag = 1;           // ensure that, if I set this value
        if(aFlag){           // the next statement executes. 
            // do something. // otherwise, it is possible to get an overflow! 
            iterations = 0;
        }
    }else{
        iterations++;
    }
}

void isrRoutine(){
    aFlag = 0; // I can be called at any time.
}

It seems like ATOMIC_BLOCK is my guy, but I wanted to be sure there isn’t a better way.


#4

Just a note: When you want to manipulate a global variable inside an ISR that variable needst to be declared volatile.

Also to account for potential “overflows” you’d usually not only check for equality (==) but rather >=.

Next, since you are unconditionally setting aFlag = 1 prior to your if (aFlag) what’ would be the use of the check at all (unless explicitly hope for the ISR to interject)?


#5

@ScruffR thanks for your comments!

I realize the example is somewhat synthetic, but the check there is to illustrate that an ISR could interject and cause a problem. I guess my question is in that sort of scenario, other than disabling the interrupt, is there a better way to account for that?


#6

@jls, you should make use of Finite State Machine design in both your ISR and loop(). If what you are looking for is a FIFO or circular buffer where the ISR is constantly putting data into a queue, you will need to manager head and tail pointers within the ISR and loop(). This is the typical approach taken by the hardware serial input and output circular buffers. BTW, you can use ATOMIC_BLOCK around the setting of the flag in loop() which blocks interrupts for only a very short time.

If you just want 1000 samples, then process samples and so on, then make the ISR stop sampling when the count gets to 1000. Perhaps you need to describe you data sampling/processing needs before we can advise on best approach.


#7

Hi @peekay123,

Thanks for your comments about FSMs and buffering – certainly appreciated. I guess my question isn’t to solve a literal example (i.e., that code is just something I cooked up to illustrate my question), but to understand better how the SDK allows one to account for those scenarios, should they arise.

The core of my question is: If I do not call SYSTEM_THREAD(ENABLED) do SINGLE_THREADED_BLOCK and ATOMIC_BLOCK still do something?