401 lines
13 KiB
Rust
401 lines
13 KiB
Rust
//! USB Terminal
|
|
//!
|
|
//! i forked the code from <https://github.com/sndnvaps/rp-usb-serial>
|
|
//! 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<RefCell<Option<Terminal>>> = Mutex::new(RefCell::new(None));
|
|
static GLOBAL_USB_BUS : StaticCell<UsbBusAllocator<hal::usb::UsbBus>> = 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<u8, USB_TX_BUF_SIZE>,
|
|
rx_buf : Queue<u8, USB_RX_BUF_SIZE>,
|
|
}
|
|
|
|
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);
|
|
};
|
|
}
|
|
*/ |