Search topics...
CPU FundamentalsPipeline & Barriersfoundational

You enable a peripheral clock and immediately write to its registers. What could go wrong?

0 upvotes
Practice with AISoon
Study the fundamentals first — CPU Fundamentals topic page

The peripheral clock enable is a write to the RCC (Reset and Clock Control) register on the AHB bus. This write enters the CPU's write buffer and propagates through the bus matrix to the RCC peripheral. The RCC then enables the clock to the target peripheral, but this takes additional time — the clock signal must propagate through clock distribution logic and reach the peripheral's clock domain. Meanwhile, the CPU has already moved on to the next instruction: a write to the peripheral's configuration register.

If the peripheral's clock has not arrived by the time the configuration write reaches the peripheral, the write is silently lost. The peripheral was not clocked and therefore could not latch the data. There is no bus error, no fault, no error flag — the write simply vanishes. The peripheral remains in its default/reset state while the firmware believes it is configured. This is one of the most common and difficult-to-diagnose bugs in ARM Cortex-M firmware, because it is timing-dependent: it may work reliably on one board revision (slightly different PCB trace lengths) and fail on another, or work in debug mode (slower execution) and fail in release builds (full optimization).

The fix is straightforward: insert a __DSB() (Data Synchronization Barrier) between the clock enable and the first peripheral register access:

c
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
__DSB(); // Wait for clock enable to propagate
GPIOA->MODER = 0x01; // Now safe to configure

The DSB ensures the clock enable write has completed — fully propagated through the write buffer and bus fabric — before any subsequent memory operation begins. Some engineers use a dummy read-back of the RCC register ((void)RCC->AHB1ENR;) as an alternative, since a read forces the bus to complete the pending write. Both approaches work, but __DSB() is the architecturally correct solution and is what ARM recommends in the Cortex-M programming manuals.

Source: CPU Fundamentals Q&A