Skip to content

Commit 183d460

Browse files
committed
Add USB support to arduino-micro
Fixes #40. The purpose of this PR is to add USB support for the `atmega32u4`. This is done using rust-embedded-community/usb-device GitHub repo, since that appears to be the most popular crate this sort of thing. When using `usb-device`, there are three main components to be aware of: * For `avr-hal`, the main thing we care about is `UsbBus`. This trait is a hardware abstraction layer for the actual USB hardware. We implement this trait in `mcu/atmega-hal/src/usb.rs`. * The `UsbClass` trait is used to implement support for each different type of USB device (e.g. speaker, keyboard, or serial port). These implementations are portable, so we can just use existing implementations from other crates. For `micro-usb-serial.rs`, we create a serial port using `usbd-serial`'s implementation of `UsbClass`. `usbd-serial` was chosen simply because it is the only serial class implementation that is mentioned in `usb-device`'s readme file. * `UsbDevice` is used by our users (and our example code) to actually configure the USB device. Basically, to create `UsbDevice` the user just needs to combine our `UsbBus` implementation with the list of `UsbClass`es that they want to use.
1 parent ff4909c commit 183d460

File tree

7 files changed

+188
-2
lines changed

7 files changed

+188
-2
lines changed

arduino-hal/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ categories = ["no-std", "embedded", "hardware-support"]
1414
default = ["rt"]
1515
rt = ["avr-device/rt"]
1616

17+
usb-support = ["usb-device"]
1718
board-selected = []
1819
mcu-atmega = []
1920
mcu-attiny = []
@@ -22,7 +23,7 @@ arduino-leonardo = ["mcu-atmega", "atmega-hal/atmega32u4", "board-selected"]
2223
arduino-mega2560 = ["mcu-atmega", "atmega-hal/atmega2560", "board-selected"]
2324
arduino-mega1280 = ["mcu-atmega", "atmega-hal/atmega1280", "board-selected"]
2425
arduino-nano = ["mcu-atmega", "atmega-hal/atmega328p", "atmega-hal/enable-extra-adc", "board-selected"]
25-
arduino-micro = ["mcu-atmega", "atmega-hal/atmega32u4", "board-selected"]
26+
arduino-micro = ["mcu-atmega", "atmega-hal/atmega32u4", "atmega-hal/usb-support", "usb-support", "board-selected"]
2627
arduino-uno = ["mcu-atmega", "atmega-hal/atmega328p", "board-selected"]
2728
trinket-pro = ["mcu-atmega", "atmega-hal/atmega328p", "board-selected"]
2829
sparkfun-promicro = ["mcu-atmega", "atmega-hal/atmega32u4", "board-selected"]
@@ -38,6 +39,7 @@ docsrs = ["arduino-uno"]
3839
cfg-if = "1"
3940
embedded-hal = "1.0"
4041
ufmt = "0.2.0"
42+
usb-device = { version = "0.3.2", optional = true }
4143

4244
[dependencies.avr-hal-generic]
4345
path = "../avr-hal-generic/"

arduino-hal/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ pub use attiny_hal::pac;
114114
#[cfg(feature = "board-selected")]
115115
pub use hal::Peripherals;
116116

117+
#[cfg(feature = "usb-support")]
118+
pub use atmega_hal::UsbdBus;
119+
117120
#[cfg(feature = "board-selected")]
118121
pub mod clock;
119122
#[cfg(feature = "board-selected")]
@@ -253,6 +256,21 @@ macro_rules! default_serial {
253256
};
254257
}
255258

259+
/// TODO add documentation
260+
///
261+
/// # Example
262+
/// ```no_run
263+
/// let dp = arduino_hal::Peripherals::take().unwrap();
264+
/// let usb_bus = arduino_hal::default_usb_bus!(ds);
265+
/// ```
266+
#[cfg(feature = "usb-support")]
267+
#[macro_export]
268+
macro_rules! default_usb_bus {
269+
($p:expr) => {
270+
$crate::UsbdBus::new($p.USB_DEVICE, $p.PLL)
271+
};
272+
}
273+
256274
/// Convenience macro to instantiate the [`Usart`] driver for this board.
257275
///
258276
/// # Example

examples/arduino-micro/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ publish = false
88
panic-halt = "1.0.0"
99
ufmt = "0.2.0"
1010
nb = "1.1.0"
11+
usb-device = "0.3.2"
12+
usbd-serial = "0.2.2"
1113

1214
[dependencies.arduino-hal]
1315
path = "../../arduino-hal/"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![no_std]
2+
#![no_main]
3+
use arduino_hal::Peripherals;
4+
use panic_halt as _;
5+
use usb_device::bus::UsbBusAllocator;
6+
use usb_device::device::StringDescriptors;
7+
use usb_device::device::UsbDeviceBuilder;
8+
use usb_device::device::UsbVidPid;
9+
use usb_device::LangID;
10+
use usbd_serial::SerialPort;
11+
12+
#[arduino_hal::entry]
13+
fn main() -> ! {
14+
let dp: Peripherals = arduino_hal::Peripherals::take().unwrap();
15+
16+
let usb_bus = arduino_hal::default_usb_bus!(dp);
17+
let usb_bus_allocator = UsbBusAllocator::new(usb_bus);
18+
let mut serial = SerialPort::new(&usb_bus_allocator);
19+
20+
let string_descriptors = StringDescriptors::new(LangID::EN_US)
21+
.manufacturer("test manufacturer")
22+
.product("test product")
23+
.serial_number("test serial number");
24+
25+
let rand_ids = UsbVidPid(0x1ea7, 0x4a09);
26+
27+
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus_allocator, rand_ids)
28+
.strings(&[string_descriptors])
29+
.unwrap()
30+
.max_packet_size_0(64)
31+
.unwrap()
32+
.device_class(usbd_serial::USB_CLASS_CDC)
33+
.build();
34+
35+
loop {
36+
// Wait until we have data
37+
if !usb_dev.poll(&mut [&mut serial]) {
38+
continue;
39+
}
40+
41+
// Read the data into this buffer
42+
let mut read_buf = [0u8; 10];
43+
let Ok(read_count) = serial.read(&mut read_buf) else {
44+
continue;
45+
};
46+
if read_count == 0 {
47+
continue;
48+
}
49+
50+
// Ideally we want to do something like this:
51+
//
52+
// ```
53+
// let mut write_buf = [0u8; 20];
54+
// let write_count = ufmt::uwriteln!(&mut write_buf, "Got: {}", &write_buf);
55+
// ```
56+
//
57+
// TODO: Figure out how to get the above code to compile. It seems like
58+
// I might need to manually implement the uDebug trait? That doesn't seem
59+
// right... In the meantime, simply return a string of question marks.
60+
let write_buf = [b'?'; 20];
61+
let write_count = read_count;
62+
63+
// TODO: is this `.expect()` safe?
64+
let len = serial
65+
.write(&write_buf[0..write_count])
66+
.expect("The host should be reading data faster than the arduino can write it");
67+
assert_eq!(len, write_count);
68+
}
69+
}

mcu/atmega-hal/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ categories = ["no-std", "embedded", "hardware-support"]
1414
rt = ["avr-device/rt"]
1515
device-selected = []
1616
enable-extra-adc = []
17+
usb-support = ["usb-device"]
1718
atmega48p = ["avr-device/atmega48p", "device-selected"]
1819
atmega16 = ["avr-device/atmega16", "device-selected"]
1920
atmega164pa = ["avr-device/atmega164pa", "device-selected"]
2021
atmega168 = ["avr-device/atmega168", "device-selected"]
2122
atmega328p = ["avr-device/atmega328p", "device-selected"]
2223
atmega328pb = ["avr-device/atmega328pb", "device-selected"]
2324
atmega32a = ["avr-device/atmega32a", "device-selected"]
24-
atmega32u4 = ["avr-device/atmega32u4", "device-selected"]
25+
atmega32u4 = ["avr-device/atmega32u4", "device-selected", "usb-support"]
2526
atmega2560 = ["avr-device/atmega2560", "device-selected"]
2627
atmega128a = ["avr-device/atmega128a", "device-selected"]
2728
atmega1280 = ["avr-device/atmega1280", "device-selected"]
@@ -37,6 +38,7 @@ docsrs = ["atmega328p"]
3738

3839
[dependencies]
3940
avr-hal-generic = { path = "../../avr-hal-generic/" }
41+
usb-device = { version = "0.3.2", optional = true }
4042

4143
[dependencies.avr-device]
4244
version = "0.8"

mcu/atmega-hal/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ pub use avr_hal_generic::clock;
119119
pub use avr_hal_generic::delay;
120120
pub use avr_hal_generic::prelude;
121121

122+
#[cfg(feature = "usb-support")]
123+
mod usb;
124+
#[cfg(feature = "usb-support")]
125+
pub use usb::UsbdBus;
126+
122127
#[cfg(feature = "device-selected")]
123128
pub mod adc;
124129
#[cfg(feature = "device-selected")]

mcu/atmega-hal/src/usb.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use avr_device::atmega32u4::PLL;
2+
use avr_device::atmega32u4::USB_DEVICE;
3+
use usb_device::bus::PollResult;
4+
use usb_device::bus::UsbBus;
5+
use usb_device::endpoint::EndpointAddress;
6+
use usb_device::endpoint::EndpointType;
7+
use usb_device::UsbDirection;
8+
use usb_device::UsbError;
9+
10+
// TODO: I'm not too familiar with naming conventions in avr-hal (or Rust, for
11+
// that matter). Is `UsbdBus` acceptable?
12+
//
13+
// I decided to use the same name that the `musb` crate uses for their
14+
// implementation; I recall seeing somewhere that "usbd" was an abbreviation for
15+
// the "usb-device" crate.
16+
pub struct UsbdBus {}
17+
18+
impl UsbdBus {
19+
// TODO: I'm not sure that the arguments to the `new` function are
20+
// correct; there's a chance that they'll need to change during
21+
// implementation.
22+
//
23+
// Considering this example code:
24+
// https://github.com/agausmann/atmega-usbd/blob/master/examples/arduino_keyboard.rs#L54-L78
25+
// https://github.com/agausmann/atmega-usbd/blob/master/src/lib.rs#L69-L77
26+
//
27+
// * USB_DEVICE is clearly required.
28+
//
29+
// * The PLL will definitely need to be modified, and I'm inclined to
30+
// think that we should just take ownership and do it here.
31+
//
32+
// At first glance, it looks like the PLL can simultanously drive both the
33+
// USB controller and high speed timer(s). If so, there *might* be a
34+
// usecase where the user initializes PLL, then passes a shared reference
35+
// into this constructor. I don't think that's worth worring about though;
36+
// we can always add a new constructor later, and I don't want every
37+
// single user to have duplicate code for initializing PLL.
38+
//
39+
// * I don't *think* there's anything else we need?
40+
pub fn new(_usb: USB_DEVICE, _pll: PLL) -> Self {
41+
todo!();
42+
}
43+
}
44+
45+
// TODO: implement this trait using code from
46+
// https://github.com/agausmann/atmega-usbd/blob/master/src/lib.rs
47+
impl UsbBus for UsbdBus {
48+
fn alloc_ep(
49+
&mut self,
50+
_: UsbDirection,
51+
_: Option<EndpointAddress>,
52+
_: EndpointType,
53+
_: u16,
54+
_: u8,
55+
) -> Result<EndpointAddress, UsbError> {
56+
todo!()
57+
}
58+
fn enable(&mut self) {
59+
todo!()
60+
}
61+
fn reset(&self) {
62+
todo!()
63+
}
64+
fn set_device_address(&self, _: u8) {
65+
todo!()
66+
}
67+
fn write(&self, _: EndpointAddress, _: &[u8]) -> Result<usize, UsbError> {
68+
todo!()
69+
}
70+
fn read(&self, _: EndpointAddress, _: &mut [u8]) -> Result<usize, UsbError> {
71+
todo!()
72+
}
73+
fn set_stalled(&self, _: EndpointAddress, _: bool) {
74+
todo!()
75+
}
76+
fn is_stalled(&self, _: EndpointAddress) -> bool {
77+
todo!()
78+
}
79+
fn suspend(&self) {
80+
todo!()
81+
}
82+
fn resume(&self) {
83+
todo!()
84+
}
85+
fn poll(&self) -> PollResult {
86+
todo!()
87+
}
88+
}

0 commit comments

Comments
 (0)