#![no_std] #![no_main] use cortex_m::prelude::_embedded_hal_PwmPin; use defmt::info; use defmt::println; use defmt_rtt as _; use panic_probe as _; use rp2040_hal::pll::PLLConfig; use rp2040_hal::pll::common_configs::PLL_USB_48MHZ; // Alias for our HAL crate use rp2040_hal as hal; use hal::fugit::RateExtU32; use hal::pac; use embedded_hal::digital::InputPin; use embedded_hal::digital::OutputPin; use sh1106_pico_rs::graphics::GraphicsBuf; use sh1106_pico_rs::sh1106::SH1106Dev; /// NB if OVERCLOCKING /// you MUST use a BOOT_LOADER which can tolerate the OC /// the GENERIC_03H one cannot (unsure why) but W25Q080 has been solid for me #[unsafe(link_section = ".boot_loader")] #[used] pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; /// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust /// if your board has a different frequency const XTAL_FREQ_HZ: u32 = 12_000_000u32; #[rp2040_hal::entry] fn main() -> ! { // Grab our singleton objects let mut pac = pac::Peripherals::take().unwrap(); let core = pac::CorePeripherals::take().unwrap(); // Set up the watchdog driver - needed by the clock setup code // let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); hal::vreg::set_voltage( &mut pac.VREG_AND_CHIP_RESET, pac::vreg_and_chip_reset::vreg::VSEL_A::VOLTAGE1_30, ); // settle // cortex_m::asm::delay(3_000_000); let xosc = hal::xosc::setup_xosc_blocking_custom_delay(pac.XOSC, XTAL_FREQ_HZ.Hz(), 128) .map_err(|_x| false) .unwrap(); // watchdog.enable_tick_generation((XTAL_FREQ_HZ / 1_000_000) as u8); // Configure the clocks let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS); let pll_usb = rp2040_hal::pll::setup_pll_blocking( pac.PLL_USB, xosc.operating_frequency(), PLL_USB_48MHZ, &mut clocks, &mut pac.RESETS, ) .unwrap(); let pll_sys = hal::pll::setup_pll_blocking( pac.PLL_SYS, xosc.operating_frequency(), PLLConfig { vco_freq: 1500.MHz(), refdiv: 1, post_div1: 5, post_div2: 1, }, &mut clocks, &mut pac.RESETS, ) .unwrap(); clocks.init_default(&xosc, &pll_sys, &pll_usb).unwrap(); // let mut delay = cortex_m::delay::Delay::new(core.SYST, 200_000_000); // The single-cycle I/O block controls our GPIO pins let sio = hal::Sio::new(pac.SIO); // Set the pins to their default state let pins = hal::gpio::Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS, ); let mut led_pin = pins.gpio25.into_push_pull_output(); led_pin.set_high(); // Init PWMs let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); // Configure PWM0 let pwm0 = &mut pwm_slices.pwm0; pwm0.clr_ph_correct(); pwm0.set_div_int(1); pwm0.set_div_frac(0); pwm0.clear_interrupt(); pwm0.enable(); let mut pwm_pin = pins.gpio16; pwm_pin.set_drive_strength(rp2040_hal::gpio::OutputDriveStrength::TwelveMilliAmps); pwm0.channel_a.output_to(pwm_pin); let mut on_off_input = pins.gpio15.into_pull_up_input(); let mut tune_up_input = pins.gpio14.into_pull_up_input(); let mut tune_dn_input = pins.gpio13.into_pull_up_input(); let mut tick = 0u32; let mut last_input = 0u32; let mut broadcast_on = false; let mut current_tune = 10; const MAX_TUNE: u16 = 350u16; const MIN_TUNE: u16 = 4u16; pwm0.set_top(current_tune); pwm0.channel_a.set_duty(current_tune / 2); // Create the I²C drive let mut i2c = hal::I2C::i2c0( pac.I2C0, pins.gpio8.reconfigure(), pins.gpio9.reconfigure(), 400.kHz(), &mut pac.RESETS, &clocks.system_clock, ); let mut delay = cortex_m::delay::Delay::new(core.SYST, 200_000); let mut sh1106_dev = SH1106Dev::new(&mut delay, &mut i2c); sh1106_dev.set_vertical_flip(&mut i2c, true); let sh1106_dev = sh1106_dev; let mut gfx_buf = GraphicsBuf::new(); let mut last_gfx_update = 0u32; loop { tick = tick.wrapping_add(1); if tick.wrapping_sub(last_gfx_update) > 500_000 { gfx_buf.clear(); gfx_buf.draw_string(20, 14, "Hello Radio!"); let tuned_freq = 272_000u32 / (current_tune as u32); let mut tune_str_buf = [0u8; 5]; let len = u32_into_str(tuned_freq, &mut tune_str_buf); let tune_str = str::from_utf8(&tune_str_buf[tune_str_buf.len() - len..]).unwrap(); // info!("val {}, len {}", tuned_freq, len); // info!("{}, {}", tune_str, tune_str.len()); gfx_buf.draw_string(20, 28, tune_str); gfx_buf.sprites[0].y = match gfx_buf.sprites[0].y { 32 => 33, _ => 32, }; let len = u32_into_str(current_tune as u32, &mut tune_str_buf); let tune_str = str::from_utf8(&tune_str_buf[tune_str_buf.len() - len..]).unwrap(); // info!("len {}", len); gfx_buf.draw_string(64, 28, tune_str); match broadcast_on { true => gfx_buf.draw_string(20, 42, "ON THE AIR"), false => gfx_buf.draw_string(20, 48, "Offline"), } gfx_buf.redraw(); sh1106_dev.blit_framebuffer(&mut i2c, &mut gfx_buf); last_gfx_update = tick; } if tick.wrapping_sub(last_input) < 5_000_000 { continue; } if on_off_input.is_low().unwrap() { info!("Broadcast toggle"); last_input = tick; broadcast_on = !broadcast_on; match broadcast_on { true => pwm0.enable(), false => pwm0.disable(), } continue; } if tune_up_input.is_low().unwrap() { info!("Tune UP"); last_input = tick; current_tune = MAX_TUNE.min(current_tune + 2); pwm0.set_top(current_tune); pwm0.channel_a.set_duty(current_tune / 2); continue; } if tune_dn_input.is_low().unwrap() { info!("Tune DOWN"); last_input = tick; current_tune = MIN_TUNE.max(current_tune - 2); pwm0.set_top(current_tune); pwm0.channel_a.set_duty(current_tune / 2); } } } // returns the length of the final str removing leading zeroes fn u32_into_str(mut value: u32, str_buf: &mut [u8]) -> usize { let mut len = str_buf.len(); // info!("val {}", value); for i in 0..len { // info!("val {}, i {}, digit {}", value, i, value % 10); str_buf[len - i - 1] = b'0' + (value % 10) as u8; value /= 10; if value <= 0 { // info!("Ran out of digits at {}", i); len = i + 1; break; } } // info!("utf8 buf {}, len {}", str_buf, len); len }