14
votes

Here are the data types on STM32 microcontrollers: http://www.keil.com/support/man/docs/armcc/armcc_chr1359125009502.htm.

These microcontrollers use 32-bit ARM core processors.

Which data types have automatic atomic read and atomic write access?

I'm pretty sure all 32-bit data types do (since the processor is 32-bits), and all 64-bit data types do NOT (since it would take at least 2 processor operations to read or write a 64-bit word), but what about bool (1 byte), and uint16_t/int16_t (2 bytes)?

Context: I'm sharing variables between multiple threads (single core, but multiple threads, or "tasks" as they are called, in FreeRTOS) on the STM32 and need to know if I need to enforce atomic access by turning off interrupts, using mutexes, etc.

UPDATE:

Refering to this sample code:

volatile bool shared_bool;
volatile uint8_t shared u8;
volatile uint16_t shared_u16;
volatile uint32_t shared_u32;
volatile uint64_t shared_u64;
volatile float shared_f; // 32-bits
volatile double shared_d; // 64-bits

// Task (thread) 1
while (1)
{
    // Write to the values in this thread.
    // What I write to each variable will vary. Since other threads
    // are reading these values, I need to ensure my *writes* are atomic, or else
    // I must use a mutex to prevent another thread from reading a variable in the middle
    // of this thread's writing.
    shared_bool = true;
    shared_u8 = 129;
    shared_u16 = 10108;
    shared_u32 = 130890;
    shared_f = 1083.108;
    shared_d = 382.10830;
}

// Task (thread) 2
while (1)
{
    // Read from the values in this thread.
    // What thread 1 writes into these values can change at any time, so I need to ensure
    // my *reads* are atomic, or else I'll need to use a mutex to prevent the other 
    // thread from writing to a variable in the midst of reading
    // it in this thread.
    if (shared_bool == whatever)
    {
        // do something
    }
    if (shared_u8 == whatever)
    {
        // do something
    }
    if (shared_u16 == whatever)
    {
        // do something
    }
    if (shared_u32 == whatever)
    {
        // do something
    }
    if (shared_u64 == whatever)
    {
        // do something
    }
    if (shared_f == whatever)
    {
        // do something
    }
    if (shared_d == whatever)
    {
        // do something
    }
}

In the code above, which variables can I do this for without using a mutex? My suspicion is as follows:

  1. volatile bool: safe--no mutex required
  2. volatile uint8_t: safe--no mutex required
  3. volatile uint16_t: safe--no mutex required
  4. volatile uint32_t: safe--no mutex required
  5. volatile uint64_t: UNSAFE--YOU MUST USE A Critical section or MUTEX!
  6. volatile float: safe--no mutex required
  7. volatile double: UNSAFE--YOU MUST USE A Critical section or MUTEX!

Example critical section with FreeRTOS:
- https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html

// Force atomic access with these critical section atomic access guards.
taskENTER_CRITICAL();
// do the (now guaranteed to be safe) read or write here
taskEXIT_CRITICAL();

Related, but not answering my question:

  1. Atomic operations in ARM
  2. ARM: Is writing/reading from int atomic?
  3. (My own question and answer on atomicity in 8-bit AVR [and Arduino] microcontrollers): https://stackoverflow.com/a/39693278/4561887
  4. https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
3
This would be what the ARM instruction set manual for your particular chip is for?user268396
You have to look at the assembly code.Fiddling Bits
Are you trying to defend against two cores operating on the same data, or being interrupted in the middle of a write to yield to the other thread on the same core?zneak
The latter: "being interrupted in the middle of a write to yield to the other thread on the same core".Gabriel Staples

3 Answers

15
votes

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. He doesn't mention smaller-byte reads, but for 4-byte reads he is conclusively sure. I have to assume that 4-byte variables being the native processor width, and also, word-aligned, is critical to this being true.

From tasks.c, lines 2173-2178 in FreeRTOS v9.0.0, for instance:

UBaseType_t uxTaskGetNumberOfTasks( void )
{
    /* A critical section is not required because the variables are of type
    BaseType_t. */
    return uxCurrentNumberOfTasks;
}

He uses this exact phrase of...

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

...in two different locations in this file.

Final answer to my question: all types <= 4 bytes (all bolded types in the list of 9 rows below) are atomic.

Furthermore, upon closer inspection of the TRM on p141 as shown in my screenshot above, the key sentences I'd like to point out are:

In ARMv7-M, the single-copy atomic processor accesses are:
• all byte accesses.
• all halfword accesses to halfword-aligned locations.
• all word accesses to word-aligned locations.

And, per this link, the following is true for "basic data types implemented in ARM C and C++" (ie: on STM32):

  1. bool/_Bool is "byte-aligned" (1-byte-aligned)
  2. int8_t/uint8_t is "byte-aligned" (1-byte-aligned)
  3. int16_t/uint16_t is "halfword-aligned" (2-byte-aligned)
  4. int32_t/uint32_t is "word-aligned" (4-byte-aligned)
  5. int64_t/uint64_t is "doubleword-aligned" (8-byte-aligned) <-- NOT GUARANTEED ATOMIC
  6. float is "word-aligned" (4-byte-aligned)
  7. double is "doubleword-aligned" (8-byte-aligned) <-- NOT GUARANTEED ATOMIC
  8. long double is "doubleword-aligned" (8-byte-aligned) <-- NOT GUARANTEED ATOMIC
  9. all pointers are "word-aligned" (4-byte-aligned)

This means that I now have and understand the evidence I need to conclusively state that all bolded rows just above have automatic atomic read and write access (but NOT increment/decrement of course, which is multiple operations). This is the final answer to my question. The only exception to this atomicity might be in packed structs I think, in which case these otherwise-naturally-aligned data types may not be naturally aligned.

Also note that when reading the Technical Reference Manual, "single-copy atomicity" apparently just means "single-core-CPU atomicity", or "atomicity on a single-CPU-core architecture." This is in contrast to "multi-copy atomicity", which refers to a "mutliprocessing system", or multi-core-CPU architecture. Wikipedia states "multiprocessing is the use of two or more central processing units (CPUs) within a single computer system" (https://en.wikipedia.org/wiki/Multiprocessing).

My architecture in question, STM32F767ZI (with ARM Cortex-M7 core), is a single-core architecture, so apparently "single-copy atomicity", as I've quoted above from the TRM, applies.

Further Reading:

Notes about the 30 Oct. 2018 changes:

2
votes

Depending what you mean by atomic.

If it is not the simple load or store operation like

a += 1;

then all types are not atomic.

If it is simple store or load oparation 32bits, 16 bits and 8 bits data types are atomic. If the value in the register will have to be normalized 8 & 16 bits store and load may be not atomic.

If your hardware supports bitbanding then if the bitbanding is used the bit operations (set and reset)int the memory areas supporting bitbanding are atomic

Note.

if your code does not allow unaligned operations 8 & 16 bit operations may be not atomic.

0
votes

Atomic "arithmetic" can be processed by CPU Core registers!

It can be any types one or four bytes depends on architecture and instruction set

BUT modification of any variable located in memory take at least 3 system steps: RMW = Read memory to register, Modify register and Write register to memory.

Therefore atomic modification can possible only if you control using of CPU registers it does means need use pure assembler and don't use C or Cpp compiler.

When you use C\Cpp compiler it placed global or global static variable in memory so C\Cpp don't provide any atomic actions and types

Note: you can use for example "FPU registers" for atomic modification (if you really need it), but you must hide from the compiler and RTOS that architecture has FPU.