Embedded Systems · Microcontrollers
Clock Gating, Peripheral Enable Order and the Write That Disappears
Why a peripheral register write does nothing until its bus clock is enabled, why enabling order matters for parts with dependency chains, and how to diagnose the silent failure.
A peripheral that is not clocked is not a peripheral that is slow. It is a block of flip-flops with no clock edge, and a register write that reaches it changes nothing. The write still completes on the bus and the bus returns a normal response, so no fault is raised and no error counter increments. Firmware that reads the register back to confirm the write may see a plausible value rather than an error, and on some parts that read-back is the only evidence available. Either way the value is not in the peripheral, and the peripheral is not running.
This is one of the most frequent bring-up failures on a modern microcontroller, and it is a direct consequence of the power-saving architecture rather than a defect.
Why the clock is a switch, not a throttle
A peripheral’s clock is distributed through a gate that the power controller can close. Closing it stops the flip-flops in that block from toggling, which removes both their dynamic power and the leakage associated with their clock tree. On a part with dozens of peripherals, only a handful are active at any moment, and the difference between all-clocked and selectively-clocked can be a large fraction of the total current.
The gate is not transparent to the bus. Each peripheral has an enable bit in a clock-enable register, and the bit controls a gate between the bus clock and the peripheral’s internal clock. Before the bit is set, the peripheral’s register file is still reachable through the bus — the address decoder still answers — but the flip-flops holding the configuration do not update. The register file’s storage is either held in reset or powered down, and what the bus reads back is not what the peripheral is using.
The enable registers themselves live in the system block, which is clocked by a clock that is always on. That is why the enable bit can be written at all: the register containing the switch is outside the thing being switched.
The reset value is not the configuration value
Most peripheral control registers reset to zero, and zero usually means disabled. A timer with a zero prescaler register may count at the full clock or may be held stopped depending on the peripheral; a UART with a zero baud divisor may produce no output or garbage; a GPIO with zero mode bits may be an input with no pull. The pattern is that the reset state is the safe, inert state, and firmware must move every register it depends on out of it.
The corollary is that enabling the clock and configuring the peripheral are two separate acts, and the order between them is fixed: clock first, then configuration. Configuration written before the clock is enabled is lost, and the loss is silent.
A second-order version of the same problem is the peripheral that requires its clock to be enabled before its reset is released, or vice versa. Some parts hold a peripheral in reset until a separate reset bit is cleared, and a write to the reset-release register while the clock is off has no effect because the register being written is itself inside the clocked domain. The symptom is a peripheral that responds to reads but never to writes even after the clock enable bit is set, and the fix is to enable the clock, release the reset, and then wait for the peripheral’s own ready indication if it has one.
Dependency chains between peripherals
The interesting failures involve peripherals that are clocked from another peripheral’s output rather than from the system clock.
A timer that drives an ADC’s trigger input needs the timer’s clock enabled before the ADC sees a trigger, and the ADC needs its own clock enabled before it can respond to one. An I2S peripheral clocked from a shared audio PLL needs that PLL locked and routed before the peripheral’s clock enable bit has any effect; setting the enable first produces a peripheral with a valid clock enable and no clock, which reads back as enabled and does nothing.
A serial peripheral whose baud rate is derived from a fractional divider on the bus clock needs the bus clock’s frequency to be known at the moment the divisor is computed. If the clock tree is reconfigured after the divisor is set, the divisor is stale and the baud rate is wrong by the ratio of the two frequencies. The failure looks like framing errors that appear the moment the clock tree is reconfigured and persist until the divisor is recomputed, because the divisor was correct for one frequency and the actual clock is now another.
The general rule is that the clock tree has a topological order: source, then divider or PLL, then distribution, then peripheral enable. Configuration that depends on a frequency must be computed after the frequency is final, and configuration that depends on another peripheral must happen after that peripheral is running.
The write buffer and why the read-back lies
On a Cortex-M part, a store to device memory is buffered. The store retires from the core’s point of view once the buffer accepts it, and the actual bus cycle may complete several cycles later. A read of the same address immediately afterwards is ordered after the store, because device memory does not permit the two to be reordered, so the read is issued only once the store has been accepted by the slave.
What the slave then returns depends on the slave. If the peripheral is clocked and its register file implements the field, the read returns the value just written. If the peripheral is not clocked, the read returns whatever the unclocked storage presents — the reset value, a stale value, or zero — and the write that preceded it changed nothing. The read-back therefore proves only that the bus carried the transaction, never that the peripheral accepted it.
Proving that a peripheral actually accepted a configuration requires a register whose value is produced by the peripheral rather than stored. Status registers, ready bits, and counters are such registers. A timer whose counter register reads as zero forever is a timer with no clock; a timer whose counter advances is a timer that is running. When a peripheral has no such register, the only reliable check is behavioural: whether the pin toggles, whether the interrupt fires, whether the transfer completes.
There is a stronger barrier available when ordering must be enforced across a bridge. The architecture provides a memory barrier instruction, and on parts with a posted write buffer between the core and a peripheral bus, a barrier after a configuration write prevents later transactions from being issued before that write has been accepted downstream. Barriers cost cycles and are only needed where the ordering matters; a store followed by a load of the same address is already ordered by the device-memory rules described in the memory map article.
Sleep modes invert the problem
The same gate that makes bring-up fragile makes low-power operation possible, and the two interact. Entering a sleep mode closes gates on peripherals that the mode does not keep alive, and the set of survivors is mode-specific. A UART that must wake the core from a character needs its clock to remain enabled in the deepest mode that still listens, and the part’s documentation lists which peripherals remain clocked in each mode.
The failure mode here is the opposite of bring-up: a system that works at full speed and stops responding after the first sleep. The register contents are usually retained across the mode, so the configuration is still there, but the peripheral is no longer clocked and therefore no longer able to raise the interrupt that would wake the core. The device then sleeps until an external event that nothing is watching for.
Wake-up latency is the other half of the trade. A peripheral whose clock was gated needs its oscillator to restart and settle before it can do anything, and that settling time is measured in microseconds to milliseconds depending on whether the source is an internal RC oscillator or a crystal. A system that gates aggressively pays that latency on every wake, and a system that never gates pays the current continuously. The intermediate design keeps a low-power oscillator running and gates only the peripheral clocks, which is why many parts provide a separate always-on clock domain.
A bring-up order that survives the failure modes
The order that avoids the known traps is:
1. Set flash wait states and voltage scaling for the target frequency.
2. Configure the clock source (internal RC or external crystal) and wait for it to stabilise.
3. Configure the PLL and wait for lock.
4. Set the bus prescalers, then switch the system clock to the PLL.
5. Enable the peripheral's clock in the system block.
6. Release the peripheral's reset, and wait for any ready indication.
7. Configure the peripheral's registers.
8. Configure the pins, which are usually the last thing to become active.
Step 8 last is deliberate. A pin configured as an output before its peripheral is ready drives whatever the reset state of the output data register happens to be, and on a pin connected to a motor driver or a relay that is a visible glitch. Configuring the pin as an input with a defined pull first, then switching it to its peripheral function after the peripheral is configured, keeps the pin inert until the signal it will carry is well defined.
Step 1 first is equally deliberate and is the subject of the flash timing article: the wait-state count must match the frequency the core will run at, and it must be programmed before the frequency changes, not after.
The diagnosis for a peripheral that appears dead follows the same list backwards. Confirm the clock enable bit reads back set. Confirm the reset bit is released. Confirm the pin is in the right mode. Confirm the peripheral’s own status register shows activity rather than only showing the values that were written to it. The last check is the one that distinguishes “configured” from “running”, and it is the only one that cannot be satisfied by a register file that is holding a value nobody is using.
References
- ARMv7-M Architecture Reference Manual (ARM DDI 0403) — Arm Limited
- Cortex-M4 Devices Generic User Guide (ARM DUI 0553) — Arm Limited
- CMOS VLSI Design: A Circuits and Systems Perspective — Weste & Harris, Pearson