SoC
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
  • Arm Cortex M3
  • Contact
Reading: How to implement atomic operations on multi-core Cortex-M0/M0+?
SUBSCRIBE
SoCSoC
Font ResizerAa
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
Search
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
Have an existing account? Sign In
Follow US
  • Looking for Something?
  • Privacy Policy
  • About Us
  • Sitemap
  • Contact Us
© S-O-C.ORG, All Rights Reserved.
Arm

How to implement atomic operations on multi-core Cortex-M0/M0+?

Holly Lindsey
Last updated: September 15, 2023 12:00 pm
Holly Lindsey 7 Min Read
Share
SHARE

Atomic operations allow thread-safe access to shared resources without the use of locks in multi-threaded systems. On Cortex-M0/M0+ multi-core microcontrollers, atomic operations can be implemented using special instructions like LDREX and STREX or by disabling interrupts during critical sections. The choice depends on performance requirements and how critical the shared resource is.

Contents
What are atomic operations?Why use atomic operations?Challenges of atomicity on Cortex-M0/M0+Using LDREX and STREXAdvantagesDisadvantagesDisabling InterruptsAdvantagesDisadvantagesImplementing atomic countersLDREX / STREX (M0+ only)Disable interruptsImplementing thread-safe queuesLDREX / STREX (M0+ only)Disable interruptsConclusion

What are atomic operations?

An atomic operation is a series of instructions that is guaranteed to execute atomically, meaning no other thread can observe or modify the state in the middle of the atomic operation. This avoids race conditions and ensures data consistency in multi-threaded environments without using locks.

Atomic operations typically read, modify and write a value to memory. The read-modify-write sequence is guaranteed to be uninterrupted. For example, atomic increment involves reading a value, incrementing it, and writing back the result atomically.

Why use atomic operations?

Atomic operations are useful for implementing thread-safe data structures like queues and maps that are accessed by multiple threads. They allow safe modification of shared data without locks. This improves performance and scalability in multi-core systems.

Locks have some disadvantages likepriority inversion, deadlock, and performance bottlenecks. Atomic operations avoid these by ensuring atomic access without locks.

Challenges of atomicity on Cortex-M0/M0+

Implementing atomic operations efficiently on Cortex-M0/M0+ can be challenging because:

  • M0/M0+ lacks cache making atomicity harder to guarantee
  • NoLoad/Store exclusive instructions like on Cortex-M3/M4
  • Few registers make implementing software routines tricky
  • Interrupt latency can disturb atomicity

Using LDREX and STREX

Cortex-M0+ provides LDREX (Load Exclusive Register) and STREX (Store Exclusive Register) instructions that can be used to implement atomic operations.

LDREX loads a value from memory into a register exclusively. This reserves the memory location. STREX will then store a value to that location and set a status flag if the store succeeded.

If no other access occurred between LDREX and STREX, the store succeeds atomically. The sequence can be retried if interrupted by an interrupt. LDREX R1, [R2] // Load exclusively … // operate on value … STREX R3, R1, [R2] // Attempt to store result atomically CBZ R3, done // Retry if interrupted

This construct implements an atomic read-modify-write sequence. M0 does not have LDREX/STREX support so cannot directly use this method.

Advantages

  • Faster than disabling interrupts
  • Does not affect interrupt latency like disabling does

Disadvantages

  • Retries incur overhead if operations are frequently interrupted
  • Only works for single word operations

Disabling Interrupts

All Cortex-M cores allow disabling interrupts by setting the PRIMASK register bit. This prevents preemption during a critical section: CPSID I // Disable interrupts … // Access shared resource CPSIE I // Re-enable interrupts

This approach makes a section of code atomic by preventing context switches. Simple for small critical sections on both M0 and M0+.

Advantages

  • Very easy to implement
  • Works on both M0 and M0+

Disadvantages

  • Increases interrupt latency which can disturb real-time behavior
  • Only suits small critical sections due to latency impact

Implementing atomic counters

A common use of atomics is an atomic counter accessed by multiple threads. This can be implemented on Cortex-M0/M0+ using:

LDREX / STREX (M0+ only)

UINT32 counter; void atomic_increment(void) { UINT32 tmp; UINT32 status; do { LDREX tmp, [counter] ADD tmp, tmp, #1 STREX status, tmp, [counter] } while(status != 0) }

Disable interrupts

UINT32 counter; void atomic_increment(void) { PRIMASK = 1; // Disable interrupts counter++; PRIMASK = 0; // Enable interrupts }

The interrupt disable method works for both M0 and M0+ at the cost of higher latency. LDREX/STREX is faster but only works on M0+. The best method depends on performance requirements.

Implementing thread-safe queues

Thread-safe queues allow safe access from multiple threads without locks. The key operations of enqueue and dequeue must be atomic. This can be done on Cortex-M0/M0+ using:

LDREX / STREX (M0+ only)

struct queue_t { UINT32 *buffer; size_t head; size_t tail; size_t size; }; void enqueue(struct queue_t *q, UINT32 data) { size_t head; do { LDREX head, [q->head] // Calculate new head position … // Check queue is not full … STREX status, head, [q->head] } while(status) // Update tail if wrapped around … q->buffer[head] = data; } UINT32 dequeue(struct queue_t *q) { size_t tail; UINT32 data; do { LDREX tail, [q->tail] // Check queue not empty … // Calculate return data pointer … STREX status, tail, [q->tail] } while(status) data = q->buffer[tail]; // Update head pointer if wrapped around … return data; }

Disable interrupts

void enqueue(struct queue_t *q, UINT32 data) { PRIMASK = 1; // Disable interrupts q->buffer[q->head++] = data; PRIMASK = 0; // Enable interrupts } UINT32 dequeue(struct queue_t *q) { UINT32 data; PRIMASK = 1; // Disable interrupts data = q->buffer[q->tail++]; PRIMASK = 0; // Enable interrupts return data; }

Again LDREX/STREX provides optimal performance on M0+ while interrupt disable gives a simple solution for both M0 and M0+. The queue pointers must be read/written atomically so LDREX/STREX is ideal.

Conclusion

Atomic operations are critical for building reliable multi-threaded applications. On Cortex-M0/M0+ this can be achieved using:

  • LDREX/STREX instructions on M0+ – fast without affecting interrupt latency
  • Disabling interrupts on both M0 and M0+ – simple to implement but affects interrupt latency

The right method depends on performance requirements. For highly critical data, LDREX/STREX is best on M0+ while interrupt disable gives a simple option when sharing is less critical.

Newsletter Form (#3)

More ARM insights right in your inbox

 


Share This Article
Facebook Twitter Email Copy Link Print
Previous Article Which interrupt has the highest priority in arm?
Next Article What is the maximum operating frequency of the 32-bit ARM Cortex-M0+ processor core?
Leave a comment Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

2k Followers Like
3k Followers Follow
10.1k Followers Pin
- Sponsored-
Ad image

You Might Also Like

Reorganising C code to be optimal for Thumb-1 Instruction-Set with Cortex M0+

Cortex-M0+ processors utilize the Thumb-1 instruction set which is optimized…

6 Min Read

What is the startup code of ARM in C?

The startup code of an ARM processor written in C…

8 Min Read

Debugging osDelay() Errors and Incorrect Behavior

The osDelay() function is used in embedded systems programming on…

7 Min Read

What happens after reset and before the Cortex-M processor starts executing the program?

When a Cortex-M processor first powers on or resets, it…

6 Min Read
SoCSoC
  • Looking for Something?
  • Privacy Policy
  • About Us
  • Sitemap
  • Contact Us
Welcome Back!

Sign in to your account