Skip to content

Commit 42b9778

Browse files
committed
feat(examples): use USB serial as the standard output by default on RP2040
1 parent c3094cb commit 42b9778

File tree

3 files changed

+77
-18
lines changed

3 files changed

+77
-18
lines changed

examples/basic_rp_pico/README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ You need the following software to run this example.
88
- Try `nix-shell -p gcc-arm-embedded` if you use Nix(OS)
99
- [`elf2uf2`](https://github.com/raspberrypi/pico-sdk/tree/master/tools/elf2uf2) or [`elf2uf2-rs`](https://github.com/jonil/elf2uf2-rs)
1010

11-
You also need a UART adapter to observe the program's output. Connect its RX to the pinout 1 (GPIO 0, UART0 TX) of the board. Configure the adapter's baudrate to 115200.
12-
1311
First, place your Pico into BOOTSEL mode by holding the BOOTSEL button down while connecting it to your computer. You should see a volume named `RPI-RP2` being mounted if your Pico has successfully entered BOOTSEL mode.
1412

1513
After that, run the following commands (assuming `elf2uf2-rs` is installed):
@@ -20,4 +18,4 @@ cargo build --release
2018
elf2uf2-rs -d ../../target/thumbv6m-none-eabi/release/r3_example_basic_rp_pico
2119
```
2220

23-
You should see the on-board LED blinking after doing this. You should also see some serial output.
21+
You should see the on-board LED blinking after doing this. This program presents your Pico as a USB serial device. If you open it by a serial monitor app, you should see some textual output.

examples/basic_rp_pico/src/main.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ impl port::SysTickOptions for System {
3737
const FREQUENCY: u64 = 1_000_000;
3838
}
3939

40+
const USE_USB_UART: bool = true;
41+
4042
// --------------------------------------------------------------------------
4143

4244
#[derive(Debug)]
@@ -74,18 +76,22 @@ const fn configure_app(b: &mut CfgBuilder<System>) -> Objects {
7476
while p.RESETS.reset_done.read().pads_bank0().bit_is_clear() {}
7577
while p.RESETS.reset_done.read().io_bank0().bit_is_clear() {}
7678

77-
// Confiugre UART0
78-
use support_rp2040::serial::UartExt;
79-
let uart0 = p.UART0;
80-
uart0.reset(&p.RESETS);
81-
uart0.configure_pins(&p.IO_BANK0);
82-
uart0.configure_uart(115_200);
79+
if !USE_USB_UART {
80+
// Confiugre UART0
81+
use support_rp2040::serial::UartExt;
82+
let uart0 = p.UART0;
83+
uart0.reset(&p.RESETS);
84+
uart0.configure_pins(&p.IO_BANK0);
85+
uart0.configure_uart(115_200);
8386

84-
support_rp2040::stdout::set_stdout(uart0.into_nb_writer());
87+
support_rp2040::stdout::set_stdout(uart0.into_nb_writer());
88+
}
8589
})
8690
.finish(b);
8791

88-
support_rp2040::usbstdio::configure(b);
92+
if USE_USB_UART {
93+
support_rp2040::usbstdio::configure(b);
94+
}
8995

9096
System::configure_systick(b);
9197

src/r3_support_rp2040/src/usbstdio.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ struct UsbStdioGlobal {
2222
static USB_STDIO_GLOBAL: interrupt::Mutex<RefCell<Option<UsbStdioGlobal>>> =
2323
interrupt::Mutex::new(RefCell::new(None));
2424

25+
/// Start a no-interrupt section and get the global instance of
26+
/// `UsbStdioGlobal`. Will panic if the `UsbStdioGlobal` hasn't been initialized
27+
/// yet.
28+
fn with_usb_stdio_global<T>(f: impl FnOnce(&mut UsbStdioGlobal) -> T) -> T {
29+
interrupt::free(|cs| {
30+
let mut g = USB_STDIO_GLOBAL.borrow(cs).borrow_mut();
31+
let g = g
32+
.as_mut()
33+
.expect("UsbStdioGlobal hasn't been initialized yet");
34+
f(g)
35+
})
36+
}
37+
2538
/// Add a USB serial device to the system and register it as the destination of
2639
/// the standard output ([`crate::stdout`]).
2740
pub const fn configure<System: Kernel>(b: &mut CfgBuilder<System>) {
@@ -56,6 +69,9 @@ pub const fn configure<System: Kernel>(b: &mut CfgBuilder<System>) {
5669
*USB_STDIO_GLOBAL.borrow(cs).borrow_mut() =
5770
Some(UsbStdioGlobal { serial, usb_device })
5871
});
72+
73+
// Register the standard output
74+
crate::stdout::set_stdout(NbWriter);
5975
})
6076
.finish(b);
6177

@@ -71,12 +87,9 @@ pub const fn configure<System: Kernel>(b: &mut CfgBuilder<System>) {
7187
InterruptHandler::build()
7288
.line(int_num)
7389
.start(|_| {
74-
interrupt::free(|cs| {
75-
// Get the global `UsbStdioGlobal` instance, which should
76-
// have been created by the startup hook above
77-
let mut g = USB_STDIO_GLOBAL.borrow(cs).borrow_mut();
78-
let g = g.as_mut().unwrap();
79-
90+
// Get the global `UsbStdioGlobal` instance, which should
91+
// have been created by the startup hook above
92+
with_usb_stdio_global(|g| {
8093
g.usb_device.poll(&mut [&mut g.serial]);
8194

8295
let mut buf = [0; 64];
@@ -98,7 +111,49 @@ pub const fn configure<System: Kernel>(b: &mut CfgBuilder<System>) {
98111
.finish(b);
99112
}
100113

101-
// TODO: Hook this up to `crate::stdout`.
114+
struct NbWriter;
115+
116+
fn map_usb_error_to_nb_error(e: usb_device::UsbError) -> nb::Error<core::convert::Infallible> {
117+
match e {
118+
usb_device::UsbError::WouldBlock => nb::Error::WouldBlock,
119+
usb_device::UsbError::BufferOverflow
120+
| usb_device::UsbError::EndpointOverflow
121+
| usb_device::UsbError::Unsupported
122+
| usb_device::UsbError::InvalidEndpoint
123+
| usb_device::UsbError::EndpointMemoryOverflow => unreachable!("{:?}", e),
124+
// I think the following ones are protocol errors? I'm not sure
125+
// if they can be returned by `write` and `flush`.
126+
//
127+
// It's really a bad idea to gather all error codes in a single `enum`
128+
// without meticulously documenting how and when each of them will be
129+
// returned.
130+
usb_device::UsbError::ParseError | usb_device::UsbError::InvalidState => {
131+
panic!("{:?} is probably unexpected, but I'm not sure", e)
132+
}
133+
}
134+
}
135+
136+
impl embedded_hal::serial::Write<u8> for NbWriter {
137+
type Error = core::convert::Infallible;
138+
139+
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
140+
with_usb_stdio_global(|g| {
141+
// It's really inefficient to output one byte at once...
142+
let num_bytes_written = g.serial.write(&[word]).map_err(map_usb_error_to_nb_error)?;
143+
if num_bytes_written == 0 {
144+
Err(nb::Error::WouldBlock)
145+
} else {
146+
Ok(())
147+
}
148+
})
149+
}
150+
151+
fn flush(&mut self) -> nb::Result<(), Self::Error> {
152+
with_usb_stdio_global(|g| g.serial.flush().map_err(map_usb_error_to_nb_error))
153+
}
154+
}
155+
156+
// TODO:
102157
//
103158
// - The USB controller needs to be periodically polled to work correctly.
104159
// The panic handler should poll USB instead of doing nothing.

0 commit comments

Comments
 (0)