Skip to content

Commit cad051d

Browse files
Merge pull request #264 from ferrous-systems/update-loopback-fw-jp-updates
My proposed changes to the async loopback-fw
2 parents ce2578f + c7a3c84 commit cad051d

File tree

1 file changed

+112
-63
lines changed

1 file changed

+112
-63
lines changed

nrf52-code/puzzle-fw/src/main.rs

Lines changed: 112 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
//! Firmware for the nRF52840 Dongle, for playing the puzzle game
22
//!
33
//! Sets up a USB Serial port and listens for radio packets.
4+
//!
5+
//! This application has two queues:
6+
//!
7+
//! * USB HID from host computer -> `usb_hid` task -> `HidTransferHandler` -> `MSG_CHANNEL` -> `radio` task
8+
//! * USB ACM from host computer -> `usb_acm` task -> `MSG_CHANNEL` -> `radio` task
9+
//! * various tasks -> `ACM_PIPE` - `usb_acm` task -> USB ACM to host computer
410
511
#![no_main]
612
#![no_std]
13+
#![deny(missing_docs)]
714

815
#[cfg(not(feature = "dk"))]
916
use bsp::RgbLed;
@@ -22,10 +29,27 @@ mod app {
2229
use rtic_monotonics::systick::prelude::*;
2330
use static_cell::StaticCell;
2431

25-
const MSG_QUEUE_LEN: usize = 8;
26-
const ACM_QUEUE_LEN: usize = 256;
32+
const MSG_CHANNEL_LEN: usize = 8;
33+
const ACM_PIPE_LEN: usize = 256;
2734
const MAX_ACM_PACKET_SIZE: usize = 64;
2835

36+
/// Handles commands from host, to application
37+
type MessageChannel =
38+
embassy_sync::channel::Channel<CriticalSectionRawMutex, Message, MSG_CHANNEL_LEN>;
39+
/// The receiving end of a [`MessageChannel`]
40+
type MessageChannelReceiver =
41+
embassy_sync::channel::Receiver<'static, CriticalSectionRawMutex, Message, MSG_CHANNEL_LEN>;
42+
/// The sending end of a [`MessageChannel`]
43+
type MessageChannelSender =
44+
embassy_sync::channel::Sender<'static, CriticalSectionRawMutex, Message, MSG_CHANNEL_LEN>;
45+
46+
/// Handles text output from application, to host
47+
type AcmPipe = embassy_sync::pipe::Pipe<CriticalSectionRawMutex, ACM_PIPE_LEN>;
48+
/// The reading end of an [`AcmPipe`]
49+
type AcmPipeReader = embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, ACM_PIPE_LEN>;
50+
/// The writing end of an [`AcmPipe`]
51+
type AcmPipeWriter = embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, ACM_PIPE_LEN>;
52+
2953
/// The secret message, but encoded.
3054
///
3155
/// We do this rather than the plaintext -- otherwise `strings $elf` will reveal the answer
@@ -57,33 +81,32 @@ mod app {
5781
// Intermediate buffer which is required because we can not used async code
5882
// in the [core::fmt::Write] implementation.
5983
buffer: heapless::String<256>,
60-
usb_acm_writer: embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, ACM_QUEUE_LEN>,
84+
acm_pipe_writer: AcmPipeWriter,
6185
}
6286

6387
impl core::fmt::Write for WriteAsyncPipeAdapter {
6488
fn write_str(&mut self, s: &str) -> core::fmt::Result {
65-
write!(self.buffer, "{}", s).unwrap();
66-
Ok(())
89+
write!(self.buffer, "{}", s)
6790
}
6891
}
6992

7093
impl WriteAsyncPipeAdapter {
7194
/// Flush the buffer to the underlying writer.
7295
async fn flush(&mut self) {
73-
self.usb_acm_writer.write_all(self.buffer.as_bytes()).await;
96+
self.acm_pipe_writer.write_all(self.buffer.as_bytes()).await;
7497
self.buffer.clear();
7598
}
7699
}
77100

101+
/// Messages we can get over USB HID which the radio task needs to handle
78102
#[derive(Debug, defmt::Format, Copy, Clone, PartialEq, Eq)]
79103
enum Message {
80104
ChangeChannel(u8),
81105
WantInfo,
82106
}
83107

84-
struct HidTransferHandler(
85-
embassy_sync::channel::Sender<'static, CriticalSectionRawMutex, Message, MSG_QUEUE_LEN>,
86-
);
108+
/// A helper for dealing with incoming USB HID events
109+
struct HidTransferHandler(MessageChannelSender);
87110

88111
impl hid::RequestHandler for HidTransferHandler {
89112
// HID requests are used to switch the channel.
@@ -115,20 +138,19 @@ mod app {
115138
rx_count: u32,
116139
/// How many packets have been received with errors?
117140
err_count: u32,
118-
/// A place to read the message queue
119-
msg_queue_rx: embassy_sync::channel::Receiver<
120-
'static,
121-
CriticalSectionRawMutex,
122-
Message,
123-
MSG_QUEUE_LEN,
124-
>,
125-
/// A place to write to the message queue
126-
msg_queue_tx_acm:
127-
embassy_sync::channel::Sender<'static, CriticalSectionRawMutex, Message, MSG_QUEUE_LEN>,
141+
/// A place to read from the message channel
142+
msg_channel_receiver: MessageChannelReceiver,
143+
/// A place to write to the message channel
144+
msg_channel_sender_acm: MessageChannelSender,
145+
/// The LED on the board
128146
leds: bsp::Leds,
147+
/// Our raw USB device
129148
usb_dev: embassy_usb::UsbDevice<'static, hal::usb::Driver<'static, HardwareVbusDetect>>,
130-
usb_acm_write_adapter: WriteAsyncPipeAdapter,
131-
usb_acm_reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, ACM_QUEUE_LEN>,
149+
/// Handles doing async writeln! to the USB ACM interface
150+
usb_acm_pipe_adapter: WriteAsyncPipeAdapter,
151+
/// Provides queued data to be written to USB ACM
152+
acm_pipe_reader: AcmPipeReader,
153+
/// The ACM part of our USB device interface
132154
usb_acm: embassy_usb::class::cdc_acm::CdcAcmClass<
133155
'static,
134156
hal::usb::Driver<'static, HardwareVbusDetect>,
@@ -249,19 +271,20 @@ mod app {
249271
#[cfg(not(feature = "dk"))]
250272
radio.set_channel(current_channel);
251273

252-
static MSG_QUEUE: static_cell::ConstStaticCell<
253-
embassy_sync::channel::Channel<CriticalSectionRawMutex, Message, MSG_QUEUE_LEN>,
254-
> = static_cell::ConstStaticCell::new(embassy_sync::channel::Channel::new());
255-
static ACM_QUEUE: static_cell::ConstStaticCell<
256-
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, ACM_QUEUE_LEN>,
257-
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
274+
static MSG_CHANNEL: static_cell::ConstStaticCell<MessageChannel> =
275+
static_cell::ConstStaticCell::new(embassy_sync::channel::Channel::new());
276+
static ACM_PIPE: static_cell::ConstStaticCell<AcmPipe> =
277+
static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
258278

259-
let msg_queue = MSG_QUEUE.take();
260-
let (acm_reader, acm_writer) = ACM_QUEUE.take().split();
279+
let msg_channel = MSG_CHANNEL.take();
280+
let msg_channel_receiver = msg_channel.receiver();
281+
let msg_channel_sender_acm = msg_channel.sender();
282+
let msg_channel_sender_hid = msg_channel.sender();
283+
let (acm_pipe_reader, acm_pipe_writer) = ACM_PIPE.take().split();
261284

262-
let acm_adapter = WriteAsyncPipeAdapter {
285+
let usb_acm_pipe_adapter = WriteAsyncPipeAdapter {
263286
buffer: heapless::String::new(),
264-
usb_acm_writer: acm_writer,
287+
acm_pipe_writer,
265288
};
266289

267290
defmt::debug!("Building structures...");
@@ -273,18 +296,18 @@ mod app {
273296
timer: board.timer,
274297
rx_count: 0,
275298
err_count: 0,
276-
msg_queue_rx: msg_queue.receiver(),
277-
msg_queue_tx_acm: msg_queue.sender(),
299+
msg_channel_receiver,
300+
msg_channel_sender_acm,
278301
leds: board.leds,
279302
usb_dev,
280303
usb_acm,
281-
usb_acm_write_adapter: acm_adapter,
282-
usb_acm_reader: acm_reader,
304+
usb_acm_pipe_adapter,
305+
acm_pipe_reader,
283306
};
284307

285308
usb_dev::spawn().unwrap();
286309
usb_acm::spawn().unwrap();
287-
let _ = usb_hid::spawn(hid_reader, msg_queue.sender());
310+
let _ = usb_hid::spawn(hid_reader, msg_channel_sender_hid);
288311
radio::spawn().unwrap();
289312

290313
defmt::debug!("Init Complete!");
@@ -312,28 +335,34 @@ mod app {
312335
}
313336
}
314337

338+
/// Run the USB Device
315339
#[task(local = [usb_dev], priority = 1)]
316340
async fn usb_dev(ctx: usb_dev::Context) {
317341
ctx.local.usb_dev.run().await;
318342
}
319343

344+
/// Handles USB HID data
320345
#[task(priority = 1)]
321346
async fn usb_hid(
322347
_ctx: usb_hid::Context,
323348
// Need to send this by value, because it is consumed by the run method.
324349
usb_hid_reader: hid::HidReader<'static, hal::usb::Driver<'static, HardwareVbusDetect>, 64>,
325-
msg_queue_tx_hid: embassy_sync::channel::Sender<
326-
'static,
327-
CriticalSectionRawMutex,
328-
Message,
329-
MSG_QUEUE_LEN,
330-
>,
350+
msg_channel_tx_hid: MessageChannelSender,
331351
) {
332-
let mut req_handler = HidTransferHandler(msg_queue_tx_hid);
352+
let mut req_handler = HidTransferHandler(msg_channel_tx_hid);
333353
usb_hid_reader.run(false, &mut req_handler).await;
334354
}
335355

336-
#[task(local = [usb_acm, msg_queue_tx_acm, usb_acm_reader], priority = 1)]
356+
/// This task handles the USB ACM interface
357+
///
358+
/// * Puts messages into the MSG_CHANNEL (via `msg_channel_sender_acm`) when
359+
/// text received from the USB ACM interface
360+
/// * Transfers text into the USB ACM interface, from the ACM_PIPE (via
361+
/// `acm_pipe_reader`)
362+
/// * Deals with being disconnected from the host
363+
///
364+
/// Defers to [`connected_usb_acm`] for most of the work
365+
#[task(local = [usb_acm, msg_channel_sender_acm, acm_pipe_reader], priority = 1)]
337366
async fn usb_acm(mut ctx: usb_acm::Context) {
338367
loop {
339368
// Wait for up to 200 ms for a connection, discard ACM data otherwise.
@@ -344,12 +373,21 @@ mod app {
344373
Err(_) => {
345374
let mut dummy_buf: [u8; 32] = [0; 32];
346375
// Timeout. Consume the message queue.
347-
while let Ok(_bytes_read) = ctx.local.usb_acm_reader.try_read(&mut dummy_buf) {}
376+
while let Ok(_bytes_read) = ctx.local.acm_pipe_reader.try_read(&mut dummy_buf) {
377+
}
348378
}
349379
}
350380
}
351381
}
352382

383+
/// This task handles the USB ACM interface
384+
///
385+
/// * Puts messages into the MSG_CHANNEL (via `msg_channel_sender_acm`) when
386+
/// text received from the USB ACM interface
387+
/// * Transfers text into the USB ACM interface, from the ACM_PIPE (via
388+
/// `acm_pipe_reader`)
389+
///
390+
/// Called by [`usb_acm`] when we are actually connected
353391
async fn connected_usb_acm(ctx: &mut usb_acm::Context<'_>) {
354392
let mut buffer = [0u8; MAX_ACM_PACKET_SIZE];
355393
loop {
@@ -363,7 +401,11 @@ mod app {
363401
for b in &buffer[0..n] {
364402
if *b == b'?' {
365403
// User pressed "?" in the terminal
366-
_ = ctx.local.msg_queue_tx_acm.send(Message::WantInfo).await;
404+
_ = ctx
405+
.local
406+
.msg_channel_sender_acm
407+
.send(Message::WantInfo)
408+
.await;
367409
}
368410
}
369411
}
@@ -378,7 +420,7 @@ mod app {
378420
}
379421
while let Ok(bytes_read) = ctx
380422
.local
381-
.usb_acm_reader
423+
.acm_pipe_reader
382424
.try_read(&mut buffer[0..MAX_ACM_PACKET_SIZE - 1])
383425
{
384426
match Mono::timeout_after(
@@ -405,23 +447,31 @@ mod app {
405447
}
406448
}
407449

450+
/// Commands we can receive over the radio
408451
enum Command {
409452
SendSecret,
410453
MapChar(u8, u8),
411454
Correct,
412455
Wrong,
413456
}
414457

458+
/// Handles the radio interface
459+
///
460+
/// * Listens for incoming data
461+
/// * Works out what command it is
462+
/// * Sends the appropriate response
463+
/// * Handles messages on the MSG_CHANNEL (via `msg_channel_receiver`)
464+
/// * Sends logs to the ACM_PIPE (via `usb_acm_pipe_adapter`)
415465
#[task(local = [
416466
radio,
417467
current_channel,
418468
packet,
419469
timer,
420470
rx_count,
421471
err_count,
422-
msg_queue_rx,
472+
msg_channel_receiver,
423473
leds,
424-
usb_acm_write_adapter,
474+
usb_acm_pipe_adapter,
425475
], priority = 2)]
426476
async fn radio(mut ctx: radio::Context) {
427477
defmt::info!(
@@ -442,7 +492,7 @@ mod app {
442492
}
443493

444494
loop {
445-
while let Ok(msg) = ctx.local.msg_queue_rx.try_receive() {
495+
while let Ok(msg) = ctx.local.msg_channel_receiver.try_receive() {
446496
match msg {
447497
Message::WantInfo => {
448498
defmt::info!(
@@ -452,20 +502,20 @@ mod app {
452502
ctx.local.current_channel
453503
);
454504
let _ = writeln!(
455-
&mut ctx.local.usb_acm_write_adapter,
505+
&mut ctx.local.usb_acm_pipe_adapter,
456506
"\nrx={}, err={}, ch={}, app=puzzle-fw",
457507
ctx.local.rx_count, ctx.local.err_count, ctx.local.current_channel
458508
);
459-
ctx.local.usb_acm_write_adapter.flush().await;
509+
ctx.local.usb_acm_pipe_adapter.flush().await;
460510
}
461511
Message::ChangeChannel(n) => {
462512
defmt::info!("Changing Channel to {}", n);
463513
let _ = writeln!(
464-
&mut ctx.local.usb_acm_write_adapter,
514+
&mut ctx.local.usb_acm_pipe_adapter,
465515
"\nChanging Channel to {}",
466516
n
467517
);
468-
ctx.local.usb_acm_write_adapter.flush().await;
518+
ctx.local.usb_acm_pipe_adapter.flush().await;
469519

470520
if !(11..=26).contains(&n) {
471521
defmt::info!("Bad Channel {}!", n);
@@ -510,7 +560,7 @@ mod app {
510560
*dest = *src;
511561
}
512562

513-
let _ = writeln!(&mut ctx.local.usb_acm_write_adapter, "TX Secret");
563+
let _ = writeln!(&mut ctx.local.usb_acm_pipe_adapter, "TX Secret");
514564
#[cfg(not(feature = "dk"))]
515565
{
516566
ctx.local.leds.ld2_rgb.blue.on();
@@ -522,7 +572,7 @@ mod app {
522572
ctx.local.packet.set_len(1 + ADDR_BYTES as u8);
523573
ctx.local.packet[ADDR_BYTES] = cipher;
524574
let _ = writeln!(
525-
&mut ctx.local.usb_acm_write_adapter,
575+
&mut ctx.local.usb_acm_pipe_adapter,
526576
"TX Map({plain}) => {cipher}"
527577
);
528578
#[cfg(not(feature = "dk"))]
@@ -542,8 +592,7 @@ mod app {
542592
{
543593
*dest = *src;
544594
}
545-
let _ =
546-
writeln!(&mut ctx.local.usb_acm_write_adapter, "TX Correct");
595+
let _ = writeln!(&mut ctx.local.usb_acm_pipe_adapter, "TX Correct");
547596
#[cfg(not(feature = "dk"))]
548597
{
549598
ctx.local.leds.ld2_rgb.blue.on();
@@ -562,7 +611,7 @@ mod app {
562611
*dest = *src;
563612
}
564613
let _ =
565-
writeln!(&mut ctx.local.usb_acm_write_adapter, "TX Incorrect");
614+
writeln!(&mut ctx.local.usb_acm_pipe_adapter, "TX Incorrect");
566615
#[cfg(not(feature = "dk"))]
567616
{
568617
ctx.local.leds.ld2_rgb.blue.off();
@@ -579,20 +628,20 @@ mod app {
579628
Mono::delay(500.micros()).await;
580629
if let Err(e) = ctx.local.radio.try_send(ctx.local.packet).await {
581630
let _ = writeln!(
582-
&mut ctx.local.usb_acm_write_adapter,
631+
&mut ctx.local.usb_acm_pipe_adapter,
583632
"\nWriting reply packet failed with error {:?}",
584633
e
585634
);
586635
}
587636
}
588-
ctx.local.usb_acm_write_adapter.flush().await;
637+
ctx.local.usb_acm_pipe_adapter.flush().await;
589638
}
590639
Err(_e) => {
591640
defmt::debug!("RX fail!");
592641
let _ = ctx
593642
.local
594-
.usb_acm_write_adapter
595-
.usb_acm_writer
643+
.usb_acm_pipe_adapter
644+
.acm_pipe_writer
596645
.write("!".as_bytes())
597646
.await;
598647
*ctx.local.err_count += 1;

0 commit comments

Comments
 (0)