//! USB Terminal //! //! i forked the code from //! credits to sndnvaps. //! //! made some adjustments to fit my needs: //! - added some comments. //! - added a AtomicU8 GLOBAL_EVENT and made the interrupt set MSB, the other bits can be used for other interrupts. //! - added UsbConfig for setting init function //! - added ReceiveHandler //! - added UsbMessages //! - added UsbWord //! - added clear_screen() function that sends ANSI sequence to host machine. //! - added pp_debug(), pp_info(), pp_warning(), pp_danger() to print with some colors. //! - added a print_banner() function. //! - removed macros, print and println //! //! thinks thats it.. //! //! # Examples //! ``` //! use usb_terminal::config::UsbConfig; //! use usb_terminal::GLOBAL_EVENT; //! use usb_terminal::Terminal; //! use usb_terminal::rx::ReceiveHandler; //! // .... //! //! #[entry] //! fn main() -> ! { //! //! // init your HAL, do your weird ass dance and stuff.. //! //! let mut rx = ReceiveHandler::new(); //! Terminal::init( //! pac.USB, //! pac.USB_DPRAM, //! &mut pac.RESETS, //! clocks.usb_clock, //! UsbConfig::default() //! ); //! //! unsafe { //! NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ); //! } //! //! loop { //! // if MSB is 1 than an interrupt has happend //! let event = GLOBAL_EVENT.load(Ordering::Acquire); //! if (event >> 7) & 1 == 1 { //! // interrupt has happened. //! match rx.poll() { //! Some(mut msg) => { // msg is of type UsbMessage //! Terminal::clear_screen(); // sends ansi sequence to clear screen //! Terminal::print_banner(); // prints banner //! for i in 0..msg.words { // for each word in message echo back in the four styles. //! Terminal::pp_debug(&msg.get_word(i).unwrap().word); //! Terminal::pp_info(&msg.get_word(i).unwrap().word); //! Terminal::pp_warning(&msg.get_word(i).unwrap().word); //! Terminal::pp_danger(&msg.get_word(i).unwrap().word); //! } //! Terminal::user_prompt(); // prints out the user_prompt //! } //! None => { //! asm::nop(); // keep being weird, dance your weird dance. //! } //! } //! } //! } //! } //! ``` #![no_std] #![no_main] pub mod config; mod special_chars; pub mod message; pub mod rx; use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use core::cell::RefCell; use critical_section::Mutex; use heapless::String; use heapless::spsc::Queue; use static_cell::StaticCell; use usb_device::class_prelude::UsbBusAllocator; use usb_device::device::UsbDeviceState; use usb_device::prelude::*; use usbd_serial::SerialPort; use rp235x_hal as hal; use hal::pac::{self, interrupt}; use hal::usb::UsbBus; #[doc(inline)] use crate::config::UsbConfig; /// After the interrupt service routine for usb-serial has triggered the most significant bit of this /// global variable is set. GLOBAL_EVENT needs to hold the same data type as USB_RECEIVED_MASK and USB_RECEIVED_UNMASK. pub static GLOBAL_EVENT : AtomicU8 = AtomicU8::new(0u8); /// Received mask will be applied like: GLOBAL_EVENT | USB_RECEIVED_MASK pub const USB_RECEIVED_MASK : u8 = 0x80; /// After you acknoledge the event apply this like: GLOBAL_EVENT & USB_RECEIVED_MASK pub const USB_RECEIVED_UNMASK : u8 = 0x7F; static GLOBAL_USB_SERIAL : Mutex>> = Mutex::new(RefCell::new(None)); static GLOBAL_USB_BUS : StaticCell> = StaticCell::new(); static USB_INIT : AtomicBool = AtomicBool::new(false); const USB_RX_BUF_SIZE : usize = 256; const USB_TX_BUF_SIZE : usize = 256; /// Represents the Terminal pub struct Terminal { device : UsbDevice<'static, UsbBus>, serial : SerialPort<'static, UsbBus>, tx_buf : Queue, rx_buf : Queue, } impl Terminal { /// Initialize the terminal /// if private global AtomicBool USB_INIT is set, this function will do nothing. /// /// # Example /// ``` /// use usb_terminal::config::UsbConfig; /// use usb_terminal::Terminal; /// /// Terminal::init( /// pac_usb, /// pac_usb_dpram, /// pac_resets, /// usb_clock, /// UsbConfig::new( /// vendor : 0xD00D, /// device : 0xB00B, /// manufacturer : "puckoprutt embedded tech", /// product : "Jordklots erövrare", /// serial_number : "bananbåt", /// )); /// // make the world brighter with your weird dance <3 /// ``` pub fn init(usbctrl_reg: pac::USB, usbctrl_dpram: pac::USB_DPRAM, usb_reset: &mut pac::RESETS, clocks: hal::clocks::UsbClock, config: UsbConfig) { if USB_INIT.load(Ordering::SeqCst) { return; } let usb_bus = UsbBus::new(usbctrl_reg, usbctrl_dpram, clocks, true, usb_reset); let alloc = GLOBAL_USB_BUS.init(UsbBusAllocator::new(usb_bus)); let serial = SerialPort::new(alloc); let device = UsbDeviceBuilder::new(alloc, UsbVidPid(config.vendor, config.device)) .strings(&[StringDescriptors::default() .manufacturer(config.manufacturer) .product(config.product) .serial_number(config.serial_number)]) .unwrap() .device_class(2) .build(); critical_section::with(|cs| { GLOBAL_USB_SERIAL.borrow(cs).replace(Some(Self { device, serial, tx_buf: Queue::new(), rx_buf: Queue::new(), })); }); USB_INIT.store(true, Ordering::SeqCst); } /// poll command will run when an interrupt occurs. /// if private AtomicBool USB_INIT is not set this function will not do anything. /// else the following private commands in this order: /// - force_poll /// - it will take ahold of GLOBAL_USB_SERIAL mutex and poll latest data if any new data /// - pump_rx_echo /// - takes the GLOBAL_USB_SERIAL mutex and move the latest data to internal buffer. and also sets MSB of GLOBAL_EVENT /// - flush_tx /// - flushes the transmit buffer /// /// this command will run when an interrupt occurs pub fn poll() { Self::force_poll(); Self::pump_rx_echo(); Self::flush_tx(); } #[inline(always)] fn force_poll() { if !USB_INIT.load(Ordering::SeqCst) { return; } critical_section::with(|cs| { let mut opt = GLOBAL_USB_SERIAL.borrow(cs).borrow_mut(); if let Some(usb) = opt.as_mut() { usb.device.poll(&mut [&mut usb.serial]); } }); } fn pump_rx_echo() { if !USB_INIT.load(Ordering::SeqCst) { return; } critical_section::with(|cs| { let mut opt = GLOBAL_USB_SERIAL.borrow(cs).borrow_mut(); let Some(usb) = opt.as_mut() else { return }; if usb.device.state() != UsbDeviceState::Configured { return; } let mut temp = [0u8; 64]; loop { match usb.serial.read(&mut temp) { Ok(0) => break, Ok(n) => { for &b in &temp[..n] { let _ = usb.rx_buf.enqueue(b); } let res = GLOBAL_EVENT.load(Ordering::Acquire) | USB_RECEIVED_MASK; GLOBAL_EVENT.store(res, Ordering::Release); } Err(usb_device::UsbError::WouldBlock) => break, Err(_) => break, } } }); } fn flush_tx() { if !USB_INIT.load(Ordering::SeqCst) { return; } critical_section::with(|cs| { let mut opt = GLOBAL_USB_SERIAL.borrow(cs).borrow_mut(); let Some(usb) = opt.as_mut() else { return }; if usb.device.state() != UsbDeviceState::Configured { return; } while let Some(b) = usb.tx_buf.dequeue() { if usb.serial.write(&[b]).is_err() { let _ = usb.tx_buf.enqueue(b); break; } } }); } /// writes a byte array to transmit buffer. pub fn write(data: &[u8]) { if !USB_INIT.load(Ordering::SeqCst) { return; } critical_section::with(|cs| { let mut binding = GLOBAL_USB_SERIAL.borrow(cs).borrow_mut(); if let Some(usb) = binding.as_mut() { for &b in data { let _ = usb.tx_buf.enqueue(b); } } }); Self::poll(); } /// prints a pretty banner. pub fn print_banner() { Self::write(b" 8 o o \r\n"); Self::write(b" 8 8 8 \r\n"); Self::write(b"\x1B[0;31m.oPYo.\x1B[0m o o .oPYo. 8 .o .oPYo. \x1B[0;31m.oPYo.\x1B[0m oPYo. o o o8P o8P \r\n"); Self::write(b"\x1B[0;31m8 8\x1B[0m 8 8 8 ' 8oP' 8 8 \x1B[0;31m8 8\x1B[0m 8 `' 8 8 8 8 \r\n"); Self::write(b"\x1B[0;31m8 8\x1B[0m 8 8 8 . 8 `b. 8 8 \x1B[0;31m8 8\x1B[0m 8 8 8 8 8 \r\n"); Self::write(b"\x1B[0;31m8YooP'\x1B[0m `YooP' `YooP' 8 `o. `YooP' \x1B[0;31m8YooP'\x1B[0m 8 `YooP' 8 8 \r\n"); Self::write(b"\x1B[0;31m8\x1B[0m \x1B[0;32m....::.....::.....:..::...:.....:\x1B[0m\x1B[0;31m8\x1B[0m \x1B[0;32m....:..:::::.....:::..:::..:\x1B[0m\r\n"); Self::write(b"\x1B[0;31m8\x1B[0m \x1B[0;32m:::::::::::::::::::::::::::::::::\x1B[0m\x1B[0;31m8\x1B[0m \x1B[0;32m::::::\x1B[5m \x1B[0membedded tech \x1B[25m\x1B[0;32m:::::::\r\n"); Self::write(b"\x1B[0;32m..:::::::::::::::::::::::::::::::::..::::::::::::::::::::::::::::\x1B[0m\r\n\r\n"); } /// prints the user prompt. pub fn user_prompt() { Self::write(b"\x1B[0;32m[cool dude]\x1B[0m -> "); } /// prints a debug message. pub fn pp_debug(data: &[u8]) { Self::write(b"[puckoprutt] <- "); Self::write(data); } /// prints a info message. pub fn pp_info(data: &[u8]) { Self::write(b"\x1B[1;34m[puckoprutt]\x1B[0m <- "); Self::write(data); } /// prints a warning message. pub fn pp_warning(data: &[u8]) { Self::write(b"\x1B[43m\x1B[4;37m[puckoprutt]\x1B[0m <- \x1B[4;33m"); Self::write(data); Self::write(b"\x1B[0m"); } /// prints a danger/error message. pub fn pp_danger(data: &[u8]) { Self::write(b"\x1B[41m\x1B[1;37m[puckoprutt]\x1B[0m !!DANGER!! \x1B[1;31m"); Self::write(data); Self::write(b"\x1B[0m"); } pub fn pp_println(data: String<16>, loglevel: u8) { match loglevel { 30..=39 => Self::pp_danger(data.as_bytes()), 20..=29 => Self::pp_warning(data.as_bytes()), 10..=19 => Self::pp_info(data.as_bytes()), _ => Self::pp_debug(data.as_bytes()), } } /// clears the screen with a simple ANSI sequence. pub fn clear_screen() { Self::write(b"\x1B[2J"); } /// reads internal buffer to a external buffer and returns the size of the buffer. pub fn read(buf: &mut [u8]) -> usize { if !USB_INIT.load(Ordering::SeqCst) { return 0; } critical_section::with(|cs| { let mut opt = GLOBAL_USB_SERIAL.borrow(cs).borrow_mut(); let Some(usb) = opt.as_mut() else { return 0 }; let mut n = 0; while n < buf.len() { match usb.rx_buf.dequeue() { Some(b) => { buf[n] = b; n += 1; } None => break, } } n }) } } #[inline(always)] fn usbctrl_irq_impl() { Terminal::poll(); } #[allow(non_snake_case)] #[interrupt] fn USBCTRL_IRQ() { usbctrl_irq_impl(); } /* #[macro_export] macro_rules! pp_debug { ($($tt:tt)*) => { $crate::Terminal::fmt_write(format_args!($($tt)*), 0); }; } #[macro_export] macro_rules! pp_info { ($($tt:tt)*) => { $crate::Terminal::fmt_write(format_args!($($tt)*), 10); }; } #[macro_export] macro_rules! pp_warning { ($($tt:tt)*) => { $crate::Terminal::fmt_write(format_args!($($tt)*), 20); }; } #[macro_export] macro_rules! pp_danger { ($($tt:tt)*) => { $crate::Terminal::fmt_write(format_args!($($tt)*), 30); }; } */