HomeAbout UsContact Us

Bit-Banding in Embedded C — Atomic GPIO Manipulation Without Locks

By Jithin Tom
Published in Embedded C/C++
June 19, 2026
3 min read
Bit-Banding in Embedded C — Atomic GPIO Manipulation Without Locks

Table Of Contents

01
The Problem with Read-Modify-Write
02
How Bit-Banding Works
03
Defining Bit-Band Aliases in C
04
Why This Matters in Real Systems
05
Performance Characteristics
06
Limitations and Considerations
07
Summary
08
References
09
Frequently Asked Questions

Setting or clearing a single GPIO pin seems trivial until an interrupt service routine (ISR) needs to toggle the same pin from a different context. The classic read-modify-write sequence — load the port register, OR or AND with a mask, store back — is inherently non-atomic. If an ISR fires between the load and the store, its modification is silently lost when the main loop writes the stale value back. On ARM Cortex-M processors, the bit-band region solves this problem in hardware.

The Problem with Read-Modify-Write

Consider the standard approach to setting pin 5 of GPIOA on an STM32:

// NON-ATOMIC: vulnerable to race conditions
GPIOA->ODR |= (1 << 5); // Set pin 5

This compiles to three instructions: load the ODR register, OR with the mask, store the result back. If an ISR modifies a different bit in the same ODR register between the load and the store, the ISR’s change is overwritten. The typical workaround is to disable interrupts around the operation, but that adds latency and complexity.

// Safe but costly: disables all interrupts
__disable_irq();
GPIOA->ODR |= (1 << 5);
__enable_irq();

For a single bit write, disabling all interrupts is overkill. Bit-banding provides a better solution.

How Bit-Banding Works

ARM Cortex-M3 and M4 cores map two peripheral and SRAM regions to alias regions where each bit of the original region is expanded into a full 32-bit word. Writing 1 to that word sets the corresponding bit in the original region; writing 0 clears it. The operation is atomic at the bus level — no read-modify-write, no race conditions. (Note: Newer architectures like Cortex-M7 and Cortex-M33 have dropped bit-banding in favor of other atomic mechanisms).

The alias address is computed with a simple formula:

alias_addr = bit_band_base + (byte_offset_from_region * 32) + (bit_number * 4)

For the STM32F4 GPIOA ODR register at 0x40020014, the bit-band base for the peripheral region is 0x42000000. To access bit 1 of GPIOA ODR:

byte_offset = 0x40020014 - 0x40000000 = 0x20014
bit_number = 1
alias_addr = 0x42000000 + (0x20014 * 32) + (1 * 4)
= 0x42000000 + 0x00400280 + 0x00000004
= 0x42400284
Bit-Band Alias Address Computation
+-------------------------+ +-------------------------+
| Peripheral Region | | Bit-Band Alias Region |
| Base: 0x40000000 | | Base: 0x42000000 |
| | | |
| GPIOA_ODR @ 0x40020014 | | Each bit -> 32-bit word |
| | | |
| bit 7 | bit 6 | ... | | WORD | WORD | ... |
| bit 1 | bit 0 | | for b7 | for b0 |
| | | |
+-------------------------+ +-------------------------+
| |
| alias = base + (offset * 32) |
| + (bit_num * 4) |
+---------------------------------> 0x42400284

Defining Bit-Band Aliases in C

The cleanest approach is to define macros that compute the alias addresses at compile time:

// Bit-band alias macros for STM32 Cortex-M
// Works on Cortex-M3 and M4 (ARMv7-M)
#define PERIPH_BITBAND_ADDR(addr, bit) \
(((uint32_t)(addr) - 0x40000000) * 32 + (bit) * 4 + 0x42000000)
#define SRAM_BITBAND_ADDR(addr, bit) \
(((uint32_t)(addr) - 0x20000000) * 32 + (bit) * 4 + 0x22000000)
// Convenience: define a single bit-band alias
#define BITBAND_PERIPH(addr, bit) \
(*(volatile uint32_t *)PERIPH_BITBAND_ADDR((uint32_t)addr, bit))
// Usage: atomic GPIO pin control on STM32F4
#define GPIOA_PIN1 BITBAND_PERIPH(&GPIOA->ODR, 1)
#define GPIOA_PIN5 BITBAND_PERIPH(&GPIOA->ODR, 5)
#define GPIOB_PIN12 BITBAND_PERIPH(&GPIOB->ODR, 12)

With these macros, setting or clearing a pin is a single atomic store instruction:

void gpio_init(void) {
/* Enable GPIOA clock */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
/* Configure PA1 as push-pull output, high speed */
GPIOA->MODER &= ~(3U << (1 * 2)); /* Clear mode bits */
GPIOA->MODER |= (1U << (1 * 2)); /* Set output mode */
GPIOA->OSPEEDR |= (3U << (1 * 2)); /* High speed */
}
int main(void) {
gpio_init();
while (1) {
GPIOA_PIN1 = 1; /* Atomic SET — single STR instruction */
delay_ms(500);
GPIOA_PIN1 = 0; /* Atomic CLEAR — single STR instruction */
delay_ms(500);
}
}

The compiler generates a single STR instruction for each write — no read, no modify, no race window.

Why This Matters in Real Systems

Bit-banding shines in scenarios where multiple contexts share GPIO pins or where timing precision is critical.

ISR-safe signaling. A timer ISR toggles a pin for a logic analyzer trigger while the main loop drives the same port for status LEDs. Without bit-banding, the main loop’s read-modify-write could corrupt the ISR’s pin state. With bit-banding, each context writes only its own bit — no interference.

Deterministic timing. Disabling interrupts adds jitter to the system response. Bit-banding eliminates the need for interrupt masking in single-bit GPIO operations, keeping interrupt latency predictable.

Multi-pin protocols. Bit-banging a communication protocol (like a software I2C or SPI master) requires precise timing on individual pins. Bit-banding ensures each pin transition is atomic and has deterministic cycle count.

// Bit-Band GPIO for Software SPI Master
+-----------+ +-----------+
| MCU | | Sensor |
| | | |
| PA5 (SCK) |------------------------->| SCK |
| PA6 (MISO)|<-------------------------| DO |
| PA7 (MOSI)|------------------------->| DI |
| PA4 (CS) |------------------------->| CS |
+-----------+ +-----------+
// Bit-band aliases for software SPI
#define SPI_SCK BITBAND_PERIPH(&GPIOA->ODR, 5)
#define SPI_MISO BITBAND_PERIPH(&GPIOA->IDR, 6)
#define SPI_MOSI BITBAND_PERIPH(&GPIOA->ODR, 7)
#define SPI_CS BITBAND_PERIPH(&GPIOA->ODR, 4)
uint8_t spi_transfer(uint8_t tx_byte) {
uint8_t rx_byte = 0;
for (int i = 7; i >= 0; i--) {
/* Set MOSI based on current bit */
SPI_MOSI = (tx_byte >> i) & 1;
/* Clock high */
SPI_SCK = 1;
/* Read MISO */
rx_byte |= (SPI_MISO & 1) << i;
/* Clock low */
SPI_SCK = 0;
}
return rx_byte;
}

Performance Characteristics

On a Cortex-M4, a bit-band store compiles to a single STR instruction — 2 clock cycles. Compare this to the read-modify-write approach:

OperationInstructionsCycles (Cortex-M4)Atomic
GPIOA->ODR \|= (1 << 5)LDR, ORR, STR6No
GPIOA_PIN5 = 1 (bit-band)STR2Yes
__disable_irq() + RMW + __enable_irq()CPSID, LDR, ORR, STR, CPSIE10+Yes

The bit-band approach is both faster and atomic — a rare combination where correctness and performance align.

Limitations and Considerations

Architecture-specific. Bit-banding is available primarily on ARM Cortex-M3 and M4 cores. Cortex-M0 and M0+ do not implement it to save silicon area. Furthermore, newer architectures like Cortex-M7 and ARMv8-M (e.g., Cortex-M33) have dropped support for bit-banding in favor of more standard atomic mechanisms. Always check the device’s Technical Reference Manual for bit-band region addresses.

Alternatives on Newer Cores. For processors without bit-banding, developers should use:

  • Load-Exclusive / Store-Exclusive (LDREX / STREX): Standard ARM instructions for atomic read-modify-write sequences.
  • Hardware registers: Many peripherals on modern MCUs provide hardware-level atomic registers (e.g., BSRR for GPIOs).

Peripheral and SRAM only. The bit-band regions cover 0x40000000–0x400FFFFF (peripheral) and 0x20000000–0x200FFFFF (SRAM) on most STM32 devices. Registers outside these ranges cannot use bit-banding.

Single-bit only. Bit-banding operates on individual bits. If you need to write multiple bits atomically, consider using the BSRR (Bit Set/Reset Register) available on STM32 GPIO peripherals, which provides atomic set and reset of multiple bits in a single write.

// BSRR: atomic multi-bit set/reset (alternative to bit-band)
// Upper 16 bits = reset, lower 16 bits = set
GPIOA->BSRR = (1 << 5) | (1 << 12); // Set pins 5 and 12
GPIOA->BSRR = (1 << 21) | (1 << 28); // Reset pins 5 and 12

Summary

Bit-banding is a powerful hardware feature of early ARM Cortex-M processors (primarily M3 and M4) that maps individual bits to 32-bit alias addresses, enabling atomic single-bit writes without read-modify-write sequences or interrupt masking. The technique is invaluable for ISR-safe GPIO manipulation, deterministic pin timing, and software-implemented communication protocols on these platforms. While newer architectures like Cortex-M7 and Cortex-M33 have moved away from bit-banding in favor of exclusive access instructions (LDREX/STREX), understanding and utilizing bit-banding remains essential for maintaining and optimizing legacy Cortex-M3/M4 systems. By defining clean macros for alias addresses, embedded C code gains both correctness and performance — a combination that is hard to achieve with software workarounds alone.

References

  1. ARM Limited, “Cortex-M4 Technical Reference Manual,” Revision r0p1, Section 4: Memory Model — Bit-Banding, 2010. [Online]. Available: https://developer.arm.com/documentation/ddi0439/b/

  2. ARM Limited, “ARMv7-M Architecture Reference Manual,” Section B3.2: Bit-banding, 2016. [Online]. Available: https://developer.arm.com/documentation/ddi0403/latest/

  3. STMicroelectronics, “PM0214: STM32 Cortex-M4 MCUs and MPUs Programming Manual,” Section 2.2.5: Bit-banding.

  4. Joseph Yiu, The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors, 3rd ed. Newnes, 2014, Chapter 2: Memory Model — Bit-Band Operations.

Frequently Asked Questions

What is bit-banding and why is it used in embedded systems?

Bit-banding maps individual bits in a memory region to complete 32-bit words in another region (the bit-band alias). Writing to this alias address modifies the single bit atomically without requiring a read-modify-write cycle.

How does bit-banding provide atomic operations without locks?

Since the hardware memory controller translates the single write to the alias address into a hardware-level atomic bit operation, interrupt service routines (ISRs) cannot preempt the task mid-operation, avoiding race conditions.

Is bit-banding supported on all ARM microcontrollers?

No, bit-banding is an optional feature of Cortex-M3 and Cortex-M4 processors. It is generally not present in Cortex-M0, Cortex-M0+, or Cortex-M7 cores, which rely on other atomic synchronization mechanisms.

Tags

embedded-cbit-bandinggpioatomicarmcortex-mbit-manipulation

Share


Previous Article
Building a Personal Brand as an Embedded Engineer
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Stack Usage Analysis and Optimization in Embedded C
Stack Usage Analysis and Optimization in Embedded C
July 12, 2026
3 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media