simple picorom interface

This commit is contained in:
2025-11-16 20:57:31 +01:00
parent a2bc2232c0
commit f7d3c4c633
4 changed files with 511 additions and 16 deletions

69
Cargo.lock generated
View File

@@ -155,6 +155,15 @@ version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e"
[[package]]
name = "defmt"
version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
dependencies = [
"defmt 1.0.1",
]
[[package]] [[package]]
name = "defmt" name = "defmt"
version = "1.0.1" version = "1.0.1"
@@ -194,7 +203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e"
dependencies = [ dependencies = [
"critical-section", "critical-section",
"defmt", "defmt 1.0.1",
] ]
[[package]] [[package]]
@@ -391,6 +400,16 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "heapless"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.5.2" version = "0.5.2"
@@ -522,7 +541,17 @@ version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [ dependencies = [
"num_enum_derive", "num_enum_derive 0.5.11",
]
[[package]]
name = "num_enum"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
dependencies = [
"num_enum_derive 0.7.5",
"rustversion",
] ]
[[package]] [[package]]
@@ -536,6 +565,17 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "num_enum_derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.110",
]
[[package]] [[package]]
name = "panic-probe" name = "panic-probe"
version = "1.0.0" version = "1.0.0"
@@ -543,7 +583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
dependencies = [ dependencies = [
"cortex-m", "cortex-m",
"defmt", "defmt 1.0.1",
] ]
[[package]] [[package]]
@@ -600,13 +640,17 @@ version = "0.1.0"
dependencies = [ dependencies = [
"cortex-m", "cortex-m",
"cortex-m-rt", "cortex-m-rt",
"defmt", "defmt 1.0.1",
"defmt-rtt", "defmt-rtt",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"heapless 0.9.2",
"num_enum 0.7.5",
"panic-probe", "panic-probe",
"pio", "pio",
"pio-proc", "pio-proc",
"rp-pico", "rp-pico",
"usb-device",
"usbd-serial",
] ]
[[package]] [[package]]
@@ -616,7 +660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"num_enum", "num_enum 0.5.11",
"paste", "paste",
] ]
@@ -1042,10 +1086,23 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
dependencies = [ dependencies = [
"heapless", "defmt 0.3.100",
"heapless 0.8.0",
"portable-atomic", "portable-atomic",
] ]
[[package]]
name = "usbd-serial"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065e4eaf93db81d5adac82d9cef8f8da314cb640fa7f89534b972383f1cf80fc"
dependencies = [
"embedded-hal 0.2.7",
"embedded-io",
"nb 1.1.0",
"usb-device",
]
[[package]] [[package]]
name = "vcell" name = "vcell"
version = "0.1.3" version = "0.1.3"

View File

@@ -9,13 +9,52 @@ test = false
bench = false bench = false
[dependencies] [dependencies]
cortex-m = "0.7" cortex-m = { version = "0.7", features = ["inline-asm"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
panic-probe = { version = "1.0", features = ["print-defmt", "defmt-error"] } panic-probe = { version = "1.0", features = ["print-defmt", "defmt-error"] }
rp-pico = "0.9" rp-pico = "0.9"
embedded-hal = "1.0" embedded-hal = "1.0"
pio = "0.2" pio = "0.2"
pio-proc = "0.2" pio-proc = "0.2"
usb-device = { version = "0.3", features = ["defmt"] }
usbd-serial = "0.2"
defmt = "1.0" defmt = "1.0"
defmt-rtt = "1.1" defmt-rtt = "1.1"
num_enum = { version = "0.7", default-features = false }
heapless = "0.9"
# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 3
overflow-checks = true
# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 3
overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false

View File

@@ -8,9 +8,14 @@
//! GP26 => CE //! GP26 => CE
//! GP27 => OE //! GP27 => OE
use core::cell::Cell; mod serial;
use cortex_m::{delay::Delay, interrupt::Mutex}; use core::{
cell::Cell,
sync::atomic::{AtomicU8, Ordering},
};
use cortex_m::{delay::Delay, interrupt::Mutex, singleton};
use defmt_rtt as _; use defmt_rtt as _;
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use panic_probe as _; use panic_probe as _;
@@ -21,11 +26,16 @@ use rp_pico::{
Sio, Watchdog, Sio, Watchdog,
clocks::init_clocks_and_plls, clocks::init_clocks_and_plls,
gpio::{FunctionPio0, PullNone}, gpio::{FunctionPio0, PullNone},
pio::{PIO0SM0, PIO0SM3, PIOBuilder, PioIRQ, Rx, ShiftDirection, Tx}, pio::{PIO0SM0, PIO0SM3, PIOBuilder, PioIRQ, Rx, Tx},
prelude::*, prelude::*,
usb::UsbBus,
}, },
pac::{self, CorePeripherals, Peripherals, interrupt}, pac::{self, CorePeripherals, Peripherals, interrupt},
}; };
use usb_device::{class_prelude::*, prelude::*};
use usbd_serial::SerialPort;
use crate::serial::{Link, PacketBuilder};
const ADDRESS_BASE: u8 = 0; const ADDRESS_BASE: u8 = 0;
const ADDRESS_PINS: u8 = 15; const ADDRESS_PINS: u8 = 15;
@@ -35,13 +45,19 @@ const CE: u8 = 26;
#[allow(unused)] #[allow(unused)]
const OE: u8 = 27; const OE: u8 = 27;
const ROM_SIZE: usize = 1024 * 32;
static GLOBAL_ADDRESS_RX: Mutex<Cell<Option<Rx<PIO0SM3>>>> = Mutex::new(Cell::new(None)); static GLOBAL_ADDRESS_RX: Mutex<Cell<Option<Rx<PIO0SM3>>>> = Mutex::new(Cell::new(None));
static GLOBAL_DATA_TX: Mutex<Cell<Option<Tx<PIO0SM0>>>> = Mutex::new(Cell::new(None)); static GLOBAL_DATA_TX: Mutex<Cell<Option<Tx<PIO0SM0>>>> = Mutex::new(Cell::new(None));
static ROM_DATA: [AtomicU8; ROM_SIZE] = [const { AtomicU8::new(0) }; ROM_SIZE];
static GLOBAL_USB_DEVICE: Mutex<Cell<Option<UsbDevice<UsbBus>>>> = Mutex::new(Cell::new(None));
static GLOBAL_USB_SERIAL: Mutex<Cell<Option<SerialPort<UsbBus>>>> = Mutex::new(Cell::new(None));
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
let mut pac = Peripherals::take().unwrap(); let mut pac = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap(); let mut core = CorePeripherals::take().unwrap();
let mut watchdog = Watchdog::new(pac.WATCHDOG); let mut watchdog = Watchdog::new(pac.WATCHDOG);
@@ -195,8 +211,37 @@ fn main() -> ! {
address_rx.enable_rx_not_empty_interrupt(PioIRQ::Irq0); address_rx.enable_rx_not_empty_interrupt(PioIRQ::Irq0);
cortex_m::interrupt::free(|cs| GLOBAL_ADDRESS_RX.borrow(cs).set(Some(address_rx))); cortex_m::interrupt::free(|cs| GLOBAL_ADDRESS_RX.borrow(cs).set(Some(address_rx)));
let usbctrl_regs = pac.USBCTRL_REGS;
let usbctrl_dpram = pac.USBCTRL_DPRAM;
let resets = &mut pac.RESETS;
let usb_bus = singleton!(: UsbBusAllocator<UsbBus> = UsbBusAllocator::new(UsbBus::new(
usbctrl_regs,
usbctrl_dpram,
clocks.usb_clock,
true,
resets,
)))
.unwrap();
let serial = SerialPort::new(usb_bus);
cortex_m::interrupt::free(|cs| GLOBAL_USB_SERIAL.borrow(cs).set(Some(serial)));
let usb_dev = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x2e8a, 0x000a))
.strings(&[StringDescriptors::default()
.manufacturer("Max Känner")
.product("PicoROM.rs")
.serial_number("1")])
.unwrap()
.device_class(2)
.build();
cortex_m::interrupt::free(|cs| GLOBAL_USB_DEVICE.borrow(cs).set(Some(usb_dev)));
unsafe { unsafe {
// Highest priority for pio
core.NVIC.set_priority(pac::interrupt::PIO0_IRQ_0, 0x00);
// lowest priority for usb
core.NVIC.set_priority(pac::interrupt::USBCTRL_IRQ, 0xFF);
pac::NVIC::unmask(pac::interrupt::PIO0_IRQ_0); pac::NVIC::unmask(pac::interrupt::PIO0_IRQ_0);
pac::NVIC::unmask(pac::interrupt::USBCTRL_IRQ);
} }
loop { loop {
@@ -220,12 +265,86 @@ fn PIO0_IRQ_0() {
} }
if let Some(address) = ADDRESS { if let Some(address) = ADDRESS {
while let Some(address) = address.read() { while let Some(rx) = address.read() {
let address = (address & 0x7fff) as u16; let address = (rx & 0x7fff) as u16;
if let Some(data) = DATA { if let Some(tx) = DATA {
defmt::trace!("replying with {:#04x} @ {:#06x}", address as u8, address); let data = ROM_DATA[usize::from(address)].load(Ordering::Relaxed);
data.write(u32::from(address)); defmt::trace!("replying with {:#04x} @ {:#06x}", data, address);
tx.write(u32::from(data));
} }
} }
} }
} }
#[interrupt]
fn USBCTRL_IRQ() {
static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = None;
static mut USB_SERIAL: Option<SerialPort<UsbBus>> = None;
static mut DO_PREAMBLE: bool = true;
static mut PACKET_BUILDER: PacketBuilder = PacketBuilder::new();
static mut LINK: Link = Link::new();
const PREAMBLE: &[u8] = b"PicoROM Hello";
if USB_DEVICE.is_none() {
*USB_DEVICE = cortex_m::interrupt::free(|cs| GLOBAL_USB_DEVICE.borrow(cs).take());
}
if USB_SERIAL.is_none() {
*USB_SERIAL = cortex_m::interrupt::free(|cs| GLOBAL_USB_SERIAL.borrow(cs).take());
}
let Some(usb_dev) = USB_DEVICE else { return };
let Some(serial) = USB_SERIAL else { return };
if !serial.rts() {
*DO_PREAMBLE = true;
}
if *DO_PREAMBLE && serial.rts() {
match serial.write(PREAMBLE) {
Ok(count) if count == PREAMBLE.len() => {
*DO_PREAMBLE = false;
defmt::info!("Send Preamble")
}
Ok(count) => {
defmt::warn!(
"Unable to write the whole preamble. Only wrote {} Bytes",
count
);
}
Err(UsbError::WouldBlock) => (),
Err(e) => {
defmt::error!("Unable to write preamble: {}", e)
}
}
}
if usb_dev.poll(&mut [serial]) {
let mut buf = [0u8; 64];
match serial.read(&mut buf) {
Ok(0) => {}
Ok(count) => {
for byte in buf.into_iter().take(count) {
if let Some(packet) = PACKET_BUILDER.push(byte) {
defmt::debug!("Got packet: {}", packet);
if let Some(response) = LINK.handle_packet(&packet) {
defmt::info!("Sending Response: {}", response);
if let Err(e) = response.send(serial)
&& e != UsbError::WouldBlock
{
defmt::error!("Unable to send response: {}", e);
}
}
}
}
}
Err(UsbError::WouldBlock) => (),
Err(e) => {
defmt::error!("USB Error: {}", e);
}
}
}
if usb_dev.state() == UsbDeviceState::Default {
*DO_PREAMBLE = true;
}
}

280
src/serial.rs Normal file
View File

@@ -0,0 +1,280 @@
use core::{borrow::BorrowMut, sync::atomic::Ordering};
use defmt::Format;
use heapless::{String, Vec};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use usb_device::{UsbError, bus::UsbBus};
use usbd_serial::SerialPort;
use crate::ROM_DATA;
const MAX_PAYLOAD: u8 = 30;
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Format, TryFromPrimitive, IntoPrimitive,
)]
#[repr(u8)]
pub enum Kind {
PointerSet = 3,
PointerGet = 4,
PointerCur = 5,
Write = 6,
Read = 7,
ReadData = 8,
CommitFlash = 12,
CommitDone = 13,
ParameterSet = 20,
ParameterGet = 21,
Parameter = 22,
ParameterError = 23,
ParameterQuery = 24,
CommsStart = 80,
CommsEnd = 81,
CommsData = 82,
Identify = 0xf8,
Bootsel = 0xf9,
Error = 0xfe,
Debug = 0xff,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(C)]
pub struct Packet {
pub kind: Kind,
pub payload: Vec<u8, { MAX_PAYLOAD as usize }>,
}
impl Format for Packet {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"Packet {{ kind: {}, payload: {:x}}}",
self.kind,
&self.payload[..]
);
}
}
impl Packet {
pub fn send(
&self,
serial: &mut SerialPort<impl UsbBus, impl BorrowMut<[u8]>, impl BorrowMut<[u8]>>,
) -> Result<(), UsbError> {
serial.write(&[
self.kind.into(),
u8::try_from(self.payload.len()).unwrap_or(MAX_PAYLOAD),
])?;
serial.write(&self.payload)?;
Ok(())
}
pub fn error<const N: usize>(reason: &[u8; N]) -> Self {
Self {
kind: Kind::Error,
payload: Vec::from_array(*reason),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PacketBuilder {
Idle,
Kind {
kind: Kind,
},
Payload {
kind: Kind,
target_size: u8,
payload: Vec<u8, { MAX_PAYLOAD as usize }>,
},
}
impl PacketBuilder {
pub const fn new() -> Self {
Self::Idle
}
pub fn push(&mut self, byte: u8) -> Option<Packet> {
match self {
Self::Idle => {
let kind = Kind::try_from(byte).ok()?;
*self = Self::Kind { kind };
}
Self::Kind { kind } => {
let target_size = byte;
let kind = *kind;
match target_size {
0 => {
*self = Self::Idle;
return Some(Packet {
kind,
payload: Vec::new(),
});
}
1..=MAX_PAYLOAD => {
*self = Self::Payload {
kind,
target_size,
payload: Vec::new(),
};
}
_ => {
*self = Self::Idle;
}
}
}
Self::Payload {
kind,
target_size,
payload,
} => {
payload.push(byte).unwrap();
if payload.len() >= usize::from(*target_size) {
let kind = *kind;
let payload = payload.clone();
*self = Self::Idle;
return Some(Packet { kind, payload });
}
}
}
None
}
}
pub struct Link {
pointer: u32,
name: String<{ MAX_PAYLOAD as usize }>,
rom_name: String<{ MAX_PAYLOAD as usize }>,
}
impl Link {
pub const fn new() -> Self {
Self {
pointer: 0,
name: String::new(),
rom_name: String::new(),
}
}
pub fn handle_packet(&mut self, packet: &Packet) -> Option<Packet> {
if self.name.is_empty() {
let _ = self.name.push_str("rom");
}
match packet.kind {
Kind::PointerSet => {
if packet.payload.len() != 4 {
return Some(Packet::error(b"Invalid pointer length"));
}
self.pointer = u32::from_le_bytes(packet.payload[..].try_into().unwrap());
defmt::info!("Set pointer to {:#010x}", self.pointer);
None
}
Kind::PointerGet => Some(Packet {
kind: Kind::PointerCur,
payload: Vec::from_array(self.pointer.to_le_bytes()),
}),
Kind::Write => {
for byte in &packet.payload {
let Some(target) = ROM_DATA.get(self.pointer as usize) else {
self.pointer += 1;
// return Some(Packet::error(b"Write out of range"));
continue;
};
target.store(*byte, Ordering::Relaxed);
self.pointer += 1;
}
None
}
Kind::ParameterGet => match &packet.payload[..] {
b"name\0" => Some(Packet {
kind: Kind::Parameter,
payload: self.name.clone().into_bytes(),
}),
b"addr_mask\0" => Some(Packet {
kind: Kind::Parameter,
payload: Vec::from_array(*b"0x7fff"),
}),
b"rom_name\0" => Some(Packet {
kind: Kind::Parameter,
payload: self.rom_name.clone().into_bytes(),
}),
payload => {
if let Ok(name) = str::from_utf8(payload) {
defmt::warn!("Got unknonwn parameter request: {}", name);
} else {
defmt::warn!("Got unknonw parameter request: {}", payload);
}
Some(Packet {
kind: Kind::ParameterError,
payload: Vec::new(),
})
}
},
Kind::ParameterSet => {
let mut parts = packet.payload.split(|b| *b == b',');
let Some(name) = parts.next() else {
return Some(Packet {
kind: Kind::ParameterError,
payload: Vec::new(),
});
};
let Some(value) = parts.next() else {
return Some(Packet {
kind: Kind::ParameterError,
payload: Vec::new(),
});
};
match name {
b"name" => {
let Ok(value) = str::from_utf8(value) else {
return Some(Packet {
kind: Kind::ParameterError,
payload: Vec::new(),
});
};
self.name.clear();
let _ = self.name.push_str(value);
Some(Packet {
kind: Kind::Parameter,
payload: self.name.clone().into_bytes(),
})
}
b"addr_mask" => Some(Packet {
kind: Kind::Parameter,
payload: Vec::from_array(*b"0x7fff"),
}),
b"rom_name" => {
let Ok(value) = str::from_utf8(value) else {
return Some(Packet {
kind: Kind::ParameterError,
payload: Vec::new(),
});
};
self.rom_name.clear();
let _ = self.rom_name.push_str(value);
Some(Packet {
kind: Kind::Parameter,
payload: self.rom_name.clone().into_bytes(),
})
}
payload => {
if let Ok(name) = str::from_utf8(payload) {
defmt::warn!("Got unknonwn parameter request: {}", name);
} else {
defmt::warn!("Got unknonw parameter request: {}", payload);
}
Some(Packet {
kind: Kind::ParameterError,
payload: Vec::new(),
})
}
}
}
_ => Some(Packet::error(b"Unrecognized packet")),
}
}
}