first version, lets see what misstakes i made :D
This commit is contained in:
commit
5bc5dec715
1054
Cargo.lock
generated
Normal file
1054
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
Cargo.toml
Normal file
35
Cargo.toml
Normal 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
31
README.md
Normal 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
27
build.rs
Normal 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
60
src/config.rs
Normal 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",
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/example/usb-example.rs
Normal file
93
src/example/usb-example.rs
Normal 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
393
src/lib.rs
Normal 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
119
src/message.rs
Normal 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
143
src/rx.rs
Normal 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
39
src/special_chars.rs
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user