Protect code when accessing shared resources

Introduction to critical sections

Knowing about critical sections and their background is an important thing for MCU programming. When starting with basic Arduino code examples on a Sketch level, especially when coming from AVR, everything concerning the execution flow of your program might feel to be pretty much straightforward and as you already know there’s usually no multitasking system in the background going on. So, you might assume: What could possibly go wrong when just calling "digitalWrite()" somewhere?

The answer is: Quite a lot.

Details

This concise explanation in simple terms phrases the scope pretty good:

In simple terms, a critical section is a group of instructions/statements or a region of code that needs to be executed atomically, such as accessing a resource (file, input or output port, global data, etc.).

In concurrent programming, if one thread tries to change the value of shared data at the same time as another thread tries to read the value (i.e. data race across threads), the result is unpredictable.

Operating System | Critical Section - GeeksforGeeks

Another good one is:

A Critical Section is the part of a program that accesses shared resources. […] We can avoid race conditions by making sure that no two processes enter their Critical Sections at the same time.

Definition: Critical Section

But there are no threads?

you might ask. That’s right, but there are interrupts. The concept of protecting your code using critical sections from simultaneously accessing resources doesn’t only come into place when doing multithreaded programming on larger CPUs resp. real multitasking systems, you will also have to protect some of your code from being interleaved by interrupts, otherwise things will probably go south.

Conclusion

So, it’s all about synchronizing access of multiple actors to singleton resources, this piece also has graphics: Process Synchronization in Operating System | Studytonight. In general: There are different mechanisms for synchronizing such things, most commonly they are referred to as “locks” or “mutexes” and there are even “spin-locks”. In this case, we focus on a mechanism called “critical sections”.

Outlook

In the posts below, we want to outline some aspects specific to the AVR MCUs, but also about the Espressif ESP32 Xtensa dual-core MCU, where we will actually have to take care about symmetric multiprocessing aspects when protecting our code.


Background

As people have been asking again and @weef and me are still trying hard to expose the point of that topic, let’s try from a different angle. This is all about “getting things right” from a software perspective. @weef already mentioned that aspect elsewhere, but nevertheless he also knows about the hardware side of things as well, so we might connect some dots here.

AVR-specifics

Coming from Standard C library for AVR-GCC (avr-libc), one of the foundation libraries of the Arduino AVR core HAL, there are appropriate macros to assist you in declaring sensible regions of your code as critical sections. They

deal with code blocks that are guaranteed to be executed Atomically or Non-Atomically. The term “Atomic” in this context refers to the unability of the respective code to be interrupted. […]

A typical example that requires atomic access is a 16 (or more) bit variable that is shared between the main execution path and an ISR.

Details

While you might already know the Arduino HAL primitives interrupts() and noInterrupts(), these do not ensure full atomicity of your code.

However, by protecting your code with the ATOMIC_BLOCK(ATOMIC_RESTORESTATE) guard, you will create a block of code that is guaranteed to be executed atomically.

util/atomic.h header

These macros are implemented by the corresponding header file avr-libc: <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks.

By example

To give you an idea about what might will probably go wrong when taking it too easy:

Protect the read sequence from system interrupts. If an interrupt occurs during
the time the PD_SCK signal is high it will stretch the length of the clock pulse.
If the total pulse time exceeds 60 uSec this will cause the HX711 to enter
power down mode during the middle of the read sequence. While the device will
wake up when PD_SCK goes low again, the reset starts a new conversion cycle which
forces DOUT high until that cycle is completed.

The result is that all subsequent bits read by shiftIn() will read back as 1,
corrupting the value returned by read(). The ATOMIC_BLOCK macro disables
interrupts during the sequence and then restores the interrupt mask to its previous
state after the sequence completes, insuring that the entire read-and-gain-set
sequence is not interrupted. The macro has a few minor advantages over bracketing
the sequence between noInterrupts() and interrupts() calls.

HX711/HX711.cpp at 222b06530d1ff8b208090edf61fa9ac6f26538ac · hiveeyes/HX711 · GitHub
Prevent ints from corrupting the read sequence by dhmsjs · Pull Request #62 · bogde/HX711 · GitHub

Classic Arduino noInterrupts() vs. interrupts() guard

https://www.arduino.cc/reference/en/language/functions/interrupts/nointerrupts/
https://www.arduino.cc/reference/en/language/functions/interrupts/interrupts/

SMP programming for the ESP32

Please read this important piece on how to program for the ESP32. With its two CPU cores, things get a bit more hairy when accessing shared resources.

Introduction

When programming the ESP32 using the well known Arduino HAL, there are more special effects involved you might not have known about. In the spirit of this thread, this is all about synchronizing access to shared resources between multiple actors. In the advent of affordable dual-core MCUs, there ain’t no such thing as a free lunch (anymore), as all of the problems of programming for Symmetric multiprocessing - Wikipedia will arrive on your table. Take care and enjoy the ride.

Critical sections in ESP-IDF FreeRTOS

Critical Sections & Disabling Interrupts: In ESP-IDF FreeRTOS, critical sections are implemented using mutexes. Entering critical sections involve taking a mutex, then disabling the scheduler and interrupts of the calling core. However the other core is left unaffected. If the other core attemps to take same mutex, it will spin until the calling core has released the mutex by exiting the critical section.

ESP-IDF Programming Guide: Overview

Details

ESP-IDF FreeRTOS SMP Changes: Critical Sections & Disabling Interrupts

Modifications to portENTER_CRITICAL

By example

#ifdef ESP_H
// Begin of critical section.
// Critical sections are used as a valid protection method
// against simultaneous access in vanilla FreeRTOS.
// Disable the scheduler and call portDISABLE_INTERRUPTS. This prevents
// context switches and servicing of ISRs during a critical section.
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&mux);
#endif

HX711/HX711.cpp at 222b06530d1ff8b208090edf61fa9ac6f26538ac · hiveeyes/HX711 · GitHub
ESP32 issue, clock speed too fast · Issue #75 · bogde/HX711 · GitHub

A post was split to a new topic: ESP32 multicore task scheduling and its watchdog timer (WDT)

Interrupt handling in general

Along the lines of a specific problem about interrupt-driven buffering, these resources cover the topic of interrupt (un)handling very well and concise. Using critical sections appropriately is also the solution here. Enjoy reading.

See also