Using the XOSC
When the Pico starts, it uses the ROSC (variable frequency, around 11 MHz) as both the reference clock (used by multiple components) and the system clock (i.e.: the CPU clock).
So let’s see how to use the crystal oscillator, the XOSC (stable frequency, default to 12 MHz on the board reference design) for the reference clock and the PLL (a clock multiplier that takes the XOSC as input) for the system clock:
#![no_std]
#![no_main]
use cortex_m::asm;
pub use cortex_m_rt::entry;
use defmt::info;
use rp235x_hal as _;
use {defmt_rtt as _, panic_probe as _};
#[unsafe(link_section = ".start_block")]
#[unsafe(no_mangle)]
static BOOT_ROM_INFO: [u8; 44] = [
0xd3, 0xde, 0xff, 0xff, 0x42, 0x01, 0x21,
0x10, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x79, 0x35, 0x12, 0xab, 0xf2,
0xeb, 0x88, 0x71, 0x6c, 0x93, 0x02, 0x10,
0x7c, 0x93, 0x02, 0x10, 0x70, 0xf6, 0x02,
0x10, 0x90, 0xa3, 0x1a, 0xe7, 0x1f, 0x01,
0x02, 0x03,
];
#[entry]
fn main() -> ! {
unsafe {
blink_led();
// XOSC: CTRL Register
core::ptr::write_volatile(
0x40048000 as *mut u32,
0xfabaa0,
);
// CLK_REF_CTRL
// Set to use XOSC
core::ptr::write_volatile(
0x40010030 as *mut u32,
2,
);
// deassert PLL reset
// RESETS: RESET Register
core::ptr::write_volatile(
(0x40020000 + 0x3000) as *mut u32,
1 << 14,
);
// Wait until PLL is out of reset
// RESETS: RESET_DONE Register
while core::ptr::read_volatile(
0x40020008 as *const u32,
) >> 14
& 1
!= 1
{
asm::nop();
}
// PLL final outpu frequency FOUTPOSTDIV ==
// (FREF / REFDIV) × FBDIV / (POSTDIV1 * POSTDIV2)
// Therefore, with a 12Mhz XOSC
// 150 == (12 / 1) * 125 / (5 * 2)
// PLL: FBDIV_INT Register
// FBDIV = 125
core::ptr::write_volatile(
0x40050008 as *mut u32,
125,
);
// Power up the PLL VCO and PLL
// PLL: PWR Register
// The 0x3000 is the atomic clear
core::ptr::write_volatile(
(0x40050004 + 0x3000) as *mut u32,
1 | 1 << 5,
);
// Wait until PLL is unlocked
while core::ptr::read_volatile(
0x40050000 as *const u32,
) >> 31
!= 1
{
asm::nop();
}
// PLL: PRIM Register
// POSTDIV1 = 5
// POSTDIV2 = 2
core::ptr::write_volatile(
0x4005000c as *mut u32,
5 << 16 | 2 << 12,
);
// Power up the PLL POSTDIVPD
// PLL: PWR Register
core::ptr::write_volatile(
(0x40050004 + 0x3000) as *mut u32,
1 << 3,
);
// CLOCKS: CLK_SYS_CTRL Register
// Set to use CLKSRC_PLL_SYS
core::ptr::write_volatile(
0x4001003c as *mut u32,
1,
);
};
loop {
unsafe {
blink_led();
}
for _ in 1..100000 {
asm::nop();
}
}
}
unsafe fn blink_led() {
static mut LED_COUNTER: u64 = 0;
unsafe {
let counter = LED_COUNTER;
info!("led blink {}", counter);
LED_COUNTER += 1;
// Set GPIO 16 in output low mode
core::ptr::write_volatile(
0x40028084 as *mut u32,
0x5,
);
core::ptr::write_volatile(
0x40038044 as *mut u32,
0x40,
);
core::ptr::write_volatile(
0xd0000038 as *mut u32,
1 << 16,
);
core::ptr::write_volatile(
0xd0000020 as *mut u32,
1 << 16,
);
for _ in 1..200_000 {
asm::nop();
}
// disable GPIO output
core::ptr::write_volatile(
0xd0000040 as *mut u32,
1 << 16,
);
}
}
Notice how fast the led blinks (i.e.: how fast the for loop is evaluated) after having the system clock use the PLL: the CPU frequency goes from around 11 MHz to 150 MHz.