Mapping the Bit Period into Instruction Cycles
The WS2812 protocol defines timing in nanoseconds. In a PIO program, however, we cannot directly specify a delay like “wait 350 ns.” Timing is controlled through instruction cycles.
Each PIO instruction takes exactly one state machine clock cycle, and the actual duration of that cycle depends on the clock frequency we configure. By selecting an appropriate clock frequency and arranging how many cycles each operation consumes, we can generate the required HIGH and LOW pulse widths.
The next step is to determine how many cycles correspond to one bit period (1.25 µs), and then decide how many of those cycles the signal should remain HIGH. Once this mapping is defined, the PIO program can be written so that each bit consumes the correct number of cycles.
Deciding the State Machine Clock Frequency
The WS2812 bit time is 1250 ns (1.25 µs). If we ran the PIO at 800 kHz, one instruction cycle would also take 1250 ns.
As we saw earlier, a single bit consists of both a HIGH pulse and a LOW pulse. The only difference between 0 and 1 is how long the signal stays HIGH within that 1250 ns window. If one instruction already takes the entire 1250 ns, then a single instruction would consume the whole bit period. There would be no way to generate separate HIGH and LOW phases, and no way to create the required 350 ns or 700 ns HIGH pulses.
Important
The instruction cycle must be much shorter than 1250 ns.
If we instead run the state machine at 8 MHz, one cycle becomes 125 ns. Now the 1250 ns bit period consists of 10 instruction cycles.
Timing to Cycles
As we already learnt in the previous chapter, this is approximate time required for informing the WS2812 LED whether the bit is 0 or 1.
- Logic 0: HIGH for about 350 ns, then LOW for about 800 ns
- Logic 1: HIGH for about 700 ns, then LOW for about 600 ns
Since one instruction cycle is 125 ns at 8 MHz, we can now convert these timings into cycles.
The total bit time is:
1250 ns / 125 ns = 10 cycles per bit
So each bit must consume 10 PIO cycles in total.
For a logic 0 bit:
HIGH ≈ 350 ns → 350 / 125 ≈ 3 cycles
LOW ≈ 800 ns → remaining 7 cycles
For a logic 1 bit:
HIGH ≈ 700 ns → 700 / 125 ≈ 6 cycles
LOW ≈ 600 ns → remaining 4 cycles
The values are approximate. The WS2812 protocol allows timing tolerance (±150 ns), so rounding to the nearest whole cycle remains within specification.
Deriving the PIO Clock Divider
We already determined that the state machine must run at 8 MHz so that one WS2812 bit occupies 10 instruction cycles. Now we derive the clock divider required to generate that 8 MHz state machine frequency from the system clock.
On the RP2040, the default system clock is 125 MHz. The relationship between them is:
state_machine_frequency = clk_sys / divider
Rearranging:
divider = clk_sys / state_machine_frequency
Substituting the values:
divider = 125 MHz / 8 MHz = 15.625
Instead of calculating this manually, we compute it directly in code:
#![allow(unused)]
fn main() {
const CYCLES_PER_BIT: u32 = 10;
let ws2812_freq = U24F8::from_num(800);
let bit_freq = ws2812_freq * CYCLES_PER_BIT;
let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
cfg.clock_divider = clock_freq / bit_freq;
}
ws2812_freq represents the 800 kHz bit rate. Multiplying it by CYCLES_PER_BIT produces the required instruction frequency, which is 8000 kHz, or 8 MHz.
clk_sys_freq() returns the system clock in hertz, so dividing by 1000 converts it to kilohertz to match units.
Finally, dividing the system clock by the required instruction frequency gives the clock divider. With a 125 MHz system clock, the result is 15.625.