first version, lets see what misstakes i made :D

This commit is contained in:
puckoprutt 2026-07-13 05:06:53 +02:00
commit 5bc5dec715
10 changed files with 1994 additions and 0 deletions

1054
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

35
Cargo.toml Normal file
View File

@ -0,0 +1,35 @@
[package]
name = "usb-terminal"
version = "0.1.0"
edition = "2024"
[profile.dev]
opt-level = 's' # default is opt-level = '0', but that makes very verbose machine code
codegen-units = 1 # trade compile speed for slightly better optimisations
# cargo build/run --release
[profile.release]
opt-level = 's' # default is opt-level = '3', but that makes quite verbose machine code
codegen-units = 1 # trade compile speed for slightly better optimisations
lto = 'fat' # Use Link Time Optimisations to further inline things across crates
debug = 2 # Leave the debug symbols in (default is no debug info)
strip = true # strip stupid stuff.
[[example]]
name = "print-banner"
path = "./src/example/usb-example.rs"
test = false
bench = false
[dependencies]
cortex-m = { version = "0.7.7" }
cortex-m-rt = "0.7.5"
critical-section = { version = "1.2.0", features = ["restore-state-u8"] }
embedded-hal = "1.0.0"
heapless = "0.9.3"
rp235x-hal = { version = "0.4.0", features = ["rt", "critical-section-impl"] }
semihosting = "0.1.25"
static_cell = "2.1.1"
usb-device = "0.3.2"
usbd-serial = "0.2.2"

31
README.md Normal file
View File

@ -0,0 +1,31 @@
> this is a fork of: [rp-usb-serial](https://github.com/sndnvaps/rp-usb-serial)
> i have added more functionality, on top of his/hers construction
> - credits to sndnvaps
# USB Terminal
this crate was created to give a smooth console that handles backspace and output with some color.
the original rp-usb-serial crate was a good foundation to build on in my journey to learning embedded rust.
## compiling the example
to compile just run:
´´´bash
cargo build --example print-banner
´´´
<img src="https://webchat.puckoprutt.tech/public.php/dav/files/n38qRnz2b8QP5fe/" width="300" style="margin: 0 auto;" alt="compiling.." />
## running the example
When running the example it should look like this:
<img src="https://webchat.puckoprutt.tech/public.php/dav/files/Q6Sg2mLSsdfqxCR/" width="300" style="margin: 0 auto;" alt="running example.." />
its made for terminals that can take ANSI-sequence (im guessing most of them do..) and it uses simple standard 8 colors.
example showing how to get notified when user presses enter and work with the whole line. and also how to split line into word.
usually word 0 is a command and the rest of words are arguments.
## check out the example source
just check in src/example

27
build.rs Normal file
View File

@ -0,0 +1,27 @@
//! Set up linker scripts for the rp235x-hal examples
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put the linker script somewhere the linker can find it
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out.display());
// The file `memory.x` is loaded by cortex-m-rt's `link.x` script, which
// is what we specify in `.cargo/config.toml` for Arm builds
let memory_x = include_bytes!("memory.x");
let mut f = File::create(out.join("memory.x")).unwrap();
f.write_all(memory_x).unwrap();
println!("cargo:rerun-if-changed=memory.x");
// The file `rp235x_riscv.x` is what we specify in `.cargo/config.toml` for
// RISC-V builds
let rp235x_riscv_x = include_bytes!("rp235x_riscv.x");
let mut f = File::create(out.join("rp235x_riscv.x")).unwrap();
f.write_all(rp235x_riscv_x).unwrap();
println!("cargo:rerun-if-changed=rp235x_riscv.x");
println!("cargo:rerun-if-changed=build.rs");
}

60
src/config.rs Normal file
View File

@ -0,0 +1,60 @@
//! USB Configuration
//!
//! # Examples
//! ```
//! use usb_terminal::config::UsbConfig;
//!
//! let conf = UsbConfig::new(
//! vendor : 0xffff,
//! device : 0xffff,
//! manufacturer : "puckoprutt",
//! product : "Nuclear driven submarine with built-in toaster",
//! serial_number : "yes"
//! );
//! ```
/// Simple configuration of USB
pub struct UsbConfig {
/// USB vendor ID
pub vendor : u16,
/// USB Device ID
pub device : u16,
/// USB Manufacturer
pub manufacturer : &'static str,
/// USB Product
pub product : &'static str,
/// USB Serial number
pub serial_number : &'static str,
}
impl UsbConfig {
/// Creates a new UsbConfig.
/// Note that the values manufacturer, product and serial_number have 'static lifetime
pub fn new(usb_vendor_id: u16, usb_device_id: u16, manufacturer: &'static str, product: &'static str, serial_number: &'static str) -> Self {
Self {
vendor : usb_vendor_id,
device : usb_device_id,
manufacturer : manufacturer,
product : product,
serial_number : serial_number
}
}
}
impl Default for UsbConfig {
/// creates UsbConfig with default values
/// - vendor : 0x1AD8
/// - device : 0xB00B
/// - manufacturer : "puckoprutt embedded tech"
/// - product : "Jordklots erövrare",
/// - serial_number : "bananbåt",
fn default() -> Self {
Self {
vendor : 0x1AD8,
device : 0xB00B,
manufacturer : "puckoprutt embedded tech",
product : "Jordklots erövrare",
serial_number : "bananbåt",
}
}
}

View File

@ -0,0 +1,93 @@
//! example tested on raspberry pi pico 2w.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use core::sync::atomic::Ordering;
use cortex_m::asm;
use cortex_m::peripheral::NVIC;
use cortex_m_rt::entry;
use usb_terminal::config::UsbConfig;
use usb_terminal::GLOBAL_EVENT;
use usb_terminal::Terminal;
use usb_terminal::rx::ReceiveHandler;
use rp235x_hal::{
self as hal,
block::ImageDef,
pac::Peripherals,
};
const XOSC_CRYSTAL_FREQ : u32 = 12000000;
#[entry]
fn main() -> ! {
let mut pac = Peripherals::take().unwrap();
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
// clock and timers
let clocks = hal::clocks::init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog
).ok().unwrap();
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 {
let event = GLOBAL_EVENT.load(Ordering::Acquire);
if (event >> 7) & 1 == 1 {
// interrupt has happened.
match rx.poll() {
Some(mut msg) => {
Terminal::clear_screen();
Terminal::print_banner();
for i in 0..msg.words {
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();
}
None => {
asm::nop();
}
}
}
}
}
#[unsafe(link_section = ".start_block")]
#[used]
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop { asm::udf() }
}
pub fn exit() -> ! {
semihosting::process::exit(0);
}
#[allow(non_snake_case)]
#[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
semihosting::process::exit(1);
}

393
src/lib.rs Normal file
View File

@ -0,0 +1,393 @@
//! 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::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);
Self::write(b"\r\n");
}
/// prints a info message.
pub fn pp_info(data: &[u8]) {
Self::write(b"\x1B[1;37m[puckoprutt]\x1B[0m <- ");
Self::write(data);
Self::write(b"\r\n");
}
/// 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"\r\n\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"\r\n\x1B[0m");
}
/// 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);
};
}
*/

119
src/message.rs Normal file
View File

@ -0,0 +1,119 @@
//! Messages from ReceiveHandler
//!
//! # Examples
//! ```
//! /// new_message needs parameter of type [u8; 128]
//! let message = UsbMessage::new_message(message);
//! let how_many_words: usize = message.words;
//! let command = message.get_command(); // same as get_word(0)
//! let argument1 = message.get_word(1);
//! let argument2 = message.get_word(2);
//! ```
use cortex_m::asm;
use super::Terminal;
/// trait for trim function
trait SliceExt {
fn trim(&self) -> &Self;
}
impl SliceExt for [u8; 128] {
/// trim function, will remove trailing spaces from left and right.
fn trim(&self) -> &[u8; 128] {
fn is_whitespace(c: &u8) -> bool {
*c == b'\t' || *c == b' '
}
fn is_not_whitespace(c: &u8) -> bool {
!is_whitespace(c)
}
if let Some(first) = self.iter().position(is_not_whitespace) {
if let Some(last) = self.iter().rposition(is_not_whitespace) {
self[first..last + 1].try_into().unwrap()
} else {
unreachable!();
}
} else {
&[0u8; 128]
}
}
}
/// holds a word, each word can be 16 characters long max.
pub struct UsbWord {
/// the word.
pub word: [u8; 16],
/// the length of the word.
pub size: usize
}
/// holds the whole message and the length
pub struct UsbMessage {
/// the line
pub words: usize,
/// the length
pub message: [u8; 128]
}
impl UsbMessage {
/// Creates a new UsbMessage
pub fn new_message(s: &[u8; 128]) -> Self{
Terminal::write(b"\r\n");
Terminal::pp_info(b"got: something");
Terminal::user_prompt();
let msg: &[u8; 128] = s.trim();
Self {
words : Self::count_word(msg),
message : msg.clone()
}
}
/// Count the words
fn count_word(s: &[u8]) -> usize {
let mut words: usize = 0;
let mut last_valid = true;
s.iter().take(s.len()).for_each(|w| {
if *w == 0x0 { asm::nop(); }
else if *w == 0x20 {
words += 1;
last_valid = false;
}
else {
last_valid = true
}
});
if last_valid {
words+1
} else {
words
}
}
/// gets the first word
pub fn get_command(&mut self) -> UsbWord {
self.get_word(0).unwrap()
}
/// gets the word provided or None if out of bounds.
pub fn get_word(&mut self, index: usize) -> Option<UsbWord> {
if index > self.words { return None; }
let mut word: usize = 0;
let mut ret_index: usize = 0;
let mut ret = [0u8; 16];
self.message.iter().take(self.message.len()).for_each(|w| {
if *w == 0x0 { asm::nop(); }
else if *w == 0x20 {
word += 1;
}
else if word == index {
ret[ret_index] = *w;
ret_index += 1;
}
});
Some(UsbWord{
word: ret,
size: ret_index
})
}
}

143
src/rx.rs Normal file
View File

@ -0,0 +1,143 @@
//! Receive Handling
//!
//! an interrupt occurs for every key pressed, this handler makes it easy to handle
//! in a line-by-line approach.
//!
//! # Examples
//! ```
//! use usb_terminal::Terminal
//! use usb_terminal::rx::ReceiveHandler
//! // -snip-
//! fn main() {
//! // do your weird dance...
//! let rx = ReceiveHandler::new()
//! // switch it up and do another weird dance
//!
//! // super loop in main
//! loop {
//! match rx.poll() {
//! // msg will be of type Some(UsbMessage) containing a line.
//! Some(mut msg) => {
//! // UsbMessage.words holds how many words there are in the line.
//! for i in 0..msg.words {
//! // echo back each word in red
//! Terminal::pp_danger(&msg.get_word(i).unwrap().word);
//! }
//! }
//! None => {
//! asm::nop();
//! }
//! }
//! }
//! } // main
//! ```
use core::sync::atomic::Ordering;
use cortex_m::asm;
use crate::{
Terminal, GLOBAL_EVENT, USB_RECEIVED_UNMASK,
special_chars::{
SpecialButton,
get_special_char,
is_backspace,
is_boring,
},
message::UsbMessage,
};
/// The receive handler.
/// will make it easy to handle commands and arguments sent over usb.
pub struct ReceiveHandler {
/// a special char like the home-button will come as 2 chars
/// this bool will be true if the '[' is received
/// then during the next poll a special character is recognised.
is_special_char : bool,
/// points to where in the buffer the cursor is.
buffer_ptr : usize,
/// buffer holding the received characters.
rx_buffer : [u8; 128],
}
impl ReceiveHandler {
/// Creates a new ReceiveHandler
///
/// # Example
/// ```
/// use usb_terminal::rx::ReceiveHandler
///
/// let rx = ReceiveHandler::new();
/// ```
pub fn new() -> Self {
Self {
is_special_char : false,
buffer_ptr : 0,
rx_buffer : [0u8; 128]
}
}
/// Polls the read message from the usb interrupt
/// will only return UsbMessage after \r or \n is received and rx buffer has more than 1 character.
/// will dismiss any character from ascii table 1-7 and 9-1F
/// backspace will be printed correct in any system that can read ANSI-sequences, and will
/// also erase the character from rx buffer.
pub fn poll(&mut self) -> Option<UsbMessage> {
let res = GLOBAL_EVENT.load(Ordering::Acquire) & USB_RECEIVED_UNMASK;
let mut temp_buffer = [0u8; 128];
let msg_size = Terminal::read(&mut temp_buffer);
let mut ret: Option<UsbMessage> = None;
temp_buffer.iter_mut().take(msg_size).for_each(|b| {
if self.is_special_char {
self.reset();
self.is_special_char = false;
Terminal::clear_screen();
Terminal::print_banner();
match get_special_char(b) {
SpecialButton::Home => { Terminal::pp_info(b"thats the home key."); }
SpecialButton::Insert => { Terminal::pp_info(b"thats the insert key."); }
SpecialButton::Delete => { Terminal::pp_danger(b"thats the delete key."); }
SpecialButton::ArrowUp => { Terminal::pp_info(b"thats the arrow up key."); }
SpecialButton::ArrowDown => { Terminal::pp_info(b"thats the arrow down key."); }
SpecialButton::ArrowRight => { Terminal::pp_debug(b"thats the arrow right key."); }
SpecialButton::ArrowLeft => { Terminal::pp_warning(b"thats the arrow left key."); }
SpecialButton::Unknown => { Terminal::pp_danger(b"thats the special key: {}"); }
}
}
else if *b == 0x5B {
self.is_special_char = true;
}
else if self.buffer_ptr > 0 && (*b == b'\n' || *b == b'\r') {
// we got a message.
// do something cool here
ret = Some(UsbMessage::new_message(&self.rx_buffer));
// after that reset buffer.
self.reset();
}
else if is_backspace(b) {
if self.buffer_ptr > 0 {
self.buffer_ptr -= 1;
self.rx_buffer[self.buffer_ptr] = 0;
Terminal::write(b"\x1B[1D\x1B[0J");
}
}
else if is_boring(b) {
asm::nop();
}
else {
self.rx_buffer[self.buffer_ptr] = *b;
self.buffer_ptr += 1;
Terminal::write(&[*b])
}
});
GLOBAL_EVENT.store(res, Ordering::Release);
ret
}
fn reset(&mut self) {
self.buffer_ptr = 0;
for z in self.rx_buffer.iter_mut() { *z = 0 }
}
}

39
src/special_chars.rs Normal file
View File

@ -0,0 +1,39 @@
//! Helper functions for special characters
/// Known special buttons
pub enum SpecialButton {
Home,
Insert,
Delete,
ArrowUp,
ArrowDown,
ArrowRight,
ArrowLeft,
Unknown
}
/// map special buttons to SpecialButton
pub fn get_special_char(ch: &u8) -> SpecialButton {
match *ch {
0x31 => { SpecialButton::Home }
0x32 => { SpecialButton::Insert }
0x33 => { SpecialButton::Delete }
0x41 => { SpecialButton::ArrowUp }
0x42 => { SpecialButton::ArrowDown }
0x43 => { SpecialButton::ArrowRight }
0x44 => { SpecialButton::ArrowLeft }
_ => { SpecialButton::Unknown }
}
}
/// if character received is backspace
pub fn is_backspace(ch: &u8) -> bool {
if *ch == 0x08 { return true }
false
}
/// if character received is any number below 'space' in ascii.
pub fn is_boring(ch: &u8) -> bool {
if *ch <= 0x1F {return true}
false
}