/
pico_usb_serial_interrupt.rs
209 lines (175 loc) · 6.3 KB
/
pico_usb_serial_interrupt.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! # Pico USB Serial (with Interrupts) Example
//!
//! Creates a USB Serial device on a Pico board, with the USB driver running in
//! the USB interrupt.
//!
//! This will create a USB Serial device echoing anything it receives. Incoming
//! ASCII characters are converted to upercase, so you can tell it is working
//! and not just local-echo!
//!
//! See the `Cargo.toml` file for Copyright and license details.
#![no_std]
#![no_main]
// The macro for our start-up function
use rp_pico::entry;
// The macro for marking our interrupt functions
use rp_pico::hal::pac::interrupt;
// GPIO traits
use embedded_hal::digital::OutputPin;
// Ensure we halt the program on panic (if we don't mention this crate it won't
// be linked)
use panic_halt as _;
// Pull in any important traits
use rp_pico::hal::prelude::*;
// A shorter alias for the Peripheral Access Crate, which provides low-level
// register access
use rp_pico::hal::pac;
// A shorter alias for the Hardware Abstraction Layer, which provides
// higher-level drivers.
use rp_pico::hal;
// USB Device support
use usb_device::{class_prelude::*, prelude::*};
// USB Communications Class Device support
use usbd_serial::SerialPort;
/// The USB Device Driver (shared with the interrupt).
static mut USB_DEVICE: Option<UsbDevice<hal::usb::UsbBus>> = None;
/// The USB Bus Driver (shared with the interrupt).
static mut USB_BUS: Option<UsbBusAllocator<hal::usb::UsbBus>> = None;
/// The USB Serial Device Driver (shared with the interrupt).
static mut USB_SERIAL: Option<SerialPort<hal::usb::UsbBus>> = None;
/// Entry point to our bare-metal application.
///
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables are initialised.
///
/// The function configures the RP2040 peripherals, then blinks the LED in an
/// infinite loop.
#[entry]
fn main() -> ! {
// Grab our singleton objects
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
// Set up the watchdog driver - needed by the clock setup code
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
// Configure the clocks
//
// The default is to generate a 125 MHz system clock
let clocks = hal::clocks::init_clocks_and_plls(
rp_pico::XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// Set up the USB driver
let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new(
pac.USBCTRL_REGS,
pac.USBCTRL_DPRAM,
clocks.usb_clock,
true,
&mut pac.RESETS,
));
unsafe {
// Note (safety): This is safe as interrupts haven't been started yet
USB_BUS = Some(usb_bus);
}
// Grab a reference to the USB Bus allocator. We are promising to the
// compiler not to take mutable access to this global variable whilst this
// reference exists!
let bus_ref = unsafe { USB_BUS.as_ref().unwrap() };
// Set up the USB Communications Class Device driver
let serial = SerialPort::new(bus_ref);
unsafe {
USB_SERIAL = Some(serial);
}
// Create a USB device with a fake VID and PID
let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27dd))
.strings(&[StringDescriptors::default()
.manufacturer("Fake company")
.product("Serial port")
.serial_number("TEST")])
.unwrap()
.device_class(2) // from: https://www.usb.org/defined-class-codes
.build();
unsafe {
// Note (safety): This is safe as interrupts haven't been started yet
USB_DEVICE = Some(usb_dev);
}
// Enable the USB interrupt
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ);
};
// No more USB code after this point in main! We can do anything we want in
// here since USB is handled in the interrupt - let's blink an LED!
// The delay object lets us wait for specified amounts of time (in
// milliseconds)
let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
// The single-cycle I/O block controls our GPIO pins
let sio = hal::Sio::new(pac.SIO);
// Set the pins up according to their function on this particular board
let pins = rp_pico::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
// Set the LED to be an output
let mut led_pin = pins.led.into_push_pull_output();
// Blink the LED at 1 Hz
loop {
led_pin.set_high().unwrap();
delay.delay_ms(500);
led_pin.set_low().unwrap();
delay.delay_ms(500);
}
}
/// This function is called whenever the USB Hardware generates an Interrupt
/// Request.
///
/// We do all our USB work under interrupt, so the main thread can continue on
/// knowing nothing about USB.
#[allow(non_snake_case)]
#[interrupt]
unsafe fn USBCTRL_IRQ() {
use core::sync::atomic::{AtomicBool, Ordering};
/// Note whether we've already printed the "hello" message.
static SAID_HELLO: AtomicBool = AtomicBool::new(false);
// Grab the global objects. This is OK as we only access them under interrupt.
let usb_dev = USB_DEVICE.as_mut().unwrap();
let serial = USB_SERIAL.as_mut().unwrap();
// Say hello exactly once on start-up
if !SAID_HELLO.load(Ordering::Relaxed) {
SAID_HELLO.store(true, Ordering::Relaxed);
let _ = serial.write(b"Hello, World!\r\n");
}
// Poll the USB driver with all of our supported USB Classes
if usb_dev.poll(&mut [serial]) {
let mut buf = [0u8; 64];
match serial.read(&mut buf) {
Err(_e) => {
// Do nothing
}
Ok(0) => {
// Do nothing
}
Ok(count) => {
// Convert to upper case
buf.iter_mut().take(count).for_each(|b| {
b.make_ascii_uppercase();
});
// Send back to the host
let mut wr_ptr = &buf[..count];
while !wr_ptr.is_empty() {
let _ = serial.write(wr_ptr).map(|len| {
wr_ptr = &wr_ptr[len..];
});
}
}
}
}
}
// End of file