Files
pico-radio/src/main.rs

308 lines
8.8 KiB
Rust
Raw Normal View History

2026-02-09 16:24:18 -05:00
#![no_std]
#![no_main]
use cortex_m::prelude::_embedded_hal_PwmPin;
use defmt::info;
use defmt::println;
2026-02-09 16:24:18 -05:00
use defmt_rtt as _;
use panic_probe as _;
use rp2040_hal::clocks::ClockSource;
use rp2040_hal::pll;
use rp2040_hal::pll::PLLConfig;
use rp2040_hal::pll::common_configs::PLL_USB_48MHZ;
2026-02-09 16:24:18 -05:00
// Alias for our HAL crate
use rp2040_hal as hal;
use hal::fugit::RateExtU32;
use hal::pac;
use embedded_hal::digital::InputPin;
2026-02-09 16:24:18 -05:00
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
2026-02-09 16:24:18 -05:00
#[unsafe(link_section = ".boot_loader")]
#[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
2026-02-09 16:24:18 -05:00
/// 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);
2026-02-09 16:24:18 -05:00
// Configure the clocks
let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS);
let pll_usb = rp2040_hal::pll::setup_pll_blocking(
2026-02-09 16:24:18 -05:00
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: 4,
},
&mut clocks,
2026-02-09 16:24:18 -05:00
&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);
2026-02-09 16:24:18 -05:00
// 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 pll_freq = pll_sys.get_freq();
2026-02-09 16:24:18 -05:00
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);
2026-02-09 16:24:18 -05:00
pwm0.set_div_frac(0);
pwm0.clear_interrupt();
pwm0.enable();
2026-02-09 16:24:18 -05:00
let mut pwm_pin = pins.gpio16;
pwm_pin.set_drive_strength(rp2040_hal::gpio::OutputDriveStrength::TwelveMilliAmps);
pwm0.channel_a.output_to(pwm_pin);
2026-02-09 16:24:18 -05:00
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 = true;
2026-02-09 16:24:18 -05:00
let mut current_tune = 65000;
2026-02-09 16:24:18 -05:00
const MAX_TUNE: u16 = 65000u16;
2026-02-09 16:24:18 -05:00
const MIN_TUNE: u16 = 4u16;
2026-02-13 23:08:16 -05:00
set_tune(current_tune, pwm0);
2026-02-09 16:24:18 -05:00
// 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, pll_freq.to_Hz());
2026-02-09 16:24:18 -05:00
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;
gfx_buf.sprites[0].x = 114;
let mut input_mode = InputMode::default();
let mut input_mode_chg_input = pins.gpio12.into_pull_up_input();
println!("Booted at {}MHz", pll_freq.to_MHz());
2026-02-09 16:24:18 -05:00
loop {
tick = tick.wrapping_add(1);
2026-02-09 16:24:18 -05:00
if tick.wrapping_sub(last_gfx_update) > 500_000 {
last_gfx_update = tick;
2026-02-09 16:24:18 -05:00
gfx_buf.clear();
2026-02-10 14:08:21 -05:00
gfx_buf.draw_string(20, 14, "Hello Radio!");
2026-02-09 16:24:18 -05:00
let tuned_freq = pll_freq.to_kHz() / (current_tune as u32);
2026-02-10 14:08:21 -05:00
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());
2026-02-10 14:08:21 -05:00
gfx_buf.draw_string(20, 28, tune_str);
gfx_buf.sprites[0].y = match gfx_buf.sprites[0].y {
48 => 49,
_ => 48,
};
2026-02-10 14:08:21 -05:00
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);
2026-02-10 14:08:21 -05:00
gfx_buf.draw_string(64 + 6 * (5 - len as u8), 28, tune_str);
2026-02-09 16:59:42 -05:00
match broadcast_on {
2026-02-10 14:08:21 -05:00
true => gfx_buf.draw_string(20, 42, "ON THE AIR"),
2026-02-09 17:09:38 -05:00
false => gfx_buf.draw_string(20, 48, "Offline"),
2026-02-09 16:59:42 -05:00
}
2026-02-09 16:24:18 -05:00
match input_mode {
InputMode::DisplayOnly => (),
InputMode::TuneDigit(d) => {
let x = 64 + 6 * (4 - d) as u8;
gfx_buf.draw_bresenham_line(x, 36, x + 5, 36);
}
}
2026-02-09 16:24:18 -05:00
gfx_buf.redraw();
sh1106_dev.blit_framebuffer(&mut i2c, &mut gfx_buf);
}
if tick.wrapping_sub(last_input) < 500_000 {
2026-02-09 16:24:18 -05:00
continue;
}
if input_mode_chg_input.is_low().unwrap() {
last_input = tick;
input_mode = match input_mode {
InputMode::DisplayOnly => InputMode::TuneDigit(0),
InputMode::TuneDigit(mut d) => {
d = (d + 1) % 5;
if d == 0 {
InputMode::DisplayOnly
} else {
InputMode::TuneDigit(d)
}
}
};
info!("Input Mode is now {}", input_mode);
}
2026-02-09 16:24:18 -05:00
if on_off_input.is_low().unwrap() {
info!("Broadcast toggle");
2026-02-09 16:24:18 -05:00
last_input = tick;
broadcast_on = !broadcast_on;
match broadcast_on {
true => pwm0.enable(),
false => pwm0.disable(),
}
continue;
}
if input_mode == InputMode::DisplayOnly {
continue;
}
2026-02-09 16:24:18 -05:00
if tune_up_input.is_low().unwrap() {
info!("Tune UP");
2026-02-09 16:24:18 -05:00
last_input = tick;
match input_mode {
InputMode::TuneDigit(d) => {
let pow = 10u16.pow(d);
current_tune = MAX_TUNE.min(current_tune.saturating_add(pow));
info!("Adding {}", pow);
}
InputMode::DisplayOnly => unreachable!(),
}
2026-02-13 23:08:16 -05:00
set_tune(current_tune, pwm0);
2026-02-09 16:24:18 -05:00
continue;
}
if tune_dn_input.is_low().unwrap() {
info!("Tune DOWN");
2026-02-09 16:24:18 -05:00
last_input = tick;
match input_mode {
InputMode::TuneDigit(d) => {
let pow = 10u16.pow(d);
current_tune = MIN_TUNE.max(current_tune.saturating_sub(pow));
info!("Subtracting {}", pow);
}
InputMode::DisplayOnly => unreachable!(),
}
2026-02-13 23:08:16 -05:00
set_tune(current_tune, pwm0);
2026-02-09 16:24:18 -05:00
}
}
}
2026-02-13 23:08:16 -05:00
fn set_tune(
tune: u16,
pwm: &mut rp2040_hal::pwm::Slice<rp2040_hal::pwm::Pwm0, rp2040_hal::pwm::FreeRunning>,
) {
pwm.set_top(tune - 1);
pwm.channel_a.set_duty(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;
2026-02-09 16:24:18 -05:00
value /= 10;
if value <= 0 {
// info!("Ran out of digits at {}", i);
len = i + 1;
break;
}
2026-02-09 16:24:18 -05:00
}
// info!("utf8 buf {}, len {}", str_buf, len);
len
2026-02-09 16:24:18 -05:00
}
#[derive(Default, Debug, PartialEq, Eq, defmt::Format)]
enum InputMode {
#[default]
DisplayOnly,
TuneDigit(u32),
}