Files
picorom-rs/src/main.rs
2025-11-22 14:20:09 +01:00

391 lines
12 KiB
Rust

#![no_std]
#![no_main]
//! # Pinout
//!
//! GP0..GP14 => A0..A14
//! GP15..GP22 => D0..D7
//! GP26 => CE
//! GP27 => OE
mod serial;
use core::{
mem::MaybeUninit,
sync::atomic::{AtomicU8, Ordering},
task::Poll,
};
use defmt_rtt as _;
use embassy_executor::{InterruptExecutor, Spawner, task};
use embassy_futures::{poll_once, yield_now};
use embassy_rp::{
bind_interrupts,
clocks::ClockConfig,
flash::{self, FLASH_BASE, Flash},
gpio::{Drive, Level, Output, SlewRate},
interrupt::{self, InterruptExt, Priority},
peripherals::{FLASH, PIO0, USB},
pio::{self, Direction, Pio, ShiftConfig, ShiftDirection, StateMachine, program::pio_asm},
usb::{self, Driver},
};
use embassy_usb::{
UsbDevice,
class::cdc_acm::{self, CdcAcmClass},
};
use panic_probe as _;
use static_cell::{ConstStaticCell, StaticCell};
use crate::serial::{Link, PacketBuilder};
const ADDRESS_PINS: u8 = 15;
const DATA_PINS: u8 = 8;
const ROM_SIZE: usize = 32 * 1024;
const FLASH_SIZE: usize = 2 * 1024 * 1024;
static ROM_DATA: [AtomicU8; ROM_SIZE] = [const { AtomicU8::new(0) }; ROM_SIZE];
#[unsafe(link_section = ".romdata")]
#[used]
static INIT_ROM_DATA: MaybeUninit<[u8; ROM_SIZE]> = MaybeUninit::uninit();
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
USBCTRL_IRQ => usb::InterruptHandler<USB>;
});
static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new();
#[embassy_rp::interrupt]
unsafe fn SWI_IRQ_0() {
unsafe { EXECUTOR_HIGH.on_interrupt() }
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let config =
embassy_rp::config::Config::new(defmt::unwrap!(ClockConfig::system_freq(133_000_000)));
let p = embassy_rp::init(config);
let led = unsafe { p.PIN_25.clone_unchecked() };
let mut flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH);
if let Err(e) = load_rom(&mut flash) {
defmt::error!("Unable to initialize rom from flash: {}", e);
}
// Pull from fifo and write to all data pins
let data = pio_asm!(".wrap_target", "out pins, 8", ".wrap");
// OR CE and OE and set 4 pindirs
let output_enable = pio_asm!(
".origin 0",
".side_set 4 pindirs",
"mov pc, pins side 0xf",
"mov pc, pins side 0x0",
"mov pc, pins side 0x0",
"mov pc, pins side 0x0"
);
// report pin activity to the cpu
let report_data = pio_asm!(
"wait_oe:",
"mov y, pins", // read all pins
"mov osr, y", // move into the osr so we can shift them out
"out null, 26", // skipp all pins until CE
"out x, 2", // put CE and OE into x
"jmp x--, wait_oe", // if any of CE and OE are set go back to the start
"mov isr, y", // put the address into the isr
"push",
".wrap_target",
"mov x, pins", // read all pins
"jmp x!=y wait_oe", // if any pins have changed, restart and check oe again
".wrap"
);
let Pio {
mut common,
mut sm0,
mut sm1,
mut sm2,
mut sm3,
..
} = Pio::new(p.PIO0, Irqs);
let output_enable = common.load_program(&output_enable.program);
let mut address_pins = [
common.make_pio_pin(p.PIN_0),
common.make_pio_pin(p.PIN_1),
common.make_pio_pin(p.PIN_2),
common.make_pio_pin(p.PIN_3),
common.make_pio_pin(p.PIN_4),
common.make_pio_pin(p.PIN_5),
common.make_pio_pin(p.PIN_6),
common.make_pio_pin(p.PIN_7),
common.make_pio_pin(p.PIN_8),
common.make_pio_pin(p.PIN_9),
common.make_pio_pin(p.PIN_10),
common.make_pio_pin(p.PIN_11),
common.make_pio_pin(p.PIN_12),
common.make_pio_pin(p.PIN_13),
common.make_pio_pin(p.PIN_14),
];
for pin in &mut address_pins {
pin.set_schmitt(true);
}
let mut data_pins = [
common.make_pio_pin(p.PIN_15),
common.make_pio_pin(p.PIN_16),
common.make_pio_pin(p.PIN_17),
common.make_pio_pin(p.PIN_18),
common.make_pio_pin(p.PIN_19),
common.make_pio_pin(p.PIN_20),
common.make_pio_pin(p.PIN_21),
common.make_pio_pin(p.PIN_22),
];
for pin in &mut data_pins {
pin.set_slew_rate(SlewRate::Fast);
pin.set_drive_strength(Drive::_12mA);
pin.set_schmitt(true);
}
let data_pins = [
&data_pins[0],
&data_pins[1],
&data_pins[2],
&data_pins[3],
&data_pins[4],
&data_pins[5],
&data_pins[6],
&data_pins[7],
];
let mut ce_pin = common.make_pio_pin(p.PIN_26);
ce_pin.set_schmitt(true);
let mut oe_pin = common.make_pio_pin(p.PIN_27);
oe_pin.set_schmitt(true);
let led_pin = common.make_pio_pin(p.PIN_25);
let address_pins = [
&address_pins[0],
&address_pins[1],
&address_pins[2],
&address_pins[3],
&address_pins[4],
&address_pins[5],
&address_pins[6],
&address_pins[7],
&address_pins[8],
&address_pins[9],
&address_pins[10],
&address_pins[11],
&address_pins[12],
&address_pins[13],
&address_pins[14],
data_pins[0],
data_pins[1],
data_pins[2],
data_pins[3],
data_pins[4],
data_pins[5],
data_pins[6],
data_pins[7],
&common.make_pio_pin(p.PIN_23),
&common.make_pio_pin(p.PIN_24),
&led_pin,
&ce_pin,
&oe_pin,
];
// configure data pio
let mut cfg = pio::Config::default();
cfg.use_program(&common.load_program(&data.program), &[]);
cfg.set_out_pins(&data_pins[..]);
cfg.shift_out = ShiftConfig {
threshold: DATA_PINS,
direction: ShiftDirection::default(),
auto_fill: true,
};
sm0.set_config(&cfg);
sm0.set_enable(true);
// configure output enable pio
let mut cfg = pio::Config::default();
cfg.use_program(&output_enable, &data_pins[..4]);
cfg.set_in_pins(&[&ce_pin, &oe_pin]);
sm1.set_config(&cfg);
sm1.set_enable(true);
cfg.use_program(&output_enable, &data_pins[4..]);
sm2.set_config(&cfg);
sm2.set_enable(true);
// configure address pio
let mut cfg = pio::Config::default();
cfg.use_program(&common.load_program(&report_data.program), &[]);
cfg.set_in_pins(&address_pins[..]);
cfg.shift_in = ShiftConfig {
threshold: ADDRESS_PINS,
direction: ShiftDirection::default(),
auto_fill: true,
};
sm3.set_pin_dirs(Direction::In, &address_pins[..]);
sm3.set_pin_dirs(Direction::Out, &[&led_pin]);
sm3.set_config(&cfg);
sm3.set_enable(true);
let driver = Driver::new(p.USB, Irqs);
let config = {
let mut config = embassy_usb::Config::new(0x2e8a, 0x000a);
config.manufacturer = Some("Max Känner");
config.product = Some("PicoROM.rs");
config.serial_number = Some("2");
config.max_power = 100;
config.max_packet_size_0 = 64;
config
};
let mut builder = {
static CONFIG_DESCRIPTOR: ConstStaticCell<[u8; 256]> = ConstStaticCell::new([0; 256]);
static BOS_DESCRIPTOR: ConstStaticCell<[u8; 256]> = ConstStaticCell::new([0; 256]);
static CONTROL_BUF: ConstStaticCell<[u8; 64]> = ConstStaticCell::new([0; 64]);
embassy_usb::Builder::new(
driver,
config,
CONFIG_DESCRIPTOR.take(),
BOS_DESCRIPTOR.take(),
&mut [],
CONTROL_BUF.take(),
)
};
let mut class = {
static STATE: StaticCell<cdc_acm::State> = StaticCell::new();
CdcAcmClass::new(&mut builder, STATE.init(cdc_acm::State::new()), 64)
};
let usb = builder.build();
interrupt::PIO0_IRQ_0.set_priority(Priority::P0);
interrupt::SWI_IRQ_0.set_priority(Priority::P1);
interrupt::USBCTRL_IRQ.set_priority(Priority::P3);
let int_spawner = EXECUTOR_HIGH.start(interrupt::SWI_IRQ_0);
int_spawner.must_spawn(pio_task(sm0, sm3));
spawner.must_spawn(usb_task(usb));
let mut led = Output::new(led, Level::Low);
loop {
'outer: {
led.set_low();
class.wait_connection().await;
while !class.rts() {
yield_now().await;
}
defmt::info!("USB Connected");
led.set_high();
if let Err(e) = class.write_packet(b"PicoROM Hello").await {
defmt::error!("Error sending preamble: {}", e);
break 'outer;
}
let mut buf = [0; 64];
let mut packet_builder = PacketBuilder::new();
let mut link = Link::new();
while class.rts() {
let n = match poll_once(class.read_packet(&mut buf)) {
Poll::Ready(Ok(n)) => n,
Poll::Ready(Err(e)) => {
defmt::error!("Error reading packet data: {}", e);
break 'outer;
}
Poll::Pending => {
yield_now().await;
continue;
}
};
for byte in buf.into_iter().take(n) {
if let Some(packet) = packet_builder.push(byte) {
defmt::debug!("Got packet: {}", packet);
if let Some(response) = link.handle_packet(&packet, &mut flash) {
defmt::info!("Sending Response: {}", response);
if let Err(e) = response.send(&mut class).await {
defmt::error!("Unable to send response: {}", e);
break 'outer;
}
}
}
}
}
}
defmt::info!("USB Disconnected");
}
}
#[task]
async fn usb_task(mut usb: UsbDevice<'static, Driver<'static, USB>>) -> ! {
usb.run().await
}
#[task]
async fn pio_task(
mut data_sm: StateMachine<'static, PIO0, 0>,
mut address_sm: StateMachine<'static, PIO0, 3>,
) -> ! {
loop {
let address = address_sm.rx().wait_pull().await;
let address = (address & 0x7fff) as u16;
let data = ROM_DATA[usize::from(address)].load(Ordering::SeqCst);
defmt::trace!("replying with {:#04x} @ {:#06x}", data, address);
data_sm.tx().try_push(u32::from(data));
}
}
fn store_rom(
flash: &mut Flash<'_, FLASH, impl flash::Mode, FLASH_SIZE>,
) -> Result<(), flash::Error> {
let offset = unsafe {
u32::try_from(
INIT_ROM_DATA
.as_ptr()
.cast::<u8>()
.offset_from_unsigned(FLASH_BASE.cast::<u8>()),
)
.map_err(|_| flash::Error::OutOfBounds)?
};
let len = size_of_val(&INIT_ROM_DATA).min(ROM_DATA.len());
defmt::info!("Erasing flash at offset {:#x} with size {:#x}", offset, len);
flash.blocking_erase(
offset,
offset + u32::try_from(len).map_err(|_| flash::Error::OutOfBounds)?,
)?;
let rom_buffer = unsafe { &*(&raw const ROM_DATA[..len] as *const [u8]) };
defmt::info!("Programming flash with buffer at {}", rom_buffer.as_ptr());
flash.blocking_write(offset, rom_buffer)?;
defmt::info!("Successfully commited rom to flash");
Ok(())
}
fn load_rom(
flash: &mut Flash<'_, FLASH, impl flash::Mode, FLASH_SIZE>,
) -> Result<(), flash::Error> {
let offset = unsafe {
u32::try_from(
INIT_ROM_DATA
.as_ptr()
.cast::<u8>()
.offset_from_unsigned(FLASH_BASE.cast::<u8>()),
)
.map_err(|_| flash::Error::OutOfBounds)?
};
let len = size_of_val(&INIT_ROM_DATA).min(ROM_DATA.len());
for (i, rom) in ROM_DATA.iter().enumerate().take(len) {
let mut init = [0u8];
flash.blocking_read(
offset + u32::try_from(i).map_err(|_| flash::Error::OutOfBounds)?,
&mut init,
)?;
let init = init[0];
rom.store(init, Ordering::SeqCst);
}
Ok(())
}