Skip to content

Commit 187f5d7

Browse files
committed
feat(examples): forward USB serial input to LCD
1 parent 557893f commit 187f5d7

File tree

3 files changed

+223
-16
lines changed

3 files changed

+223
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/basic_wio_terminal/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ embedded-graphics = { version = "0.7.1" }
1717
wio_terminal = { version = "0.4" }
1818
cortex-m-rt = { version = "0.6.12" }
1919
usbd-serial = { version = "0.1" }
20+
usb-device = { version = "0.2.7" }
2021
atsamd-hal = { version = "0.13", features = ["samd51p"] }
2122
arrayvec = { version = "0.7.1", default-features = false }
2223
cortex-m = { version = "*" }

examples/basic_wio_terminal/src/main.rs

Lines changed: 221 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(const_fn_trait_bound)]
33
#![feature(const_fn_fn_ptr_basics)]
44
#![feature(const_mut_refs)]
5+
#![feature(let_else)]
56
#![deny(unsafe_op_in_unsafe_fn)]
67
#![deny(unsupported_naked_functions)]
78
#![no_std]
@@ -13,18 +14,25 @@ use r3_port_arm_m as port;
1314
use wio_terminal as wio;
1415

1516
use core::{
17+
cell::RefCell,
1618
fmt::Write,
1719
panic::PanicInfo,
1820
sync::atomic::{AtomicUsize, Ordering},
1921
};
22+
use cortex_m::{interrupt::Mutex as PrimaskMutex, singleton};
2023
use eg::{image::Image, mono_font, pixelcolor::Rgb565, prelude::*, primitives, text};
2124
use r3::{
22-
kernel::{cfg::CfgBuilder, Mutex, StartupHook, Task},
25+
kernel::{cfg::CfgBuilder, InterruptLine, InterruptNum, Mutex, StartupHook, Task, Timer},
2326
prelude::*,
2427
};
2528
use spin::Mutex as SpinMutex;
29+
use usb_device::{
30+
bus::UsbBusAllocator,
31+
device::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
32+
};
33+
use usbd_serial::{SerialPort, USB_CLASS_CDC};
2634
use wio::{
27-
hal::{clock::GenericClockController, delay::Delay, gpio},
35+
hal::{clock::GenericClockController, delay::Delay, gpio, usb::UsbBus},
2836
pac::{CorePeripherals, Peripherals},
2937
prelude::*,
3038
Pins, Sets,
@@ -40,6 +48,7 @@ impl port::ThreadingOptions for System {}
4048

4149
impl port::SysTickOptions for System {
4250
const FREQUENCY: u64 = 120_000_000; // ??
51+
const TICK_PERIOD: u32 = Self::FREQUENCY as u32 / 500; // 2ms
4352
}
4453

4554
/// This part is `port::use_rt!` minus `__INTERRUPTS`. `wio_terminal`'s default
@@ -86,6 +95,9 @@ struct Objects {
8695
console_pipe: queue::Queue<System, u8>,
8796
lcd_mutex: Mutex<System>,
8897
button_reporter_task: Task<System>,
98+
usb_in_task: Task<System>,
99+
usb_poll_timer: Timer<System>,
100+
usb_interrupt_lines: [InterruptLine<System>; 3],
89101
}
90102

91103
const COTTAGE: Objects = r3::build!(System, configure_app => Objects);
@@ -121,6 +133,36 @@ const fn configure_app(b: &mut CfgBuilder<System>) -> Objects {
121133
.active(true)
122134
.finish(b);
123135

136+
// USB input handler
137+
let usb_in_task = Task::build()
138+
.start(usb_in_task_body)
139+
.priority(2)
140+
.active(true)
141+
.finish(b);
142+
let usb_poll_timer = Timer::build()
143+
.start(usb_poll_timer_handler)
144+
.delay(r3::time::Duration::from_millis(0))
145+
// Should be < 10ms for USB compliance
146+
.period(r3::time::Duration::from_millis(5))
147+
.finish(b);
148+
let usb_interrupt_lines = [
149+
InterruptLine::build()
150+
.line(interrupt::USB_OTHER as InterruptNum + port::INTERRUPT_EXTERNAL0)
151+
.priority(1)
152+
.enabled(true)
153+
.finish(b),
154+
InterruptLine::build()
155+
.line(interrupt::USB_TRCPT0 as InterruptNum + port::INTERRUPT_EXTERNAL0)
156+
.priority(1)
157+
.enabled(true)
158+
.finish(b),
159+
InterruptLine::build()
160+
.line(interrupt::USB_TRCPT1 as InterruptNum + port::INTERRUPT_EXTERNAL0)
161+
.priority(1)
162+
.enabled(true)
163+
.finish(b),
164+
];
165+
124166
// Graphics-related tasks and objects
125167
let _animation_task = Task::build()
126168
.start(animation_task_body)
@@ -139,6 +181,9 @@ const fn configure_app(b: &mut CfgBuilder<System>) -> Objects {
139181
console_pipe,
140182
lcd_mutex,
141183
button_reporter_task,
184+
usb_in_task,
185+
usb_poll_timer,
186+
usb_interrupt_lines,
142187
}
143188
}
144189

@@ -196,8 +241,53 @@ fn init_hardware() {
196241
);
197242
button_ctrlr.enable(&mut core_peripherals.NVIC);
198243
unsafe { BUTTON_CTRLR = Some(button_ctrlr) };
244+
245+
// Configure the USB serial device
246+
let sets_usb = sets.usb;
247+
let peripherals_usb = peripherals.USB;
248+
let peripherals_mclk = &mut peripherals.MCLK;
249+
let usb_bus_allocator = singleton!(
250+
: UsbBusAllocator<UsbBus> =
251+
sets_usb.usb_allocator(
252+
peripherals_usb,
253+
&mut clocks,
254+
peripherals_mclk,
255+
)
256+
)
257+
.unwrap();
258+
let serial = SerialPort::new(usb_bus_allocator);
259+
let usb_device = UsbDeviceBuilder::new(usb_bus_allocator, UsbVidPid(0x16c0, 0x27dd))
260+
.product("R3 Example")
261+
.device_class(USB_CLASS_CDC)
262+
.max_packet_size_0(64)
263+
.build();
264+
*USB_STDIO_GLOBAL.lock() = Some(UsbStdioGlobal { serial, usb_device });
199265
}
200266

267+
// Message producer
268+
// ----------------------------------------------------------------------------
269+
270+
/// The task responsible for outputting messages to the console.
271+
fn noisy_task_body(_: usize) {
272+
let _ = writeln!(Console, "////////////////////////////////");
273+
let _ = writeln!(
274+
Console,
275+
"Hello! Send text to me over the USB serial port \
276+
(e.g., `/dev/ttyACM0`), and I'll display it!"
277+
);
278+
let _ = writeln!(Console, "////////////////////////////////");
279+
loop {
280+
// Print a message
281+
let _ = write!(Console, "-- {:?} --", System::time().unwrap());
282+
283+
System::sleep(r3::time::Duration::from_secs(60)).unwrap();
284+
let _ = writeln!(Console);
285+
}
286+
}
287+
288+
// Console and graphics
289+
// ----------------------------------------------------------------------------
290+
201291
/// Acquire a lock on `wio::LCD`, yielding the CPU to lower-priority tasks as
202292
/// necessary.
203293
///
@@ -242,20 +332,6 @@ impl Write for Console {
242332
}
243333
}
244334

245-
/// The task responsible for outputting messages to the console.
246-
fn noisy_task_body(_: usize) {
247-
loop {
248-
// Print a message
249-
let _ = write!(Console, "time = {:?}", System::time().unwrap());
250-
251-
for _ in 0..10 {
252-
let _ = write!(Console, ".");
253-
System::sleep(r3::time::Duration::from_millis(55)).unwrap();
254-
}
255-
let _ = writeln!(Console);
256-
}
257-
}
258-
259335
/// The task responsible for blinking the user LED.
260336
fn blink_task_body(_: usize) {
261337
let mut st = BLINK_ST.lock();
@@ -363,6 +439,9 @@ fn animation_task_body(_: usize) {
363439
}
364440
}
365441

442+
// Button listener
443+
// ----------------------------------------------------------------------------
444+
366445
static BUTTON_STATE: AtomicUsize = AtomicUsize::new(0);
367446

368447
/// The task responsible for reporting button events.
@@ -434,6 +513,132 @@ wio::button_interrupt! {
434513
}
435514
}
436515

516+
// USB serial
517+
// ----------------------------------------------------------------------------
518+
519+
struct UsbStdioGlobal {
520+
usb_device: UsbDevice<'static, UsbBus>,
521+
serial: SerialPort<'static, UsbBus>,
522+
}
523+
524+
/// Stores [`UsbStdioGlobal`]. Only accessed by `poll_usb` (the USB interrupt
525+
/// handler).
526+
static USB_STDIO_GLOBAL: SpinMutex<Option<UsbStdioGlobal>> = SpinMutex::new(None);
527+
528+
/// The USB input queue size
529+
const USB_BUF_CAP: usize = 64;
530+
531+
/// The queue through which received data is passed from `poll_usb` to
532+
/// `usb_in_task_body`
533+
static USB_BUF_IN: PrimaskMutex<RefCell<([u8; USB_BUF_CAP], usize)>> =
534+
PrimaskMutex::new(RefCell::new(([0; USB_BUF_CAP], 0)));
535+
536+
/// USB interrupt handler
537+
fn poll_usb() {
538+
let Some(mut g) = USB_STDIO_GLOBAL.try_lock() else { return };
539+
let g = g.as_mut().unwrap();
540+
541+
// It's important that we poll the USB device frequently enough
542+
g.usb_device.poll(&mut [&mut g.serial]);
543+
544+
let mut should_unpark = false;
545+
let mut should_start_polling = false;
546+
547+
disable_interrupts(|cs| {
548+
let mut usb_buf_in = USB_BUF_IN.borrow(cs).borrow_mut();
549+
let (buf, buf_len) = &mut *usb_buf_in;
550+
let remaining = &mut buf[*buf_len..];
551+
if remaining.is_empty() {
552+
// We can't process the data fast enough; apply back-pressure.
553+
// Also, disable the USB interrupt lines because we would otherwise
554+
// get an interrupt storm. (I'm surprised we have to do this. Is
555+
// this really the proper way to apply back-pressure?)
556+
should_start_polling = true;
557+
return;
558+
}
559+
560+
if let Ok(len) = g.serial.read(remaining) {
561+
assert!(len <= remaining.len());
562+
*buf_len += len;
563+
should_unpark = len > 0;
564+
}
565+
});
566+
567+
// In this configuration `disable_interrupts` is equivalent to CPU Lock, so
568+
// kernel functions cannot be called inside it
569+
if should_unpark {
570+
COTTAGE.usb_in_task.unpark().unwrap();
571+
}
572+
573+
if should_start_polling {
574+
set_usb_polling(true);
575+
}
576+
}
577+
578+
#[interrupt]
579+
fn USB_OTHER() {
580+
poll_usb();
581+
}
582+
583+
#[interrupt]
584+
fn USB_TRCPT0() {
585+
poll_usb();
586+
}
587+
588+
#[interrupt]
589+
fn USB_TRCPT1() {
590+
poll_usb();
591+
}
592+
593+
fn usb_poll_timer_handler(_: usize) {
594+
poll_usb();
595+
}
596+
597+
/// Change whether `poll_usb` is called in response to USB interrupts or in a
598+
/// constant interval.
599+
fn set_usb_polling(b: bool) {
600+
if b {
601+
COTTAGE.usb_poll_timer.start().unwrap();
602+
for line in COTTAGE.usb_interrupt_lines.iter() {
603+
line.disable().unwrap();
604+
}
605+
} else {
606+
COTTAGE.usb_poll_timer.stop().unwrap();
607+
for line in COTTAGE.usb_interrupt_lines.iter() {
608+
line.enable().unwrap();
609+
}
610+
}
611+
}
612+
613+
/// The task to print the data received by the USB serial endpoint
614+
fn usb_in_task_body(_: usize) {
615+
let mut data = arrayvec::ArrayVec::<u8, USB_BUF_CAP>::new();
616+
loop {
617+
// Get next data to output
618+
disable_interrupts(|cs| {
619+
let mut usb_buf_in = USB_BUF_IN.borrow(cs).borrow_mut();
620+
let (buf, buf_len) = &mut *usb_buf_in;
621+
data.clear();
622+
data.try_extend_from_slice(&buf[..*buf_len]).unwrap();
623+
*buf_len = 0;
624+
});
625+
626+
if data.is_empty() {
627+
// Got nothing; sleep until new data arrives
628+
System::park().unwrap();
629+
continue;
630+
}
631+
632+
if data.is_full() {
633+
set_usb_polling(false);
634+
}
635+
636+
// Send it to the console
637+
let data = core::str::from_utf8(&data).unwrap_or("");
638+
let _ = Console.write_str(data);
639+
}
640+
}
641+
437642
// Utilities
438643
// ----------------------------------------------------------------------------
439644

0 commit comments

Comments
 (0)