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