Embedded Systems · Power Management

Peripheral Clock Gating and Autonomous Transfer

How gating a peripheral clock saves dynamic power, why a gated peripheral still costs leakage, and how DMA lets a conversion happen with the core asleep.

Dynamic power in CMOS is $P = \alpha C V^2 f$: a fraction of gates switch, they charge a capacitance, and they do it at a frequency. Every term in that expression is an attack surface. Clock gating attacks $f$ for the block being gated, and it does so more cheaply than any other technique because it needs no voltage change and no state loss.

What a gated clock removes

A clock gate is an AND gate on the clock path, or a latch-based integrated gate that avoids glitches. When the enable is low, the block’s flip-flops do not receive edges. Their inputs stop changing, so the combinational logic they feed stops switching, and the dynamic power of the whole block falls to the leakage of its transistors.

The savings are proportional to the block’s share of the switching activity. While the core is running it is normally the largest single consumer, but a peripheral that is clocked with nothing to do is pure waste: it burns dynamic power proportional to its size and its clock rate, and gating it costs a register write. A converter clocking conversions, a serial port clocking a stream, and a timer counting at the core frequency all draw current for a result nobody asked for. Gating them when idle is the cheapest dynamic-power saving available in firmware, because it needs no voltage change and loses no state.

The gate does not remove leakage. Every powered transistor still conducts subthreshold current, and the block’s area determines how much. A large peripheral left clocked-off but powered can leak more than a small peripheral that is clocked-on and working. This is why the sequence of a good low-power configuration is: first stop the clocks of everything unused, then remove power from the domains that can afford to lose state, and only then consider the core.

The ordering problem in the enable registers

Peripheral clock enables are usually grouped into a register per bus or per power domain, and the write is a read-modify-write. Two drivers that each do a read-modify-write on the same register from interrupt context can lose each other’s update, and the failure is intermittent and looks like a peripheral that occasionally does not start.

The usual fix is to keep a shadow copy of the register in RAM, modify the shadow under a critical section, and write the whole value. On parts with atomic bit-set and bit-clear aliases, the same effect is achieved without a critical section, which is preferable because it removes the interrupt-latency cost from the path.

There is a second ordering problem: a peripheral’s clock must be enabled before its registers can be written, and on some parts the clock must remain enabled for a defined number of cycles after the last register write before it is disabled. Disabling a clock while a write is in flight leaves the peripheral in an indeterminate state, and the recovery is a reset of the block rather than a rewrite of the register. The datasheet’s enable sequence is not advisory.

A third problem is that disabling a clock does not reset the block. A gated timer retains its counter, a gated UART retains its shift register’s contents and its error flags. Firmware that re-enables a peripheral expecting a clean state finds the previous state, including a pending interrupt flag that fires immediately. Either reset the block explicitly or clear its flags as part of the enable sequence.

Moving the work off the core

Clock gating saves the dynamic power of the peripheral; it does nothing for the core, which is still awake to service the peripheral. The larger win is to configure the peripheral to move data without the core, so the core can enter a sleep state for the duration.

A DMA controller is the usual mechanism. The peripheral raises a request when its data register is ready, the DMA controller performs the bus transfer, and the core is not involved. The core’s only role is to set up the descriptors and to handle the completion or error interrupt, which happens once per buffer rather than once per byte.

The energy arithmetic is stark. A UART at 115200 baud delivering one byte every 87 µs, serviced by an interrupt, wakes the core roughly 11,500 times per second. Each wake costs the interrupt entry, the handler, and the return, and the core cannot enter any state deeper than the one whose exit latency is under 87 µs. With DMA, the core wakes once per buffer of, say, 256 bytes, and between those wakes it can sleep for the buffer’s full duration at whatever depth the other constraints allow.

/* UART receive by DMA, sketched through local wrappers over the
   controller's registers. The core is not woken per byte, only at
   half-buffer and full-buffer events. */
static uint8_t rx_buf[2][256];

static void uart_rx_start(void)
{
    dma_config_t cfg = {
        .src = uart_data_address(),
        .dst = rx_buf[0],
        .len = sizeof rx_buf,
        .src_inc = false,           /* the data register does not move */
        .dst_inc = true,
        .circular = true,           /* one ring across both halves */
        .half_transfer_irq = true,
        .complete_irq = true,
    };
    dma_configure(DMA_CH_UART_RX, &cfg);
    uart_enable_rx_dma();
    dma_start(DMA_CH_UART_RX);
}

/* Called at each half-transfer and transfer-complete event. */
static void dma_rx_event(int half)
{
    consume(rx_buf[half], sizeof rx_buf[half]);
    /* No per-byte work: the next buffer is already being filled. */
}

The circular mode is what makes the scheme work without a wake per buffer boundary: the controller wraps and continues, and the core consumes the half that is not being written. The cost is that the core must keep up with the consumption rate or the buffer overruns, and overrun detection has to be explicit because a circular transfer does not stop on its own.

The constraints a sleeping core imposes on DMA

DMA does not remove the need for the peripheral’s clock; it removes the need for the core’s. The peripheral must stay clocked for the transfer to proceed, so the peripheral’s clock gate must remain enabled, and its dynamic power is still paid. What is saved is the core’s active power plus the wake overhead.

The bus fabric matters too. A DMA transfer that targets SRAM in a domain that loses power in the chosen sleep state cannot proceed. The sleep state must keep the destination memory powered, which usually means either a shallower state or a retained SRAM region. This is a direct interaction with what a sleep mode actually turns off, and it is the most common reason a design that looks like it should sleep deeply does not.

A DMA transfer can also be a wake source in itself: many controllers can signal completion as an interrupt that wakes the core. That turns a long transfer into a single wake at the end, which is the ideal shape for a burst of sampling. The alternative, polling the transfer’s progress, keeps the core awake and defeats the purpose.

Priority is the third constraint. The DMA controller competes with the core for the bus, and a high-bandwidth transfer can starve the core’s instruction fetches. On a system where the core is doing real work while the DMA runs, the arbitration policy determines the core’s effective execution time, and a worst-case execution time measured without DMA contention is optimistic. This is one of the sources of interference that a worst-case response-time analysis has to account for separately, because it is invisible in the task’s own execution time.

Autonomous peripherals without DMA

Some peripherals can do useful work with no bus traffic at all. A counter can count edges and raise a compare interrupt when a threshold is reached; a comparator can watch an analog input and interrupt on a crossing; a real-time clock can keep time and alarm. These are the cheapest kinds of autonomous operation because they need no bus master and no memory bandwidth.

The pattern for using them is to convert a polling loop into a threshold event. Instead of waking every millisecond to read a sensor and compare it against a limit, configure a comparator on the sensor’s analog output with a threshold and wake only when the output crosses it. The average wake rate then depends on the signal rather than on the firmware’s timer, and for a slowly varying signal it can be orders of magnitude lower.

The failure mode of that pattern is the missed event. A comparator with hysteresis and a digital filter is required, or noise produces a burst of spurious crossings that costs more energy than the polling it replaced. A signal that crosses the threshold and returns between two samples of the filter is not seen at all, which is fine for a level detector and wrong for an edge detector.

Measuring the effect

The only way to know whether gating helped is to measure. A useful method is to instrument the transitions: record the current state and a timestamp at each entry and exit, then reconstruct the charge from the known current of each state. The reconstruction is only as good as the current figures, and the figures have to be measured on the board rather than read from the datasheet, for the reasons given in the duty-cycle arithmetic.

On a board with an external radio and an external memory, the microcontroller’s gated peripherals can be a small fraction of the sleep current, and the largest wins are in the parts of the design the firmware cannot gate at all. Gating is still worth doing, but it is a second-order optimisation until the first-order terms are addressed.

References

  1. ARM PrimeCell DMA Controller (PL330) Technical Reference Manual — ARM Limited
  2. STM32 Reference Manual — Direct Memory Access Controller — STMicroelectronics
  3. CMOS VLSI Design: A Circuits and Systems Perspective — Neil Weste, David Harris; Pearson