usb-terminal/src/lib.rs

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);
};
}
*/