diff --git a/Cargo.toml b/Cargo.toml index d17c792..489e6b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,11 @@ rp2040-boot2 = "0.2.1" defmt-brtt = { version = "0.1.0", default-features = false } # We don't need bbqueue directly, but we need to activate the # thumbv6 feature on it so it will compile. -bbqueue = { version = "0.5", features = ["thumbv6"], optional = true } +bbqueue = { version = "0.5", features = ["thumbv6"] } [features] default = [ "defmt-brtt/rtt", "gnddetect" ] -defmt-bbq = ["defmt-brtt/bbq", "dep:bbqueue"] +defmt-bbq = ["defmt-brtt/bbq"] gnddetect = [] usb-serial-reboot = [] diff --git a/src/bin/app.rs b/src/bin/app.rs index ae27dd7..611f77c 100644 --- a/src/bin/app.rs +++ b/src/bin/app.rs @@ -14,11 +14,65 @@ mod app { use rp2040_hal::usb::UsbBus; use usb_device::class_prelude::*; - use rtic_monotonics::rp2040::{ExtU64, Timer}; + use rtic_monotonics::rp2040::{fugit::RateExtU32, ExtU64, Timer}; + + struct UsbUartState { + data_rate: u32, + data_bits: u8, + stop_bits: usbd_serial::StopBits, + parity_type: usbd_serial::ParityType, + } + + impl PartialEq for UsbUartState { + fn eq(&self, other: &usbd_serial::LineCoding) -> bool { + self.data_bits == other.data_bits() && + self.data_rate == other.data_rate() && + self.parity_type == other.parity_type() && + self.stop_bits == other.stop_bits() + } + } + + impl UsbUartState { + pub fn new(line_coding: &usbd_serial::LineCoding) -> Self { + Self { + data_rate: line_coding.data_rate(), + data_bits: line_coding.data_bits(), + stop_bits: line_coding.stop_bits(), + parity_type: line_coding.parity_type(), + } + } + + pub fn try_get_uart_config(&self) -> Option { + let data_bits = match self.data_bits { + 5 => Some(rp2040_hal::uart::DataBits::Five), + 6 => Some(rp2040_hal::uart::DataBits::Six), + 7 => Some(rp2040_hal::uart::DataBits::Seven), + 8 => Some(rp2040_hal::uart::DataBits::Eight), + _ => None, + }?; + let parity = match self.parity_type { + // "Event" is probably a typo + usbd_serial::ParityType::Event => Some(Some(rp2040_hal::uart::Parity::Even)), + usbd_serial::ParityType::Odd => Some(Some(rp2040_hal::uart::Parity::Odd)), + usbd_serial::ParityType::None => Some(None), + _ => None, + }?; + let stop_bits = match self.stop_bits { + usbd_serial::StopBits::One => Some(rp2040_hal::uart::StopBits::One), + usbd_serial::StopBits::Two => Some(rp2040_hal::uart::StopBits::Two), + _ => None + }?; + + Some(rp2040_hal::uart::UartConfig::new( + self.data_rate.Hz(), + data_bits, parity, stop_bits)) + } + } #[shared] struct Shared { probe_usb: pico_probe::usb::ProbeUsb, + uart: Uart, } #[local] @@ -28,6 +82,7 @@ mod app { translator_power: TranslatorPower, target_power: TargetPower, target_physically_connected: TargetPhysicallyConnected, + uart_state: Option, } #[init(local = [ @@ -38,6 +93,7 @@ mod app { let ( leds, probe_usb, + uart, dap_handler, target_vcc, translator_power, @@ -53,13 +109,14 @@ mod app { led_driver::spawn(leds).ok(); ( - Shared { probe_usb }, + Shared { probe_usb, uart }, Local { dap_handler, target_vcc, translator_power, target_power, target_physically_connected, + uart_state: None, }, ) } @@ -115,11 +172,13 @@ mod app { } } - #[task(binds = USBCTRL_IRQ, priority = 2, shared = [ probe_usb ], local = [dap_handler, resp_buf: [u8; 64] = [0; 64]])] + #[task(binds = USBCTRL_IRQ, priority = 2, shared = [ probe_usb, uart ], local = [dap_handler, uart_state, resp_buf: [u8; 64] = [0; 64]])] fn on_usb(ctx: on_usb::Context) { let mut probe_usb = ctx.shared.probe_usb; + let mut uart = ctx.shared.uart; let dap = ctx.local.dap_handler; let resp_buf = ctx.local.resp_buf; + let uart_state = ctx.local.uart_state; probe_usb.lock(|probe_usb| { if let Some(request) = probe_usb.interrupt() { @@ -146,6 +205,54 @@ mod app { } } } + + let uart_state_changed = uart_state.as_ref().map_or(true, |uart_state| !uart_state.eq(probe_usb.serial_line_coding())); + uart.lock(|uart| { + if uart_state_changed { + *uart_state = Some(UsbUartState::new(probe_usb.serial_line_coding())); + if let Some(uart_config) = uart_state.as_ref().unwrap().try_get_uart_config() { + uart.configure(uart_config); + } + } + + if buffer_uart_tx_data(probe_usb, uart) > 0 { + uart.flush_write_buffer(); + } + }); + }); + } + + #[task(binds = UART1_IRQ, priority = 3, shared = [probe_usb, uart])] + fn on_uart(ctx: on_uart::Context) { + let mut probe_usb = ctx.shared.probe_usb; + let mut uart = ctx.shared.uart; + + uart.lock(|uart| { + let mut uart_buf = [0u8; 32]; + let read_size = uart.read(&mut uart_buf); + probe_usb.lock(|probe_usb| { + if read_size > 0 { + probe_usb.serial_write(&uart_buf[..read_size]); + } + + buffer_uart_tx_data(probe_usb, uart); + }); + + uart.flush_write_buffer(); }); } + + fn buffer_uart_tx_data(probe_usb: &mut pico_probe::usb::ProbeUsb, uart: &mut Uart) -> usize { + if let Some(mut grant) = uart.try_grant_write(64) { + let used = probe_usb.serial_read(&mut grant); + #[cfg(feature = "usb-serial-reboot")] + if grant.len() >= 4 && &grant[..used] == &0xDABAD000u32.to_be_bytes() { + rp2040_hal::rom_data::reset_to_usb_boot(0, 0); + } + grant.commit(used); + used + } else { + 0 + } + } } diff --git a/src/lib.rs b/src/lib.rs index a9ff11f..b82e5af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod leds; pub mod pio; pub mod setup; pub mod systick_delay; +pub mod uart; pub mod usb; defmt::timestamp! {"{=u64:us}", { diff --git a/src/setup.rs b/src/setup.rs index e18091b..d347dce 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -33,6 +33,9 @@ pub type DapHandler = dap_rs::dap::Dap<'static, Context, HostStatusToken, Wait, #[used] pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; +type UartPins = (Pin, Pin); +pub type Uart = crate::uart::Uart; + #[inline(always)] pub fn setup( pac: pac::Peripherals, @@ -42,6 +45,7 @@ pub fn setup( ) -> ( LedManager, ProbeUsb, + Uart, DapHandler, TargetVccReader, TranslatorPower, @@ -95,6 +99,10 @@ pub fn setup( let sio = Sio::new(pac.SIO); let pins = Pins::new(pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut resets); + let uart = Uart::new(pac.UART1, (pins.gpio20.into_mode(), pins.gpio21.into_mode()), clocks.peripheral_clock.freq(), &mut resets); + let _dir_vcp_rx = pins.gpio25.into_push_pull_output_in_state(false.into()); + let _dir_vcp_tx = pins.gpio24.into_push_pull_output_in_state(true.into()); + let mut led_green = pins.gpio27.into_push_pull_output(); led_green.set_high().ok(); let mut led_red = pins.gpio28.into_push_pull_output(); @@ -158,10 +166,8 @@ pub fn setup( let _tdi = pins.gpio17; let _dir_tdi = pins.gpio23; - let _vcp_rx = pins.gpio21; - let _vcp_tx = pins.gpio20; - let _dir_vcp_rx = pins.gpio25; - let _dir_vcp_tx = pins.gpio24; + // let _vcp_rx = pins.gpio21; + // let _vcp_tx = pins.gpio20; // High speed IO io.set_drive_strength(OutputDriveStrength::TwelveMilliAmps); @@ -199,6 +205,7 @@ pub fn setup( ( led_manager, probe_usb, + uart, dap_hander, adc, translator_power, diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 0000000..68dce3d --- /dev/null +++ b/src/uart.rs @@ -0,0 +1,78 @@ +use rp2040_hal::uart; +use rtic_monotonics::rp2040::fugit::HertzU32; + +enum Device> { + Disabled(uart::UartPeripheral), + Enabled(uart::UartPeripheral) +} + +pub use uart::UartConfig as Config; + +const WRITE_BUFFER_SIZE: usize = 256; +static WRITE_BUFFER: bbqueue::BBBuffer = bbqueue::BBBuffer::new(); + +pub struct Uart> { + device: Option>, + peripheral_freq: HertzU32, + write_buffer_consumer: bbqueue::Consumer<'static, WRITE_BUFFER_SIZE>, + write_buffer_producer: bbqueue::Producer<'static, WRITE_BUFFER_SIZE>, +} + +impl> Uart { + pub fn new(device: D, pinout: P, peripheral_freq: HertzU32, resets: &mut rp2040_hal::pac::RESETS) -> Self { + let (producer, consumer) = WRITE_BUFFER.try_split().unwrap(); + let device = uart::UartPeripheral::new(device, pinout, resets); + Self { + device: Some(Device::Disabled(device)), + peripheral_freq, + write_buffer_consumer: consumer, + write_buffer_producer: producer, + } + } + + pub fn configure(&mut self, config: Config) { + let disabled_device = match self.device.take().unwrap() { + Device::Disabled(device) => device, + Device::Enabled(device) => device.disable(), + }; + + defmt::info!("configure uart: {}bps", config.baudrate.raw()); + + let mut device = disabled_device.enable(config, self.peripheral_freq).expect("failed to enable the uart peripheral"); + device.set_fifos(true); + device.enable_rx_interrupt(); + self.device = Some(Device::Enabled(device)); + } + + pub fn read(&self, data: &mut[u8]) -> usize { + if let Some(Device::Enabled(uart)) = self.device.as_ref() { + uart.read_raw(data).unwrap_or(0) + } else { + 0 + } + } + + pub fn try_grant_write(&mut self, max_size: usize) -> Option> { + self.write_buffer_producer.grant_max_remaining(max_size).ok() + } + + pub fn flush_write_buffer(&mut self) { + if let Some(Device::Enabled(uart)) = self.device.as_mut() { + if let Ok(grant) = self.write_buffer_consumer.split_read() { + let unused = uart.write_raw(grant.bufs().0).unwrap_or(grant.bufs().0); + let mut used = grant.bufs().0.len() - unused.len(); + if unused.len() == 0 { + let unused = uart.write_raw(grant.bufs().1).unwrap_or(grant.bufs().1); + used += grant.bufs().1.len() - unused.len(); + } + + if used < grant.combined_len() { + uart.enable_tx_interrupt(); + } else { + uart.disable_tx_interrupt(); + } + grant.release(used); + } + } + } +} \ No newline at end of file diff --git a/src/usb.rs b/src/usb.rs index 1a215b9..58e0a59 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -55,6 +55,22 @@ impl ProbeUsb { } } + pub fn serial_write(&mut self, data: &[u8]) -> usize { + if self.device.state() == UsbDeviceState::Configured && self.serial.dtr() { + self.serial.write(data).unwrap_or(0) + } else { + 0 + } + } + + pub fn serial_read(&mut self, data: &mut [u8]) -> usize { + self.serial.read(data).unwrap_or(0) + } + + pub fn serial_line_coding(&self) -> &usbd_serial::LineCoding { + self.serial.line_coding() + } + pub fn flush_logs(&mut self) { #[cfg(feature = "defmt-bbq")] { @@ -86,17 +102,6 @@ impl ProbeUsb { return Some(Request::Suspend); } - // Discard data from the serial interface - let mut buf = [0; 64 as usize]; - let _read_data = self.serial.read(&mut buf); - - #[cfg(feature = "usb-serial-reboot")] - if let Ok(read_data) = _read_data { - if &buf[..read_data] == &0xDABAD000u32.to_be_bytes() { - rp2040_hal::rom_data::reset_to_usb_boot(0, 0); - } - } - let r = self.dap_v1.process(); if r.is_some() { return r; diff --git a/uart-test/Cargo.lock b/uart-test/Cargo.lock new file mode 100644 index 0000000..0cefa65 --- /dev/null +++ b/uart-test/Cargo.lock @@ -0,0 +1,206 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serialport" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ba776acc8c373b9175829206229366273225436845c04f9c20aab8099960e2e" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "uart-test" +version = "0.1.0" +dependencies = [ + "serialport", +] + +[[package]] +name = "unescaper" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +dependencies = [ + "thiserror", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/uart-test/Cargo.toml b/uart-test/Cargo.toml new file mode 100644 index 0000000..2e7d8c3 --- /dev/null +++ b/uart-test/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "uart-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +serialport = "4.2" \ No newline at end of file diff --git a/uart-test/src/main.rs b/uart-test/src/main.rs new file mode 100644 index 0000000..9373c52 --- /dev/null +++ b/uart-test/src/main.rs @@ -0,0 +1,49 @@ +type SerialPort = Box; + +fn try_transmit(target: &mut SerialPort, host: &mut SerialPort, data: &[u8]) { + target.write_all(data).unwrap(); + let mut host_buffer = vec![0u8; data.len()]; + host.read_exact(&mut host_buffer).unwrap(); + assert_eq!(data, &host_buffer); +} + +enum Command { + TransmitCount(u32), + Bootload, +} + +impl Command { + fn from_command_line(arg: &String) -> Self { + if arg == "bootload" { + Self::Bootload + } else { + Self::TransmitCount(arg.parse::().expect("invalid count")) + } + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + if args.len() != 5 { + eprintln!("Usage: {} bootload|", args[0]); + std::process::exit(1); + } + + let target_path = args[1].as_str(); + let host_path = args[2].as_str(); + let baud_rate = args[3].parse::().expect("invalid baud rate"); + let command = Command::from_command_line(&args[4]); + + let mut target_serial = serialport::new(target_path, baud_rate).open().expect("failed to open target serial"); + target_serial.set_timeout(std::time::Duration::from_secs(1)); + + if let Command::TransmitCount(count) = command { + let mut host_serial = serialport::new(host_path, baud_rate).open().expect("failed to open host serial"); + host_serial.set_timeout(std::time::Duration::from_secs(1)); + let test_data: Vec = (0..count).collect(); + let test_data_bytes = unsafe{ std::slice::from_raw_parts(test_data.as_ptr() as *const u8, (count * 4) as usize) }; + try_transmit(&mut target_serial, &mut host_serial, test_data_bytes); + } else { + target_serial.write_all(&0xDABAD000u32.to_be_bytes()); + } +} \ No newline at end of file