← Back to list

Set New RP2040 System Clock Rate for PIO

With Internal PLL

circuit4u in The Startup · 2025-01-16 14:28 · 0 claps · 1.8 min read paywalled
#rust #rp2040
Open on Medium ↗

Set New RP2040 System Clock Rate for PIO

With Internal PLL

Many RP2040 breakout boards come with a 12MHz external crystal. Inside RP2040, there are two PLLs, one sets a system clock rate at 125MHz, the other is at 48MHz for USB signals.

In my recent application, I need to use PIO to generate a 6.78MHz carrier frequency signal. The PIO instruction speed is one clock cycle. That clock can be exactly the system clock or a divide-down of the system clock. However it is hard to get exactly 6.78MHz from a 125MHz system clock.

To give a concrete example, 125MHz clock will run each PIO instructions at 8ns, so 18 * 8 = 144ns, which is closest to 1/6.78MHz = 147.5ns. But that’s not exact. BTW, the number 18 is easy to achieve with PIO “delays”, for example

 ".wrap_target",
 "set pins, 1 [8]", // total 18 cycles for 6.78MHz (147ns) carrier: 8ns * 18 = 144 ns
 "set pins, 0 [8]",
 ".wrap" 

Fortunately, one can change RP2040 system clock frequency by configuring the internal PLL with different dividers.

There is a Rust program (link) to do the PLL divider calculation for you, as long as you change this line to the desired PIO frequency. In my case, 67.8MHz:

    let desired_pio_frequency_hz = 67_800_000u64; //6_144_000u64;

Here is the print-out of the calculator program: vco freq = 1356 MHz, post divider 1 = 5; post divider 2 = 4; pio divider = 1

The next step is to set the RP2040 clock using the corresponding divider numbers from the calculator program. Note the PLL_SYS_67P8MHZ struct

 // External high-speed crystal on the pico board is 12Mhz
    let external_xtal_freq_hz = 12_000_000u32;
    let xosc = setup_xosc_blocking(pac.XOSC, external_xtal_freq_hz.Hz())
        .map_err(InitError::XoscErr)
        .unwrap();

    watchdog.enable_tick_generation((external_xtal_freq_hz / 1_000_000) as u8);

    let mut clocks = ClocksManager::new(pac.CLOCKS);

    pub const PLL_SYS_67P8MHZ: PLLConfig = PLLConfig {
        vco_freq: HertzU32::MHz(1356),
        refdiv: 1,
        post_div1: 5,
        post_div2: 4,
    };

    let pll_sys = setup_pll_blocking(
        pac.PLL_SYS,
        xosc.operating_frequency().into(),
        PLL_SYS_67P8MHZ,
        &mut clocks,
        &mut pac.RESETS,
    )
    .unwrap();
    let pll_usb = setup_pll_blocking(
        pac.PLL_USB,
        xosc.operating_frequency().into(),
        PLL_USB_48MHZ,
        &mut clocks,
        &mut pac.RESETS,
    )
    .unwrap();

    clocks.init_default(&xosc, &pll_sys, &pll_usb).unwrap();

Similarly the PIO code of outputting a 6.78MHz carrier signal (divide down a 67.8MHz system clock)

".wrap_target",
 "set pins, 1 [4]", // total 10 cycles for 6.78MHz carrier
 "set pins, 0 [4]",
 ".wrap"

On common question, why not set system clock at exactly 6.78MHz?

Because a 10x faster system clock allows 10 PIO instructions to be executed in that time slot, which allows signal demodulation running in a second PIO.

If you need more PIO instructions to run, you can bump system clock speed to 135.6MHz for example.


메타데이터
post_id
c4ebf48a9eff
slug
set-new-rp2040-system-clock-rate-for-pio-c4ebf48a9eff
url
https://medium.com/@circuit4us/set-new-rp2040-system-clock-rate-for-pio-c4ebf48a9eff
canonical_url
https://medium.com/@circuit4us/set-new-rp2040-system-clock-rate-for-pio-c4ebf48a9eff
author_url
https://medium.com/@circuit4us
status
ok
fetched_at
2026-07-21 07:28:30