Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB support for Leonardo #572

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0a06561
First attempt of making some generic code
supersimple33 Jul 20, 2024
8cb5745
Just muting some non-issues
supersimple33 Jul 24, 2024
13f0b06
Switching to using Macro 2.0
supersimple33 Jul 24, 2024
e7c3820
Moving into atmega-hal
supersimple33 Jul 24, 2024
8d0f94e
Switch to using pac for writing less code
supersimple33 Jul 25, 2024
dfc1521
First impl for UsbBus
supersimple33 Jul 25, 2024
16ca589
Moving SuspendNotifier outside due to orphan rule
supersimple33 Jul 25, 2024
e4608f3
Removing no longer used parts of macro
supersimple33 Jul 25, 2024
e81e875
Some macro and code changes for 8u2 device
supersimple33 Jul 29, 2024
52fb66b
Switching to tabs
supersimple33 Jul 29, 2024
a87be91
Passing AvrGenericUsbBus name for viz purposes
supersimple33 Jul 29, 2024
fc15a3f
Exposing in arduino hal
supersimple33 Jul 29, 2024
652ee6a
Escaping hygiene manually
supersimple33 Jul 30, 2024
3d3ad86
Adding a new macro for making a UsbBus
supersimple33 Jul 30, 2024
ba329eb
Adding a decl-macro for new usb devices
supersimple33 Jul 30, 2024
71cc082
Adding the original hello world example
supersimple33 Jul 30, 2024
8f9d219
Making necessary functions public
supersimple33 Aug 2, 2024
2f1509c
A better error handler
supersimple33 Aug 3, 2024
b504d11
Even better panic handling
supersimple33 Aug 3, 2024
4ca1972
Merge branch 'Rahix:main' into main
supersimple33 Aug 3, 2024
08bfd24
Small nit changes
supersimple33 Aug 3, 2024
8a47b1f
cleaner error handles
supersimple33 Aug 3, 2024
0be252f
Merge branch 'main' into main
supersimple33 Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Switching to using Macro 2.0
supersimple33 committed Jul 24, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 13f0b069b7f9faf5701bb7f60e54b7d302d6a583
1 change: 1 addition & 0 deletions avr-hal-generic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![no_std]
#![cfg_attr(avr_hal_asm_macro, feature(asm_experimental_arch))]
#![cfg_attr(not(avr_hal_asm_macro), feature(llvm_asm))]
#![feature(decl_macro)]

pub use embedded_hal as hal;
pub use embedded_hal_v0 as hal_v0;
269 changes: 161 additions & 108 deletions avr-hal-generic/src/usb.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use core::cell::Cell;

use usb_device::{bus::UsbBus, UsbError};
use usb_device::{bus::{UsbBus, PollResult}, UsbDirection, UsbError, class_prelude::UsbBusAllocator, endpoint::{EndpointAddress, EndpointType}, Result as UsbResult};
use avr_device::interrupt::{CriticalSection, Mutex as AvrDMutex};

const EP_SIZE_8: u8 = 0b000;
@@ -12,7 +12,7 @@ const EP_SIZE_256: u8 = 0b101;
const EP_SIZE_512: u8 = 0b110;

#[derive(Default)]
struct EndpointTableEntry {
pub struct EndpointTableEntry { // REVIEW: what should the scoping be here?
is_allocated: bool,
eptype_bits: u8,
epdir_bit: bool,
@@ -34,118 +34,169 @@ impl EndpointTableEntry {
}
}

#[macro_export]
macro_rules! create_usb_bus {
(
$USB_DEVICE:ty,
$MAX_ENDPOINTS:literal,
) => {
pub struct AvrUsbBus<S: SuspendNotifier> {
usb: AvrDMutex<$USB_DEVICE>,
suspend_notifier: AvrDMutex<S>,
pending_ins: AvrDMutex<Cell<u8>>,
endpoints: [EndpointTableEntry; $MAX_ENDPOINTS],
dpram_usage: u16, // TODO: This need to be extracted
}

impl UsbBus<()> {
/// Create a new UsbBus without power-saving functionality.
///
/// If you would like to disable the PLL when the USB peripheral is
/// suspended, then construct the bus with [`UsbBus::with_suspend_notifier`].
pub fn new(usb: $USB_DEVICE) -> UsbBusAllocator<Self> {
Self::with_suspend_notifier(usb, ())
}
// Using Macro 2.0 here while not stable yet makes this code a lot more readable and easier to write
pub macro create_usb_bus (
$USB_DEVICE:ty,
$MAX_ENDPOINTS:literal,
) {
pub struct AvrUsbBus<S: SuspendNotifier> {
usb: AvrDMutex<$USB_DEVICE>,
suspend_notifier: AvrDMutex<S>,
pending_ins: AvrDMutex<Cell<u8>>,
endpoints: [EndpointTableEntry; $MAX_ENDPOINTS],
dpram_usage: u16, // TODO: This need to be extracted
}

impl AvrUsbBus<()> {
/// Create a new UsbBus without power-saving functionality.
///
/// If you would like to disable the PLL when the USB peripheral is
/// suspended, then construct the bus with [`UsbBus::with_suspend_notifier`].
pub fn new(usb: $USB_DEVICE) -> UsbBusAllocator<Self> {
Self::with_suspend_notifier(usb, ())
}
}

impl<S: SuspendNotifier> AvrUsbBus<S> {
/// Create a UsbBus with a suspend and resume handler.
///
/// If you want the PLL to be automatically disabled when the USB peripheral
/// is suspended, then you can pass the PLL resource here; for example:
///
/// ```
/// use avr_device::atmega32u4::Peripherals;
/// use atmega_usbd::UsbBus;
///
/// let dp = Peripherals.take().unwrap();
/// // ... (other initialization stuff)
/// let bus = UsbBus::with_suspend_notifier(dp.USB_DEVICE, dp.PLL);
/// ```
///
/// **Note: If you are using the PLL output for other peripherals like the
/// high-speed timer, then disabling the PLL may affect the behavior of
/// those peripherals.** In such cases, you can either use [`UsbBus::new`]
/// to leave the PLL running, or implement [`SuspendNotifier`] yourself,
/// with some custom logic to gracefully shut down the PLL in cooperation
/// with your other peripherals.
pub fn with_suspend_notifier(usb: $USB_DEVICE, suspend_notifier: S) -> UsbBusAllocator<Self> {
UsbBusAllocator::new(Self {
usb: AvrDMutex::new(usb),
suspend_notifier: AvrDMutex::new(suspend_notifier),
pending_ins: AvrDMutex::new(Cell::new(0)),
endpoints: Default::default(),
dpram_usage: 0,
})
}

impl<S: SuspendNotifier> UsbBus<S> {
/// Create a UsbBus with a suspend and resume handler.
///
/// If you want the PLL to be automatically disabled when the USB peripheral
/// is suspended, then you can pass the PLL resource here; for example:
///
/// ```
/// use avr_device::atmega32u4::Peripherals;
/// use atmega_usbd::UsbBus;
///
/// let dp = Peripherals.take().unwrap();
/// // ... (other initialization stuff)
/// let bus = UsbBus::with_suspend_notifier(dp.USB_DEVICE, dp.PLL);
/// ```
///
/// **Note: If you are using the PLL output for other peripherals like the
/// high-speed timer, then disabling the PLL may affect the behavior of
/// those peripherals.** In such cases, you can either use [`UsbBus::new`]
/// to leave the PLL running, or implement [`SuspendNotifier`] yourself,
/// with some custom logic to gracefully shut down the PLL in cooperation
/// with your other peripherals.
pub fn with_suspend_notifier(usb: $USB_DEVICE, suspend_notifier: S) -> UsbBusAllocator<Self> {
UsbBusAllocator::new(Self {
usb: AvrDMutex::new(usb),
suspend_notifier: AvrDMutex::new(suspend_notifier),
pending_ins: AvrDMutex::new(Cell::new(0)),
endpoints: Default::default(),
dpram_usage: 0,
})
}

fn active_endpoints(&self) -> impl Iterator<Item = (usize, &EndpointTableEntry)> {
self.endpoints
.iter()
.enumerate() // why enumerate then immediately drop?
.filter(|&(_, ep)| ep.is_allocated)
}
fn active_endpoints(&self) -> impl Iterator<Item = (usize, &EndpointTableEntry)> {
self.endpoints
.iter()
.enumerate() // why enumerate then immediately drop?
.filter(|&(_, ep)| ep.is_allocated)
}

fn set_current_endpoint(&self, cs: CriticalSection, index: usize) -> Result<(), UsbError> {
if index >= MAX_ENDPOINTS {
return Err(UsbError::InvalidEndpoint);
}
let usb = self.usb.borrow(cs);
// TODO: the rest of this needs to be abstracted
if usb.usbcon.read().clk().bit_is_set() {
return Err(UsbError::InvalidState);
}
usb.uenum.write(|w| w.bits(index as u8));
if usb.uenum.read().bits() & 0b111 != (index as u8) {
return Err(UsbError::InvalidState);
}
Ok(())
fn set_current_endpoint(&self, cs: CriticalSection, index: usize) -> Result<(), UsbError> {
if index >= $MAX_ENDPOINTS {
return Err(UsbError::InvalidEndpoint);
}

fn endpoint_byte_count(&self, cs: CriticalSection) -> u16 {
let usb = self.usb.borrow(cs);
// FIXME: Potential for desync here? LUFA doesn't seem to care.
((usb.uebchx.read().bits() as u16) << 8) | (usb.uebclx.read().bits() as u16)
let usb = self.usb.borrow(cs);
// TODO: the rest of this needs to be abstracted
if usb.usbcon.read().frzclk().bit_is_set() {
return Err(UsbError::InvalidState);
}
usb.uenum.write(|w| w.bits(index as u8));
if usb.uenum.read().bits() & 0b111 != (index as u8) {
return Err(UsbError::InvalidState);
}
Ok(())
}

fn configure_endpoint(&self, cs: CriticalSection, index: usize) -> Result<(), UsbError> {
let usb = self.usb.borrow(cs);
self.set_current_endpoint(cs, index)?;
let endpoint = &self.endpoints[index];

usb.ueconx.modify(|_, w| w.epen().set_bit());
usb.uecfg1x.modify(|_, w| w.alloc().clear_bit());

usb.uecfg0x.write(|w| {
w.epdir()
.bit(endpoint.epdir_bit)
.eptype()
.bits(endpoint.eptype_bits)
});
usb.uecfg1x
.write(|w| w.epbk().bits(0).epsize().bits(endpoint.epsize_bits));
usb.uecfg1x.modify(|_, w| w.alloc().set_bit());

assert!(
usb.uesta0x.read().cfgok().bit_is_set(),
"could not configure endpoint {}",
index
);
fn endpoint_byte_count(&self, cs: CriticalSection) -> u16 {
let usb = self.usb.borrow(cs);
// FIXME: Potential for desync here? LUFA doesn't seem to care.
((usb.uebchx.read().bits() as u16) << 8) | (usb.uebclx.read().bits() as u16)
}

fn configure_endpoint(&self, cs: CriticalSection, index: usize) -> Result<(), UsbError> {
let usb = self.usb.borrow(cs);
self.set_current_endpoint(cs, index)?;
let endpoint = &self.endpoints[index];

usb.ueconx.modify(|_, w| w.epen().set_bit());
usb.uecfg1x.modify(|_, w| w.alloc().clear_bit());

usb.uecfg0x.write(|w| {
w.epdir()
.bit(endpoint.epdir_bit)
.eptype()
.bits(endpoint.eptype_bits)
});
usb.uecfg1x
.write(|w| w.epbk().bits(0).epsize().bits(endpoint.epsize_bits));
usb.uecfg1x.modify(|_, w| w.alloc().set_bit());

assert!(
usb.uesta0x.read().cfgok().bit_is_set(),
"could not configure endpoint {}",
index
);

usb.ueienx
.modify(|_, w| w.rxoute().set_bit().rxstpe().set_bit());
Ok(())
}
}

impl<S: SuspendNotifier> UsbBus for AvrUsbBus<S> {
fn alloc_ep(
&mut self,
ep_dir: UsbDirection,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
_interval: u8,
) -> Result<EndpointAddress, UsbError> {
unimplemented!()
}

fn enable(&mut self) {
unimplemented!()
}

fn reset(&self) {
unimplemented!()
}

fn set_device_address(&self, addr: u8) {
unimplemented!()
}

fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult<usize> {
unimplemented!()
}

fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult<usize> {
unimplemented!()
}

fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) {
unimplemented!()
}

usb.ueienx
.modify(|_, w| w.rxoute().set_bit().rxstpe().set_bit());
Ok(())
}
fn is_stalled(&self, ep_addr: EndpointAddress) -> bool {
unimplemented!()
}

fn suspend(&self) {
unimplemented!()
}

fn resume(&self) {
unimplemented!()
}

fn poll(&self) -> PollResult {
unimplemented!()
}
}
}
@@ -164,4 +215,6 @@ pub trait SuspendNotifier: Send + Sized + 'static {
///
/// This function should block until PLL lock has been established.
fn resume(&self) {}
}
}

impl SuspendNotifier for () {}