C++ Decrementing an Element of a Single-Byte (Volatile) Array Is Not Atomic! Why? (Also: How to Force Atomicity in Atmel Avr Mcus/Arduino)

C++ decrementing an element of a single-byte (volatile) array is not atomic! WHY? (Also: how do I force atomicity in Atmel AVR mcus/Arduino)

Ok, the answer to "Why is incrementing/decrementing a single byte variable NOT atomic?" is answered very well here by Ishamael here, and Michael Burr here.

Now that I got my answer that -- decrement and ++ increment operations are never atomic, even when done on byte values (see answers above and Nick Gammon's link here), I'd like to ensure the follow-up question of how do I force atomicity on Atmel AVR microcontrollers is also answered so this question becomes somewhat of a resource:

Here are all techniques I am aware of to force atomicity in Atmel AVR microcontrollers, such as Arduino:

1) Option 1 (the preferred method):

uint8_t SREG_bak = SREG; //save global interrupt state
noInterrupts(); //disable interrupts (for Arduino only; this is an alias of AVR's "cli()")
//atomic variable-access code here
SREG = SREG_bak; //restore interrupt state

2) Option 2 (the less-safe, not recommended method, since it can cause you to inadvertently enable nested interrupts if you accidentally use this approach in a code block or library which gets called inside an ISR):

noInterrupts(); //disable interrupts (Arduino only; is an alias to AVR's "cli()" call)
//atomic variable-access code here
interrupts(); //enable interrupts (Arduino only; is an alias to AVR's "sei()" call)

Alternative option 2:

cli(); //clear (disable) interrupts flag; noInterrupts() is simply a macro for this
//atomic variable-access code here
sei(); //set (enable) interrupts flag; interrupts() is simply a macro for this

3) Option 3 (essentially the same as option 1; just using a macro held in an avr-libc library instead, and with variable scope applied within the braces of course)

source: http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

#include <util/atomic.h> //(place at top of code)
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
//atomic access code here
}

Related:

  1. [My Q&A] Which variable types/sizes are atomic on STM32 microcontrollers?
  2. https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
  3. ***** [My answer] Which Arduinos support ATOMIC_BLOCK? [and how can I duplicate this concept in C with __attribute__((__cleanup__(func_to_call_when_x_exits_scope))) and in C++ with class constructors and destructors?]
  4. For how to do this in STM32 microcontrollers instead, see my answer here: What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?

What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?

Multiple ways to enable/disable interrupts in STM32 mcus:

1. Via ARM-core CMSIS:

1.A. For global interrupts

__enable_irq()   // enable all interrupts
__disable_irq() // disable all interrupts

// Returns the current state of the priority mask bit from the Priority Mask
// Register. [0 if global interrupts are **enabled** and non-zero if they
// are **disabled**]
__get_PRIMASK()

For the definition of these functions, see:

  1. https://github.com/ARM-software/CMSIS/blob/master/CMSIS/Include/cmsis_gcc.h
    1. Contains at least:
      __enable_irq() 
      __disable_irq()
      __get_PRIMASK()
      __set_PRIMASK()
  2. STM32 example location: "stm/stm32f2xx/st_hal_v1.1.3/CMSIS/Include/cmsis_gcc.h":

To save and restore the interrupt state, use __get_PRIMASK(), like this:

// 1. back up interrupt state; `__get_PRIMASK()` returns 0 if interrupts
// are **enabled**, and non-zero if they are **disabled**.
bool interrupts_enabled = (__get_PRIMASK() == 0);

// do stuff

// 2. Disable interrupts
__disable_irq();
// 3. Restore backed-up-state
if (interrupts_enabled) {
__enable_irq();
}

When dealing with global interrupts, this is the best way for bare-metal, non-FreeRTOS code!

I think this technique is also cross-compatible with ALL ARM-core mcus, not just STM32.

I first learned this technique from Tilen Majerle, here: https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/. His work and contributions to clear up this super-obfuscated stuff are infinitely valuable and appreciated!

His example:

void ImportantFunction1(void) {
/* Important function 1 */
uint32_t prim;

/* Do some stuff here which can be interrupted */

/* Read PRIMASK register, check interrupt status before you disable them */
/* Returns 0 if they are enabled, or non-zero if disabled */
prim = __get_PRIMASK();

/* Disable interrupts */
__disable_irq();

/* Do some stuff here which can not be interrupted */

/* Call subfunction */
ImportantFunction2();

/* Do some stuff here which can not be interrupted */
/* This part is still interrupt safe because ImportantFunction2 will not enable interrupts */

/* Enable interrupts back */
if (!prim) {
__enable_irq();
}

/* Do some stuff here which can be interrupted */
}

1.B. For specific IRQs (Interrupt Requests)

It is best to avoid disabling global interrupts, if possible, and disable only the fewest number of specific interrupts possible to achieve atomicity for your specific code. So, using these functions allows you to enable or disable only the specific interrupts you need to!

Enable or disable specific types of interrupts:

void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);

NVIC stands for "Nested Vector Interrupt Controller". Nested interrupts (meaning: a higher-priority interrupt can still fire within an ISR) are enabled by default on STM32 microcontrollers. Each interrupt type has a priority assigned to it, with lower priority numbers being higher priority, and higher-priority interrupts are able to fire while an ISR is being processed for a lower-priority interrupt. See here for a little more information on the STM32 NVIC: https://stm32f4-discovery.net/2014/05/stm32f4-stm32f429-nvic-or-nested-vector-interrupt-controller/.

Contrast this to AVR microcontrollers (ex: ATMega328 / Arduino Uno), which do not have priority-based interrupts, so by default, when any ISR is being processed, all interrupts (ie: global interrupts) are automatically disabled as the program enters the ISR. Note that even on AVR mcus, however, you can still manually enable nested interrupts / ISRs if you like by manually re-enabling global interrupts inside your ISR, via a call to interrupts() on Arduino or sei() (set interrupts) on raw AVR.

Each ARM-core microcontroller manufacturer, I believe, including STM32 types, must define and create its own list of IRQn_Type interrupt request types, so see below for the STM32 details on their specific interrupt types defined for each mcu.

2. Via STM32 HAL (Hardware Abstraction Layer) libraries

Enable or disable specific types of interrupts:

// enable interrupts
HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
// disable interrupts
HAL_NVIC_DisableIRQ(IRQn_Type IRQn);

See, for example: "stm/stm32f2xx/st_hal_v1.1.3/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c/.h" - definitions for those functions above are in those files. See them online:

  1. https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Inc/stm32f2xx_hal_cortex.h#L264-L265
  2. https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c#L178-L210

Here are the definitions of HAL_NVIC_EnableIRQ() and HAL_NVIC_DisableIRQ(). Notice that they just check to ensure your IRQn is valid, then they pass the input argument on to the ARM-core CMSIS NVIC_EnableIRQ() and NVIC_DisableIRQ() functions above!:

/**
* @brief Enables a device specific interrupt in the NVIC interrupt controller.
* @note To configure interrupts priority correctly, the NVIC_PriorityGroupConfig()
* function should be called before.
* @param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f2xxxx.h))
* @retval None
*/
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));

/* Enable interrupt */
NVIC_EnableIRQ(IRQn);
}

/**
* @brief Disables a device specific interrupt in the NVIC interrupt controller.
* @param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f2xxxx.h))
* @retval None
*/
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));

/* Disable interrupt */
NVIC_DisableIRQ(IRQn);
}

For IRQn_Types: see the appropriate definition file for your specific board! These are board-specific definitions, for your board from your manufacturer. Here are all of the boards in the STM32 F2xx line, for instance: https://github.com/STMicroelectronics/STM32CubeF2/tree/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include. Let's look at the stm32f217xx.h file specifically:

  1. https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
  2. Raw view (since file is too big to view on GitHub otherwise): https://raw.githubusercontent.com/STMicroelectronics/STM32CubeF2/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h

From this file, we can see the typedef enum definition for the IRQn_Type, which is the "STM32F2XX Interrupt Number Definition". Here is what it looks like:

/**
* @brief STM32F2XX Interrupt Number Definition, according to the selected device
* in @ref Library_configuration_section
*/
typedef enum
{
/****** Cortex-M3 Processor Exceptions Numbers ****************************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
HardFault_IRQn = -13, /*!< 3 Hard Fault Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M3 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M3 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M3 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers **********************************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMP_STAMP_IRQn = 2, /*!< Tamper and TimeStamp interrupts through the EXTI line */
RTC_WKUP_IRQn = 3, /*!< RTC Wakeup interrupt through the EXTI line */
FLASH_IRQn = 4, /*!< FLASH global Interrupt */
RCC_IRQn = 5, /*!< RCC global Interrupt */
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
DMA1_Stream0_IRQn = 11, /*!< DMA1 Stream 0 global Interrupt */
DMA1_Stream1_IRQn = 12, /*!< DMA1 Stream 1 global Interrupt */
DMA1_Stream2_IRQn = 13, /*!< DMA1 Stream 2 global Interrupt */
DMA1_Stream3_IRQn = 14, /*!< DMA1 Stream 3 global Interrupt */
DMA1_Stream4_IRQn = 15, /*!< DMA1 Stream 4 global Interrupt */
DMA1_Stream5_IRQn = 16, /*!< DMA1 Stream 5 global Interrupt */
DMA1_Stream6_IRQn = 17, /*!< DMA1 Stream 6 global Interrupt */
ADC_IRQn = 18, /*!< ADC1, ADC2 and ADC3 global Interrupts */
CAN1_TX_IRQn = 19, /*!< CAN1 TX Interrupt */
CAN1_RX0_IRQn = 20, /*!< CAN1 RX0 Interrupt */
CAN1_RX1_IRQn = 21, /*!< CAN1 RX1 Interrupt */
CAN1_SCE_IRQn = 22, /*!< CAN1 SCE Interrupt */
EXTI9_5_IRQn = 23, /*!< External Line[9:5] Interrupts */
TIM1_BRK_TIM9_IRQn = 24, /*!< TIM1 Break interrupt and TIM9 global interrupt */
TIM1_UP_TIM10_IRQn = 25, /*!< TIM1 Update Interrupt and TIM10 global interrupt */
TIM1_TRG_COM_TIM11_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */
TIM2_IRQn = 28, /*!< TIM2 global Interrupt */
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
TIM4_IRQn = 30, /*!< TIM4 global Interrupt */
I2C1_EV_IRQn = 31, /*!< I2C1 Event Interrupt */
I2C1_ER_IRQn = 32, /*!< I2C1 Error Interrupt */
I2C2_EV_IRQn = 33, /*!< I2C2 Event Interrupt */
I2C2_ER_IRQn = 34, /*!< I2C2 Error Interrupt */
SPI1_IRQn = 35, /*!< SPI1 global Interrupt */
SPI2_IRQn = 36, /*!< SPI2 global Interrupt */
USART1_IRQn = 37, /*!< USART1 global Interrupt */
USART2_IRQn = 38, /*!< USART2 global Interrupt */
USART3_IRQn = 39, /*!< USART3 global Interrupt */
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
RTC_Alarm_IRQn = 41, /*!< RTC Alarm (A and B) through EXTI Line Interrupt */
OTG_FS_WKUP_IRQn = 42, /*!< USB OTG FS Wakeup through EXTI line interrupt */
TIM8_BRK_TIM12_IRQn = 43, /*!< TIM8 Break Interrupt and TIM12 global interrupt */
TIM8_UP_TIM13_IRQn = 44, /*!< TIM8 Update Interrupt and TIM13 global interrupt */
TIM8_TRG_COM_TIM14_IRQn = 45, /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
TIM8_CC_IRQn = 46, /*!< TIM8 Capture Compare Interrupt */
DMA1_Stream7_IRQn = 47, /*!< DMA1 Stream7 Interrupt */
FSMC_IRQn = 48, /*!< FSMC global Interrupt */
SDIO_IRQn = 49, /*!< SDIO global Interrupt */
TIM5_IRQn = 50, /*!< TIM5 global Interrupt */
SPI3_IRQn = 51, /*!< SPI3 global Interrupt */
UART4_IRQn = 52, /*!< UART4 global Interrupt */
UART5_IRQn = 53, /*!< UART5 global Interrupt */
TIM6_DAC_IRQn = 54, /*!< TIM6 global and DAC1&2 underrun error interrupts */
TIM7_IRQn = 55, /*!< TIM7 global interrupt */
DMA2_Stream0_IRQn = 56, /*!< DMA2 Stream 0 global Interrupt */
DMA2_Stream1_IRQn = 57, /*!< DMA2 Stream 1 global Interrupt */
DMA2_Stream2_IRQn = 58, /*!< DMA2 Stream 2 global Interrupt */
DMA2_Stream3_IRQn = 59, /*!< DMA2 Stream 3 global Interrupt */
DMA2_Stream4_IRQn = 60, /*!< DMA2 Stream 4 global Interrupt */
ETH_IRQn = 61, /*!< Ethernet global Interrupt */
ETH_WKUP_IRQn = 62, /*!< Ethernet Wakeup through EXTI line Interrupt */
CAN2_TX_IRQn = 63, /*!< CAN2 TX Interrupt */
CAN2_RX0_IRQn = 64, /*!< CAN2 RX0 Interrupt */
CAN2_RX1_IRQn = 65, /*!< CAN2 RX1 Interrupt */
CAN2_SCE_IRQn = 66, /*!< CAN2 SCE Interrupt */
OTG_FS_IRQn = 67, /*!< USB OTG FS global Interrupt */
DMA2_Stream5_IRQn = 68, /*!< DMA2 Stream 5 global interrupt */
DMA2_Stream6_IRQn = 69, /*!< DMA2 Stream 6 global interrupt */
DMA2_Stream7_IRQn = 70, /*!< DMA2 Stream 7 global interrupt */
USART6_IRQn = 71, /*!< USART6 global interrupt */
I2C3_EV_IRQn = 72, /*!< I2C3 event interrupt */
I2C3_ER_IRQn = 73, /*!< I2C3 error interrupt */
OTG_HS_EP1_OUT_IRQn = 74, /*!< USB OTG HS End Point 1 Out global interrupt */
OTG_HS_EP1_IN_IRQn = 75, /*!< USB OTG HS End Point 1 In global interrupt */
OTG_HS_WKUP_IRQn = 76, /*!< USB OTG HS Wakeup through EXTI interrupt */
OTG_HS_IRQn = 77, /*!< USB OTG HS global interrupt */
DCMI_IRQn = 78, /*!< DCMI global interrupt */
CRYP_IRQn = 79, /*!< CRYP crypto global interrupt */
HASH_RNG_IRQn = 80 /*!< Hash and Rng global interrupt */
} IRQn_Type;

2.A. Example usage using STM32 HAL:

To get exclusive access (to ensure strings are atomically printed, for instance) to the USART1 for printing debug chars via a HAL-based blocking (polled) mode (ie: via HAL_UART_Transmit()), you need to disable all interrupts for USART1_IRQn by doing the following. (This guarantees you get atomic access to this device):

// 1. Disable the UART IRQ
HAL_NVIC_DisableIRQ(USART1_IRQn);

// 2. Send your string (in HAL blocking/polled mode)
// Prototype for this function is from
// "...stm/stm32f7xx/st_hal_v1.1.2/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_uart.c":
// - `HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
// uint8_t *pData, uint16_t Size, uint32_t Timeout)`
// Note: `huart` is defined by STM32CubeMX as `UART_HandleTypeDef huart1;`
// in "hal_source/Src/usart.c"
HAL_UART_Transmit(&huart1, (uint8_t *)my_str, strlen(my_str), HAL_MAX_DELAY);

// 3. Enable the UART_IRQ
// FUTURE WORK: make this nestable/more robust by only enabling the
// IRQ here if it was previously enabled before disabling it!
HAL_NVIC_EnableIRQ(USART1_IRQn);

3. Via FreeRTOS:

The FreeRTOS atomic-access-guard / interrupt-related functions are listed under the "Modules" section of the Kernel Control API here: Kernel Control:

taskYIELD()
taskENTER_CRITICAL() // interrupts off
taskEXIT_CRITICAL() // restore interrupts
taskENTER_CRITICAL_FROM_ISR() // interrupts off
taskEXIT_CRITICAL_FROM_ISR() // restore interrupts
taskDISABLE_INTERRUPTS() // interrupts off
taskENABLE_INTERRUPTS() // interrupts on

3.A. Higher-level macros:

  1. These are the preferred macros to use, and are the freertos-recommended ones!
  2. These all support nested calls, and end up calling portDISABLE_INTERRUPTS() anyway, which is the port implementation of the lower-level taskDISABLE_INTERRUPTS(), shown below.
  3. From: https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html
    taskENTER_CRITICAL()        // interrupts off
    taskEXIT_CRITICAL() // restore interrupts
  4. From: https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html
    taskENTER_CRITICAL_FROM_ISR()
    taskEXIT_CRITICAL_FROM_ISR()

3.B. Lower-level macros:

  1. These do NOT support nested calls!

  2. Official documentation on them is on the main "Kernel Control" page:

    taskDISABLE_INTERRUPTS()
    taskENABLE_INTERRUPTS()
  3. Notes and limiatations:

    1. taskDISABLE_INTERRUPTS() at the link above states:

    Normally this macro would not be called directly and taskENTER_CRITICAL() and taskEXIT_CRITICAL() should be used in its place.

    1. taskENABLE_INTERRUPTS() at the link above states:

    Normally this macro would not be called directly and taskENTER_CRITICAL() and taskEXIT_CRITICAL() should be used in its place.

    1. Note also that the use of taskDISABLE_INTERRUPTS() is demonstrated as the technique used to panic inside an example macro definition for configASSERT().
      1. From here: https://www.freertos.org/a00110.html#configASSERT, when used with a debugger, it is defined as:
        /* Define configASSERT() to disable interrupts and sit in a loop. */
        #define configASSERT( ( x ) ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
      2. My thoughts: perhaps in this one case (ie: hard asserting, or panicking), taskDISABLE_INTERRUPTS() might be preferred over taskENTER_CRITICAL() because no amount of calling taskEXIT_CRITICAL() from another thread will re-enable interrupts once taskDISABLE_INTERRUPTS() has been called [I think!?]--rather, one would have to explicitly (and accidentally) call taskENABLE_INTERRUPTS() (ex: from another thread) to re-enable interrupts once taskDISABLE_INTERRUPTS() has been called. In other words, using the low-level taskDISABLE_INTERRUPTS() call is appropriate here because it will truly cause the system to sit in a loop, as desired, whereas taskENTER_CRITICAL() would not.

3.C. Mutexes and other OS (Operating System)-enabled synchronization primitives

Beyond the examples above, you can also use FreeRTOS queues (which are thread-safe, unlike all containers in the C++ std library), mutexes, semaphores, task notifications, and other synchronization primitives, where able and where appropriate, to protect certain data which is shared between FreeRTOS tasks (threads), assuming you are running FreeRTOS.

See the list of these tools here: https://www.freertos.org/a00106.html, and in the left-hand navigation menus once you click on that link.

4. TODO: mutex primitives: raw, OS-free spin locks via atomic set_and_test() (read, modify, write) instructions

  1. Add an atomic test_and_set() (set_and_test() or read_modify_write() really makes more sense as a function name for this, I think) demo using ARM-core CMSIS functions, or assembly, or whatever means necessary, to demonstrate writing a spin lock in STM32. I don't know how to do this yet so it will require finding the right function or operation to use. See here: https://en.wikipedia.org/wiki/Test-and-set#Pseudo-C_implementation_of_a_spin_lock:

    volatile int lock = 0;

    void critical() {
    // Spin lock: loop forever until we get the lock; we know the lock was
    // successfully obtained after exiting this while loop because the
    // test_and_set() function locks the lock and returns the previous lock
    // value. If the previous lock value was 1 then the lock was **already**
    // locked by another thread or process. Once the previous lock value
    // was 0, however, then it indicates the lock was **not** locked before we
    // locked it, but now it **is** locked because we locked it, indicating
    // we own the lock.
    while (test_and_set(&lock) == 1);
    critical section // only one process can be in this section at a time
    lock = 0; // release lock when finished with the critical section
    }

    Here is a spin lock implementation I did in C11 using _Atomic types. It should work just fine for STM32 as well, and probably compiles to use the underlying exclusive STREX/LDREX operations to store (write) and read (load), but I'd have to check that by looking at the assembly. Additionally, this implementation would need to be modified to add safety anti-deadlock mechanisms such as automatic deferral, timeout, and retry, to prevent deadlock. See my notes here: Add basic mutex (lock) and spin lock implementations in C11, C++11, AVR, and STM32

5. See also:

  1. My answer on this for AVR mcus/Arduino
  2. My answer on the general practice and demo of using atomic access guards, and my doAtomicRead() func which ensures atomic access withOUT turning interrupts off
  3. [my Q&A] Which variable types/sizes are atomic on STM32 microcontrollers?
  4. [my answer] Programing STM32 like STM8 (register-level GPIO)

Which variable types/sizes are atomic on STM32 microcontrollers?

For the final, definitive answer to this question, jump straight down to the section below titled "Final answer to my question".

UPDATE 30 Oct. 2018: I was accidentally referencing the (slightly) wrong documents (but which said the exact same thing), so I've fixed them in my answer here. See "Notes about the 30 Oct. 2018 changes" at bottom of this answer for details.

I definitely don't understand every word here, but the ARM v7-M Architecture Reference Manual (Online source; PDF file direct download) (NOT the Technical Reference Manual [TRM], since it doesn't discuss atomicity) validates my assumptions:

enter image description here

So...I think my 7 assumptions at the bottom of my question are all correct. [30 Oct. 2018: Yes, that is correct. See below for details.]


UPDATE 29 Oct. 2018:

One more little tidbit:

Richard Barry, FreeRTOS founder, expert, and core developer, states in tasks.c...

/* A critical section is not required because the variables are of type BaseType_t. */

...when reading an "unsigned long" (4-byte) volatile variable on STM32. This means that he, at least, is 100% sure 4-byte reads and writes are atomic on STM32.



Related Topics



Leave a reply



Submit