feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"701ca024d73962baf82bb92a24dfb7f539fca5537616cff15451aca22d8191cc",".github/workflows/ci.yml":"dadcf27ab2d2180eda0028066e7ea7a4bdae3bbbf6798c85233a0b9e650e8867",".github/workflows/rustfmt.yml":"c7a7b46c60ea10c8af4293e8cbf13a15c58bf1f69e8cc9cc998752e3a5631903","CHANGELOG.md":"d0bc9e2bb6263e969e77f87ba10e1f209487d4fbbf0a1a89e712ab0f8b3eaf9e","Cargo.toml":"6b093cbb921cfd1703e6bc426c3b175dea863cd8816afe2dc8a8e1355d49ef06","Cargo.toml.orig":"16949174f37f13f2a9dd6f279575ccc17eb3e06aad288b0dfba0851d0158618a","LICENSE":"1d308accd0b1a61e1108ea36020dcda3db9dd1f5cfdb17f3a124bac54f5a6b07","README.md":"a9ceb97827cf4d9f257d382e8b9983a65dd12afc48762bf39d4b453bf03b947b","build.rs":"ae0e916f3b7e02a4eb57533e8059e840573115a0dacfb1b6fbd664dbba873717","src/bus.rs":"4493801a804dcc706383b19aca9e59502588cbc1c65ef68c19ab996d6941caff","src/endpoint.rs":"f5abce9da9f6f957e99eea8d292c502ba17bf103feecf4c9cfeb5642bc4b3d7b","src/endpoint_memory.rs":"4fb8355d51ec54dda8091f17f43d8ffd2fcbc452c97e2a3a151aba6623dbd5f2","src/lib.rs":"9f9b330d40e38ac899e2bb6823a1e638bfe817c9e580bafbe39e08f516c71a21","src/ral/instances/mod.rs":"880db01502226a9e1a18d4ca5e463d827bb8d3af2be724e86750dc9b05b6b96f","src/ral/instances/otg_fs_device_f401_f405_f407_f411_f427_f429.rs":"e477be7312b6d17c8de96ae186ec5ef6a8cb7d05800b04f11dbb65d622189abf","src/ral/instances/otg_fs_global_f401_f405_f407_f411_f427_f429.rs":"60f44afc1d21bd75baaaab684aa49812f0a0c741d6c4b85e0989324bd66f267c","src/ral/instances/otg_fs_host.rs":"1c393fc2c78f1f88b719a28c1cefc9d53cfe84fcf1058051404c54d17915c2f2","src/ral/instances/otg_hs_device.rs":"22d3963a75b14c7697eea02de0c3e44014117be7ebf4a99a3990615b1de8c269","src/ral/instances/otg_hs_global.rs":"3d11b355ea322c7c6094b260d2604da72d17e6b791eaa89141d354a86b1c2198","src/ral/instances/otg_hs_host.rs":"4a246ca830d5aea337d7d163af3c886a233f59f22cf975ca8e65d753b0545576","src/ral/instances/otg_s_pwrclk.rs":"3bd943b97ccd6440c1d6d99507077aa7058cb9666ed3a983046bba616d6b8332","src/ral/mod.rs":"46226a9f3b949c7bb1ce230bf60037611e1a9841b6721850bdbf2ba96812fd91","src/ral/peripherals/mod.rs":"74bc65b43f83d394e359de8866174f85ac82179be0c7e188dafe9cea26ae6998","src/ral/peripherals/otg_fs_device_v1.rs":"67763c1326c46b9ae8c4c6087dd66a4802aa0cef805cd97fd8bb8121e04f39a8","src/ral/peripherals/otg_fs_global_v1.rs":"271e2f3d8a95db86aedc0fc5a0365341681eda8e97d74deab32e3fd530396c60","src/ral/peripherals/otg_fs_host.rs":"c35266e6b5884ddeb882120704d8b3adbe5fcb25e0edc420b8cadbbd90c0ff24","src/ral/peripherals/otg_fs_pwrclk.rs":"0bd4f85bd7701216462090900238f3f7687fb049061c18955636334a5d90bbf7","src/ral/peripherals/otg_hs_device.rs":"d7028b42c7d90aa5293d05ec895d49c92557631612dbd8e6ef401a6a2ffbe19a","src/ral/peripherals/otg_hs_global.rs":"6494b730b822067bcbd89c0003e2676da446554fee17943de03412847068fb5b","src/ral/peripherals/otg_hs_host.rs":"fe2d36cd4fec6446a4690119e01af51a1061f2000692c8ade3bb3b73aed1d65e","src/ral/stm32f429/mod.rs":"9be54fb00a9cc686dd0b32b0fdbc8cb2266750e17c2ab099d923a4b42aebf703","src/target.rs":"84552c3b87e97ac9e77e10991dcf0e87eed1299db4165fceca3a21661ad1fde5","src/transition.rs":"b82cba76de2059cabfa46a70e61c43b91a9ddb8b43b741ae061dcf72cb116a2b"},"package":"8938451cb19032f13365328ea66ab38c8d16deecdf322067442297110eb74468"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "467716c2f038c1ac9d340f3f8d2f62a3a4120fde"
},
"path_in_vcs": ""
}
+33
View File
@@ -0,0 +1,33 @@
on:
push:
branches: [ staging, trying, master ]
pull_request:
name: CI
jobs:
ci-linux:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental || false }}
strategy:
matrix:
rust: [stable, nightly]
include:
# Nightly is only for reference and allowed to fail
- rust: nightly
experimental: true
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Check code (fs)
run: cargo check --features fs
- name: Check code (hs)
run: cargo check --features hs
- name: Check code (hs xcvrdly)
run: cargo check --features "hs xcvrdly"
@@ -0,0 +1,17 @@
on:
push:
branches: [ staging, trying, master ]
pull_request:
name: Code formatting check
jobs:
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
+27
View File
@@ -0,0 +1,27 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
* Use `critical-section` crate for critical sections.
* Replaced bundled RAL macros with `ral-registers`
## [v0.4.0] - 2023-11-18
### Changed
* `usb-device` version bumped to v0.3.0
[Unreleased]: https://github.com/stm32-rs/synopsys-usb-otg/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/stm32-rs/synopsys-usb-otg/compare/v0.4.0...v0.3.2
[0.3.2]: https://github.com/stm32-rs/synopsys-usb-otg/compare/v0.3.2...v0.3.1
+61
View File
@@ -0,0 +1,61 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "esp-synopsys-usb-otg"
version = "0.4.2"
authors = [
"Vadim Kaushan <admin@disasm.info>",
"The ESP-RS Team",
]
build = "build.rs"
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "'usb-device' implementation for Synopsys USB OTG IP cores"
readme = "README.md"
keywords = [
"no-std",
"embedded",
"usb",
]
license = "MIT"
repository = "https://github.com/esp-rs-compat/synopsys-usb-otg"
[package.metadata.docs.rs]
features = ["fs"]
[lib]
name = "esp_synopsys_usb_otg"
path = "src/lib.rs"
[dependencies.critical-section]
version = "1.1.1"
[dependencies.embedded-hal]
version = "0.2.4"
[dependencies.ral-registers]
version = "0.1.3"
[dependencies.usb-device]
version = "0.3"
[dependencies.vcell]
version = "0.1.0"
[features]
esp32sx = []
fs = []
hs = []
xcvrdly = []
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-2020 Matti Virkkunen, Vadim Kaushan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+44
View File
@@ -0,0 +1,44 @@
[![crates.io](https://img.shields.io/crates/d/esp-synopsys-usb-otg.svg)](https://crates.io/crates/esp-synopsys-usb-otg)
[![crates.io](https://img.shields.io/crates/v/esp-synopsys-usb-otg.svg)](https://crates.io/crates/esp-synopsys-usb-otg)
# This is a fork of [synopsys-usb-otg](https://crates.io/crates/synopsys-usb-otg) with support for ESP32-Sx added - please use https://github.com/stm32-rs/synopsys-usb-otg when not targeting ESP32-Sx
# `synopsys-usb-otg`
> [usb-device](https://github.com/mvirkkunen/usb-device) implementation for Synopsys USB OTG IP cores.
This project is a successor to the [great work](https://github.com/mvirkkunen/stm32f103xx-usb)
started by [@mvirkkunen](https://github.com/mvirkkunen).
## Supported microcontrollers
* `STM32F429xx` (OTG_FS and OTG_HS in FS mode)
* `STM32F401xx`
* `STM32F446xx` (OTG_FS and OTG_HS in FS mode)
* `STM32F723xx` (OTG_FS and OTG_HS with internal HS PHY)
* `STM32H7xxxx` (OTG1_HS and OTG2_HS in FS mode, and OTG1_HS with external HS PHY)
* And others...
## Usage
This driver is intended for use through a device hal library.
Such hal library should implement `UsbPeripheral` for the corresponding USB peripheral object.
This trait declares all the peripheral properties that may vary from one device family to the other.
Additionally, hal should pass `fs` of `hs` feature to the `synopsys-usb-otg` library to
define a peripheral type:
* `fs` - for FullSpeed peripherals
* `hs` - for HighSpeed peripherals
Only one peripheral type can be selected at the moment.
### ULPI Transciever Delay
Some ULPI PHYs like the Microchip USB334x series require a delay between the ULPI register write that initiates
the HS Chirp and the subsequent transmit command, otherwise the HS Chirp does not get executed and the deivce
enumerates in FS mode. Some USB Link IP like those in the STM32H7 series support adding this delay to work with the
affected PHYs. Enable the `xcvrdly` feature to add this delay.
## Examples
See the [usb-otg-workspace](https://github.com/Disasm/usb-otg-workspace) repo for different device-specific examples.
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let profile = std::env::var("PROFILE").unwrap();
let target = std::env::var("TARGET").unwrap();
if profile == "debug" && !target.starts_with("x86_64") {
println!("cargo:warning=synopsys-usb-otg is being compiled in debug mode. This driver works reliably only in release mode!");
}
}
+851
View File
@@ -0,0 +1,851 @@
use crate::ral::{
modify_reg, otg_device, otg_global, otg_global_dieptxfx, otg_pwrclk, read_reg, write_reg,
};
use crate::transition::{EndpointConfig, EndpointDescriptor};
use core::marker::PhantomData;
use critical_section::{CriticalSection, Mutex};
use embedded_hal::blocking::delay::DelayMs;
use usb_device::bus::{PollResult, UsbBusAllocator};
use usb_device::endpoint::{EndpointAddress, EndpointType};
use usb_device::{Result, UsbDirection, UsbError};
use crate::endpoint::{EndpointIn, EndpointOut};
use crate::endpoint_memory::{EndpointBufferState, EndpointMemoryAllocator};
use crate::target::UsbRegisters;
use crate::{PhyType, UsbPeripheral};
/// USB peripheral driver for STM32 microcontrollers.
pub struct UsbBus<USB> {
peripheral: USB,
regs: Mutex<UsbRegisters>,
allocator: EndpointAllocator<USB>,
}
impl<USB: UsbPeripheral> UsbBus<USB> {
/// Constructs a new USB peripheral driver.
pub fn new(peripheral: USB, ep_memory: &'static mut [u32]) -> UsbBusAllocator<Self> {
let bus = UsbBus {
peripheral,
regs: Mutex::new(UsbRegisters::new::<USB>()),
allocator: EndpointAllocator::new(ep_memory),
};
UsbBusAllocator::new(bus)
}
pub fn free(self) -> USB {
self.peripheral
}
fn configure_all(&self, cs: CriticalSection<'_>) {
let regs = self.regs.borrow(cs);
// Rx FIFO
// This calculation doesn't correspond to one in a Reference Manual.
// In fact, the required number of words is higher than indicated in RM.
// The following numbers are pessimistic and were figured out empirically.
let rx_fifo_size = if USB::HIGH_SPEED {
self.allocator.memory_allocator.total_rx_buffer_size_words() + 30
} else {
// F429 requires 35+ words for the (EP0[8] + EP2[64]) setup
// F446 requires 39+ words for the same setup
self.allocator.memory_allocator.total_rx_buffer_size_words() + 30
};
write_reg!(otg_global, regs.global(), GRXFSIZ, rx_fifo_size as u32);
let mut fifo_top = rx_fifo_size;
// Tx FIFO #0
let fifo_size = self.allocator.memory_allocator.tx_fifo_size_words(0);
#[cfg(feature = "fs")]
write_reg!(
otg_global,
regs.global(),
DIEPTXF0,
TX0FD: fifo_size as u32,
TX0FSA: fifo_top as u32
);
#[cfg(feature = "hs")]
write_reg!(
otg_global,
regs.global(),
GNPTXFSIZ,
TX0FD: fifo_size as u32,
TX0FSA: fifo_top as u32
);
fifo_top += fifo_size;
// Tx FIFOs
for i in 1..USB::ENDPOINT_COUNT {
let fifo_size = self.allocator.memory_allocator.tx_fifo_size_words(i);
let dieptxfx = regs.dieptxfx(i);
write_reg!(
otg_global_dieptxfx,
dieptxfx,
DIEPTXFx,
INEPTXFD: fifo_size as u32,
INEPTXSA: fifo_top as u32
);
fifo_top += fifo_size;
}
assert!(fifo_top as usize <= USB::FIFO_DEPTH_WORDS);
// Flush Rx & Tx FIFOs
modify_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH: 1, TXFFLSH: 1, TXFNUM: 0x10);
while read_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH, TXFFLSH) != (0, 0) {}
for ep in &self.allocator.endpoints_in {
if let Some(ep) = ep {
// enabling EP TX interrupt
modify_reg!(otg_device, regs.device(), DAINTMSK, |v| v
| (0x0001 << ep.address().index()));
ep.configure(cs);
}
}
for ep in &self.allocator.endpoints_out {
if let Some(ep) = ep {
if ep.address().index() == 0 {
// enabling RX interrupt from EP0
modify_reg!(otg_device, regs.device(), DAINTMSK, |v| v | 0x00010000);
}
ep.configure(cs);
}
}
}
fn deconfigure_all(&self, cs: CriticalSection<'_>) {
let regs = self.regs.borrow(cs);
// disable interrupts
modify_reg!(otg_device, regs.device(), DAINTMSK, IEPM: 0, OEPM: 0);
for ep in &self.allocator.endpoints_in {
if let Some(ep) = ep {
ep.deconfigure(cs);
}
}
for ep in &self.allocator.endpoints_out {
if let Some(ep) = ep {
ep.deconfigure(cs);
}
}
}
pub fn force_reset(&self, delay: &mut impl DelayMs<u32>) -> Result<()> {
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
write_reg!(otg_device, regs.device(), DCTL, SDIS: 1); // Soft disconnect
delay.delay_ms(3);
write_reg!(otg_device, regs.device(), DCTL, SDIS: 0); // Soft connect
delay.delay_ms(3);
});
Ok(())
}
#[cfg(feature = "hs")]
/// Reads from a ULPI register in an external ULPI PHY.
///
/// Interrupts are disabled for the duration of the function call.
///
/// **Panics:** if `phy_type` is not `PhyType::ExternalHighSpeed`.
///
/// # Example
///
/// ```
/// # use synopsys_usb_otg::{UsbPeripheral, UsbBus};
/// fn read_usb_vid_pid<USB: UsbPeripheral>(bus: &UsbBus<USB>) -> (u16, u16) {
/// let mut vid: u16 = bus.ulpi_read(0x00) as u16;
/// vid |= (bus.ulpi_read(0x01) as u16) << 8;
/// let mut pid: u16 = bus.ulpi_read(0x02) as u16;
/// pid |= (bus.ulpi_read(0x03) as u16) << 8;
/// (vid, pid)
/// }
/// ```
pub fn ulpi_read(&self, addr: u8) -> core::result::Result<u8, UlpiError> {
if self.peripheral.phy_type() != PhyType::ExternalHighSpeed {
panic!("ulpi_read is only supported with external ULPI PHYs");
}
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
// Begin ULPI register read transaction
modify_reg!(otg_global, regs.global(), PHYCR,
NEW: 1,
RW: 0,
ADDR: addr as u32
);
// ULPI transactions should take less than 1us. 1000 iterations should be enough even for fast MCUs.
let mut timeout = 1000;
while timeout > 0 {
// Wait for transaction to complete
if read_reg!(otg_global, regs.global(), PHYCR, DONE) != 0 {
// Read transaction data
return Ok((read_reg!(otg_global, regs.global(), PHYCR, DATA) & 0xFF) as u8);
}
timeout -= 1;
}
Err(UlpiError::Timeout)
})
}
#[cfg(feature = "hs")]
/// Writes to a ULPI register in an external ULPI PHY.
///
/// Interrupts are disabled for the duration of the function call.
///
/// **Panics:** if `phy_type` is not `PhyType::ExternalHighSpeed`.
pub fn ulpi_write(&self, addr: u8, data: u8) -> core::result::Result<(), UlpiError> {
if self.peripheral.phy_type() != PhyType::ExternalHighSpeed {
panic!("ulpi_write is only supported with external ULPI PHYs");
}
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
// Begin ULPI register write transaction
modify_reg!(otg_global, regs.global(), PHYCR,
NEW: 1,
RW: 1,
ADDR: addr as u32,
DATA: data as u32
);
// ULPI transactions should take less than 1us. 1000 iterations should be enough even for fast MCUs.
let mut timeout = 1000;
while timeout > 0 {
// Wait for transaction to complete
if read_reg!(otg_global, regs.global(), PHYCR, DONE) != 0 {
return Ok(());
}
timeout -= 1;
}
Err(UlpiError::Timeout)
})
}
}
#[cfg(feature = "hs")]
#[derive(Debug)]
/// Errors that can occur while interfacing with a ULPI PHY.
pub enum UlpiError {
/// The action has timed out.
Timeout,
}
pub(crate) struct EndpointAllocator<USB> {
bitmap_in: u8,
bitmap_out: u8,
endpoints_in: [Option<EndpointIn>; 9],
endpoints_out: [Option<EndpointOut>; 9],
memory_allocator: EndpointMemoryAllocator<USB>,
_marker: PhantomData<USB>,
}
impl<USB: UsbPeripheral> EndpointAllocator<USB> {
fn new(memory: &'static mut [u32]) -> Self {
assert!(USB::ENDPOINT_COUNT <= 9);
Self {
bitmap_in: 0,
bitmap_out: 0,
// [None; 9] requires Copy
endpoints_in: [None, None, None, None, None, None, None, None, None],
endpoints_out: [None, None, None, None, None, None, None, None, None],
memory_allocator: EndpointMemoryAllocator::new(memory),
_marker: PhantomData,
}
}
fn alloc_number(bitmap: &mut u8, number: Option<u8>) -> Result<u8> {
if let Some(number) = number {
if number as usize >= USB::ENDPOINT_COUNT {
return Err(UsbError::InvalidEndpoint);
}
if *bitmap & (1 << number) == 0 {
*bitmap |= 1 << number;
Ok(number)
} else {
Err(UsbError::InvalidEndpoint)
}
} else {
// Skip EP0
for number in 1..USB::ENDPOINT_COUNT {
if *bitmap & (1 << number) == 0 {
*bitmap |= 1 << number;
return Ok(number as u8);
}
}
Err(UsbError::EndpointOverflow)
}
}
fn alloc(
bitmap: &mut u8,
config: &EndpointConfig,
direction: UsbDirection,
) -> Result<EndpointDescriptor> {
let number = Self::alloc_number(bitmap, config.number)?;
let address = EndpointAddress::from_parts(number as usize, direction);
Ok(EndpointDescriptor {
address,
ep_type: config.ep_type,
max_packet_size: config.max_packet_size,
interval: config.interval,
})
}
fn alloc_in(&mut self, config: &EndpointConfig) -> Result<EndpointIn> {
let descr = Self::alloc(&mut self.bitmap_in, config, UsbDirection::In)?;
self.memory_allocator
.allocate_tx_buffer(descr.address.index() as u8, descr.max_packet_size as usize)?;
let ep = EndpointIn::new::<USB>(descr);
Ok(ep)
}
fn alloc_out(&mut self, config: &EndpointConfig) -> Result<EndpointOut> {
let descr = Self::alloc(&mut self.bitmap_out, config, UsbDirection::Out)?;
let buffer = self
.memory_allocator
.allocate_rx_buffer(descr.max_packet_size as usize)?;
let ep = EndpointOut::new::<USB>(descr, buffer);
Ok(ep)
}
fn alloc_ep(
&mut self,
ep_dir: UsbDirection,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<EndpointAddress> {
let ep_type = unsafe { core::mem::transmute(ep_type) };
let number = ep_addr.map(|a| a.index() as u8);
let config = EndpointConfig {
ep_type,
max_packet_size,
interval,
number,
pair_of: None,
};
match ep_dir {
UsbDirection::Out => {
let ep = self.alloc_out(&config)?;
let address = ep.address();
self.endpoints_out[address.index()] = Some(ep);
Ok(address)
}
UsbDirection::In => {
let ep = self.alloc_in(&config)?;
let address = ep.address();
self.endpoints_in[address.index()] = Some(ep);
Ok(address)
}
}
}
}
impl<USB: UsbPeripheral> usb_device::bus::UsbBus for UsbBus<USB> {
fn alloc_ep(
&mut self,
ep_dir: UsbDirection,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<EndpointAddress> {
self.allocator
.alloc_ep(ep_dir, ep_addr, ep_type, max_packet_size, interval)
}
fn enable(&mut self) {
// Enable USB_OTG in RCC
USB::enable();
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
let core_id = read_reg!(otg_global, regs.global(), CID);
// Wait for AHB ready
while read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) == 0 {}
// Configure OTG as device
#[cfg(feature = "fs")]
modify_reg!(otg_global, regs.global(), GUSBCFG,
SRPCAP: 0, // SRP capability is not enabled
FDMOD: 1 // Force device mode
);
#[cfg(feature = "hs")]
modify_reg!(otg_global, regs.global(), GUSBCFG,
SRPCAP: 0, // SRP capability is not enabled
TOCAL: 0x1,
FDMOD: 1 // Force device mode
);
// Configure USB PHY
#[cfg(feature = "hs")]
match self.peripheral.phy_type() {
PhyType::InternalFullSpeed => {
// Select FS Embedded PHY
modify_reg!(otg_global, regs.global(), GUSBCFG, PHYSEL: 1);
}
PhyType::InternalHighSpeed => {
// Turn off PHY
modify_reg!(otg_global, regs.global(), GCCFG, PWRDWN: 0);
// Init The UTMI Interface
modify_reg!(otg_global, regs.global(), GUSBCFG,
TSDPS: 0,
ULPIFSLS: 0,
PHYSEL: 0 // ULPI or UTMI
);
// Select VBUS source
modify_reg!(otg_global, regs.global(), GUSBCFG,
ULPIEVBUSD: 0,
ULPIEVBUSI: 0
);
// Select UTMI Interace
//modify_reg!(otg_global, regs.global(), GUSBCFG, ULPISEL: 0);
modify_reg!(otg_global, regs.global(), GUSBCFG, |r| r & !(1 << 4));
// This is a secret bit from ST that is not mentioned anywhere except
// the driver code shipped with STM32CubeIDE.
//modify_reg!(otg_global, regs.global(), GCCFG, PHYHSEN: 1);
modify_reg!(otg_global, regs.global(), GCCFG, |r| r | (1 << 23));
self.peripheral.setup_internal_hs_phy();
}
PhyType::ExternalHighSpeed => {
// Turn off embedded PHY
modify_reg!(otg_global, regs.global(), GCCFG, PWRDWN: 0);
// Init The ULPI Interface
modify_reg!(otg_global, regs.global(), GUSBCFG,
TSDPS: 0,
ULPIFSLS: 0,
PHYSEL: 0 // ULPI or UTMI
);
// Select VBUS source
modify_reg!(otg_global, regs.global(), GUSBCFG,
ULPIEVBUSD: 0,
ULPIEVBUSI: 0
);
}
}
// Perform core soft-reset
while read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) == 0 {}
modify_reg!(otg_global, regs.global(), GRSTCTL, CSRST: 1);
while read_reg!(otg_global, regs.global(), GRSTCTL, CSRST) == 1 {}
if self.peripheral.phy_type() == PhyType::InternalFullSpeed {
// Activate the USB Transceiver
modify_reg!(otg_global, regs.global(), GCCFG, PWRDWN: 1);
}
// Configuring Vbus sense and SOF output
match core_id {
0x0000_1200 | 0x0000_1100 => {
// F429-like chips have the GCCFG.NOVBUSSENS bit
//modify_reg!(otg_global, regs.global, GCCFG, NOVBUSSENS: 1);
modify_reg!(otg_global, regs.global(), GCCFG, |r| r | (1 << 21));
modify_reg!(otg_global, regs.global(), GCCFG, VBUSASEN: 0, VBUSBSEN: 0, SOFOUTEN: 0);
}
0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => {
// F446-like chips have the GCCFG.VBDEN bit with the opposite meaning
//modify_reg!(otg_global, regs.global, GCCFG, VBDEN: 0);
modify_reg!(otg_global, regs.global(), GCCFG, |r| r & !(1 << 21));
// Force B-peripheral session
//modify_reg!(otg_global, regs.global, GOTGCTL, BVALOEN: 1, BVALOVAL: 1);
modify_reg!(otg_global, regs.global(), GOTGCTL, |r| r | (0b11 << 6));
}
_ => {}
}
// Enable PHY clock
write_reg!(otg_pwrclk, regs.pwrclk(), PCGCCTL, 0);
// Soft disconnect device
modify_reg!(otg_device, regs.device(), DCTL, SDIS: 1);
// Setup USB speed and frame interval
let speed = match (USB::HIGH_SPEED, self.peripheral.phy_type()) {
(false, _) => 0b11,
(true, PhyType::InternalFullSpeed) => 0b11,
(true, PhyType::InternalHighSpeed) => 0b00,
(true, PhyType::ExternalHighSpeed) => 0b00,
};
modify_reg!(otg_device, regs.device(), DCFG,
PFIVL: 0b00,
DSPD: speed
);
#[cfg(feature = "xcvrdly")]
modify_reg!(otg_device, regs.device(), DCFG, XCVRDLY: 1);
// unmask EP interrupts
write_reg!(otg_device, regs.device(), DIEPMSK, XFRCM: 1);
// unmask core interrupts
write_reg!(otg_global, regs.global(), GINTMSK,
USBRST: 1, ENUMDNEM: 1,
USBSUSPM: 1, WUIM: 1,
IEPINT: 1, RXFLVLM: 1,
IISOIXFRM: 1
);
// clear pending interrupts
write_reg!(otg_global, regs.global(), GINTSTS, 0xffffffff);
// unmask global interrupt
modify_reg!(otg_global, regs.global(), GAHBCFG, GINT: 1);
// connect(true)
modify_reg!(otg_device, regs.device(), DCTL, SDIS: 0);
});
}
fn reset(&self) {
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
self.configure_all(cs);
modify_reg!(otg_device, regs.device(), DCFG, DAD: 0);
});
}
fn set_device_address(&self, addr: u8) {
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
modify_reg!(otg_device, regs.device(), DCFG, DAD: addr as u32);
});
}
fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> Result<usize> {
if !ep_addr.is_in() || ep_addr.index() >= USB::ENDPOINT_COUNT {
return Err(UsbError::InvalidEndpoint);
}
if let Some(ep) = &self.allocator.endpoints_in[ep_addr.index()] {
ep.write(buf).map(|_| buf.len())
} else {
Err(UsbError::InvalidEndpoint)
}
}
fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> Result<usize> {
if !ep_addr.is_out() || ep_addr.index() >= USB::ENDPOINT_COUNT {
return Err(UsbError::InvalidEndpoint);
}
if let Some(ep) = &self.allocator.endpoints_out[ep_addr.index()] {
ep.read(buf)
} else {
Err(UsbError::InvalidEndpoint)
}
}
fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) {
if ep_addr.index() >= USB::ENDPOINT_COUNT {
return;
}
let regs = UsbRegisters::new::<USB>();
crate::endpoint::set_stalled(regs, ep_addr, stalled)
}
fn is_stalled(&self, ep_addr: EndpointAddress) -> bool {
if ep_addr.index() >= USB::ENDPOINT_COUNT {
return true;
}
let regs = UsbRegisters::new::<USB>();
crate::endpoint::is_stalled(regs, ep_addr)
}
fn suspend(&self) {
// Nothing to do here?
}
fn resume(&self) {
// Nothing to do here?
}
fn poll(&self) -> PollResult {
critical_section::with(|cs| {
let regs = self.regs.borrow(cs);
let core_id = read_reg!(otg_global, regs.global(), CID);
let (wakeup, suspend, enum_done, reset, iep, rxflvl, iisoixfr) = read_reg!(
otg_global,
regs.global(),
GINTSTS,
WKUPINT,
USBSUSP,
ENUMDNE,
USBRST,
IEPINT,
RXFLVL,
IISOIXFR
);
if reset != 0 {
write_reg!(otg_global, regs.global(), GINTSTS, USBRST: 1);
self.deconfigure_all(cs);
// Flush RX
modify_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH: 1);
while read_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH) == 1 {}
}
if enum_done != 0 {
write_reg!(otg_global, regs.global(), GINTSTS, ENUMDNE: 1);
let speed = read_reg!(otg_device, regs.device(), DSTS, ENUMSPD);
// Compute and update TRDT
let mut trdt = 0;
match speed {
0b00 => {
// High speed
// From RM0431 (F72xx), RM0090 (F429), RM0390 (F446)
if self.peripheral.ahb_frequency_hz() >= 30_000_000 {
trdt = 0x9;
} else {
panic!("AHB frequency is too low")
}
}
0b01 | 0b11 => {
// Full speed
// From RM0431 (F72xx), RM0090 (F429)
#[cfg(not(feature = "esp32sx"))]
let _trdt = match self.peripheral.ahb_frequency_hz() {
0..=14_199_999 => panic!("AHB frequency is too low"),
14_200_000..=14_999_999 => 0xF,
15_000_000..=15_999_999 => 0xE,
16_000_000..=17_199_999 => 0xD,
17_200_000..=18_499_999 => 0xC,
18_500_000..=19_999_999 => 0xB,
20_000_000..=21_799_999 => 0xA,
21_800_000..=23_999_999 => 0x9,
24_000_000..=27_499_999 => 0x8,
27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE
32_000_000..=u32::MAX => 0x6,
};
#[cfg(feature = "esp32sx")]
let _trdt = 5;
trdt = trdt;
}
_ => unimplemented!(),
}
modify_reg!(otg_global, regs.global(), GUSBCFG, TRDT: trdt);
PollResult::Reset
} else if wakeup != 0 {
// Clear the interrupt
write_reg!(otg_global, regs.global(), GINTSTS, WKUPINT: 1);
PollResult::Resume
} else if suspend != 0 {
write_reg!(otg_global, regs.global(), GINTSTS, USBSUSP: 1);
PollResult::Suspend
} else if iisoixfr != 0 {
use crate::ral::endpoint_in;
// Incomplete isochronous IN transfer; see
// RM0383 Rev 3 pp797 "Incomplete isochronous IN data transfers"
write_reg!(otg_global, regs.global(), GINTSTS, IISOIXFR: 1);
let in_endpoints = self
.allocator
.endpoints_in
.iter()
.flatten()
.map(|ep| ep.address().index());
let mut iep: u16 = 0;
for epnum in in_endpoints {
let ep_regs = regs.endpoint_in(epnum);
// Filter out non-isochronous endpoints
if read_reg!(endpoint_in, ep_regs, DIEPCTL, EPTYP) & 0x11 != 0x01 {
continue;
}
// RM0383 states that the NAK bit in DIEPINT is set when a
// zero length packet is transmitted due to unavailability
// of data in the Tx FIFO.
// However, this bit is not defined in the RAL and
// by checking several STM32 RMs, it's even unclear
// if it actually exists on OTG_FS peripherals.
// While testing with macOS, the bit was never set.
// Therefore, an alternative method is used to check if
// an endpoint is affected.
// TODO: investigate if this check works correctly and
// does not affect other isochronous IN endpoints by mistake.
if read_reg!(endpoint_in, ep_regs, DIEPCTL, NAKSTS) == 1 {
continue;
}
// Set NAK
modify_reg!(endpoint_in, ep_regs, DIEPCTL, SNAK: 1);
while read_reg!(endpoint_in, ep_regs, DIEPINT, INEPNE) == 0 {}
// Disable the endpoint
modify_reg!(endpoint_in, ep_regs, DIEPCTL, SNAK: 1, EPDIS: 1);
while read_reg!(endpoint_in, ep_regs, DIEPINT, EPDISD) == 0 {}
modify_reg!(endpoint_in, ep_regs, DIEPINT, EPDISD: 1);
// Flush the TX FIFO
modify_reg!(otg_global, regs.global(), GRSTCTL, TXFNUM: epnum as u32, TXFFLSH: 1);
while read_reg!(otg_global, regs.global(), GRSTCTL, TXFFLSH) == 1 {}
iep |= 1 << epnum;
}
PollResult::Data {
ep_out: 0,
ep_in_complete: iep,
ep_setup: 0,
}
} else {
let mut ep_out = 0;
let mut ep_in_complete = 0;
let mut ep_setup = 0;
use crate::ral::{endpoint_in, endpoint_out};
// RXFLVL & IEPINT flags are read-only, there is no need to clear them
if rxflvl != 0 {
let (epnum, data_size, status) =
read_reg!(otg_global, regs.global(), GRXSTSR, EPNUM, BCNT, PKTSTS);
match status {
0x02 => {
// OUT received
ep_out |= 1 << epnum;
}
0x06 => {
// SETUP received
// flushing TX if something stuck in control endpoint
let ep = regs.endpoint_in(epnum as usize);
if read_reg!(endpoint_in, ep, DIEPTSIZ, PKTCNT) != 0 {
modify_reg!(otg_global, regs.global(), GRSTCTL, TXFNUM: epnum, TXFFLSH: 1);
while read_reg!(otg_global, regs.global(), GRSTCTL, TXFFLSH) == 1 {}
}
ep_setup |= 1 << epnum;
}
0x03 | 0x04 => {
// OUT completed | SETUP completed
// Re-enable the endpoint, F429-like chips only
if core_id == 0x4f54_400a
|| core_id == 0x0000_1200
|| core_id == 0x0000_1100
{
let ep = regs.endpoint_out(epnum as usize);
modify_reg!(endpoint_out, ep, DOEPCTL, CNAK: 1, EPENA: 1);
}
read_reg!(otg_global, regs.global(), GRXSTSP); // pop GRXSTSP
}
_ => {
read_reg!(otg_global, regs.global(), GRXSTSP); // pop GRXSTSP
}
}
if status == 0x02 || status == 0x06 {
if let Some(ep) = &self.allocator.endpoints_out[epnum as usize] {
let mut buffer = ep.buffer.borrow_ref_mut(cs);
if buffer.state() == EndpointBufferState::Empty {
read_reg!(otg_global, regs.global(), GRXSTSP); // pop GRXSTSP
let is_setup = status == 0x06;
buffer
.fill_from_fifo(*regs, data_size as u16, is_setup)
.ok();
// Re-enable the endpoint, F446-like chips only
if core_id == 0x4f54_400a
|| core_id == 0x0000_2000
|| core_id == 0x0000_2100
|| core_id == 0x0000_2300
|| core_id == 0x0000_3000
|| core_id == 0x0000_3100
{
let ep = regs.endpoint_out(epnum as usize);
modify_reg!(endpoint_out, ep, DOEPCTL, CNAK: 1, EPENA: 1);
}
}
}
}
}
if iep != 0 {
for ep in &self.allocator.endpoints_in {
if let Some(ep) = ep {
let ep_regs = regs.endpoint_in(ep.address().index());
if read_reg!(endpoint_in, ep_regs, DIEPINT, XFRC) != 0 {
write_reg!(endpoint_in, ep_regs, DIEPINT, XFRC: 1);
ep_in_complete |= 1 << ep.address().index();
}
}
}
}
for ep in &self.allocator.endpoints_out {
if let Some(ep) = ep {
match ep.buffer_state() {
EndpointBufferState::DataOut => {
ep_out |= 1 << ep.address().index();
}
EndpointBufferState::DataSetup => {
ep_setup |= 1 << ep.address().index();
}
EndpointBufferState::Empty => {}
}
}
}
if (ep_in_complete | ep_out | ep_setup) != 0 {
PollResult::Data {
ep_out,
ep_in_complete,
ep_setup,
}
} else {
PollResult::None
}
}
})
}
const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = true;
}
+286
View File
@@ -0,0 +1,286 @@
use crate::endpoint_memory::{EndpointBuffer, EndpointBufferState};
use crate::ral::{
endpoint0_out, endpoint_in, endpoint_out, modify_reg, otg_device, read_reg, write_reg,
};
use crate::target::{fifo_write, UsbRegisters};
use crate::transition::EndpointDescriptor;
use crate::UsbPeripheral;
use core::cell::RefCell;
use core::ops::{Deref, DerefMut};
use critical_section::{CriticalSection, Mutex};
use usb_device::endpoint::{EndpointAddress, EndpointType};
use usb_device::{Result, UsbDirection, UsbError};
pub fn set_stalled(usb: UsbRegisters, address: EndpointAddress, stalled: bool) {
critical_section::with(|_| match address.direction() {
UsbDirection::Out => {
let ep = usb.endpoint_out(address.index() as usize);
modify_reg!(endpoint_out, ep, DOEPCTL, STALL: stalled as u32);
}
UsbDirection::In => {
let ep = usb.endpoint_in(address.index() as usize);
modify_reg!(endpoint_in, ep, DIEPCTL, STALL: stalled as u32);
}
})
}
pub fn is_stalled(usb: UsbRegisters, address: EndpointAddress) -> bool {
let stall = match address.direction() {
UsbDirection::Out => {
let ep = usb.endpoint_out(address.index());
read_reg!(endpoint_out, ep, DOEPCTL, STALL)
}
UsbDirection::In => {
let ep = usb.endpoint_in(address.index());
read_reg!(endpoint_in, ep, DIEPCTL, STALL)
}
};
stall != 0
}
/// Arbitrates access to the endpoint-specific registers and packet buffer memory.
pub struct Endpoint {
descriptor: EndpointDescriptor,
usb: UsbRegisters,
}
impl Endpoint {
pub fn new<USB: UsbPeripheral>(descriptor: EndpointDescriptor) -> Endpoint {
Endpoint {
descriptor,
usb: UsbRegisters::new::<USB>(),
}
}
pub fn address(&self) -> EndpointAddress {
self.descriptor.address
}
#[inline(always)]
fn index(&self) -> u8 {
self.descriptor.address.index() as u8
}
pub fn ep_type(&self) -> EndpointType {
self.descriptor.ep_type
}
}
pub struct EndpointIn {
common: Endpoint,
}
impl EndpointIn {
pub fn new<USB: UsbPeripheral>(descriptor: EndpointDescriptor) -> EndpointIn {
EndpointIn {
common: Endpoint::new::<USB>(descriptor),
}
}
pub fn configure(&self, _cs: CriticalSection<'_>) {
if self.index() == 0 {
let mpsiz = match self.descriptor.max_packet_size {
8 => 0b11,
16 => 0b10,
32 => 0b01,
64 => 0b00,
other => panic!("Unsupported EP0 size: {}", other),
};
let regs = self.usb.endpoint_in(self.index() as usize);
write_reg!(endpoint_in, regs, DIEPCTL, MPSIZ: mpsiz as u32, SNAK: 1);
write_reg!(endpoint_in, regs, DIEPTSIZ, PKTCNT: 0, XFRSIZ: self.descriptor.max_packet_size as u32);
} else {
let regs = self.usb.endpoint_in(self.index() as usize);
write_reg!(endpoint_in, regs, DIEPCTL,
SNAK: 1,
USBAEP: 1,
EPTYP: self.descriptor.ep_type.to_bm_attributes() as u32,
SD0PID_SEVNFRM: 1,
TXFNUM: self.index() as u32,
MPSIZ: self.descriptor.max_packet_size as u32
);
}
}
pub fn deconfigure(&self, _cs: CriticalSection<'_>) {
let regs = self.usb.endpoint_in(self.index() as usize);
// deactivating endpoint
modify_reg!(endpoint_in, regs, DIEPCTL, USBAEP: 0);
// TODO: flushing FIFO
// disabling endpoint
if read_reg!(endpoint_in, regs, DIEPCTL, EPENA) != 0 && self.index() != 0 {
modify_reg!(endpoint_in, regs, DIEPCTL, EPDIS: 1)
}
// clean EP interrupts
write_reg!(endpoint_in, regs, DIEPINT, 0xff);
// TODO: deconfiguring TX FIFO
}
pub fn write(&self, buf: &[u8]) -> Result<()> {
let ep = self.usb.endpoint_in(self.index() as usize);
let device = self.usb.device();
if self.index() != 0 && read_reg!(endpoint_in, ep, DIEPCTL, EPENA) != 0 {
return Err(UsbError::WouldBlock);
}
if buf.len() > self.descriptor.max_packet_size as usize {
return Err(UsbError::BufferOverflow);
}
if !buf.is_empty() {
// Check for FIFO free space
let size_words = (buf.len() + 3) / 4;
if size_words > read_reg!(endpoint_in, ep, DTXFSTS, INEPTFSAV) as usize {
return Err(UsbError::WouldBlock);
}
}
write_reg!(endpoint_in, ep, DIEPTSIZ, MCNT: 1, PKTCNT: 1, XFRSIZ: buf.len() as u32);
match self.descriptor.ep_type {
// Isochronous endpoints must set the correct even/odd frame bit to
// correspond with the next frame's number.
EndpointType::Isochronous { .. } => {
// Previous frame number is OTG_DSTS.FNSOF
let frame_number = read_reg!(otg_device, device, DSTS, FNSOF);
if frame_number & 0x1 == 1 {
// Previous frame number is odd, so upcoming frame is even
modify_reg!(endpoint_in, ep, DIEPCTL, SD0PID_SEVNFRM: 1);
} else {
// Previous frame number is even, so upcoming frame is odd
#[cfg(feature = "fs")]
modify_reg!(endpoint_in, ep, DIEPCTL, SODDFRM_SD1PID: 1);
#[cfg(feature = "hs")]
modify_reg!(endpoint_in, ep, DIEPCTL, SODDFRM: 1);
}
}
_ => {}
}
modify_reg!(endpoint_in, ep, DIEPCTL, CNAK: 1, EPENA: 1);
fifo_write(self.usb, self.index(), buf);
Ok(())
}
}
pub struct EndpointOut {
common: Endpoint,
pub(crate) buffer: Mutex<RefCell<EndpointBuffer>>,
}
impl EndpointOut {
pub fn new<USB: UsbPeripheral>(
descriptor: EndpointDescriptor,
buffer: EndpointBuffer,
) -> EndpointOut {
EndpointOut {
common: Endpoint::new::<USB>(descriptor),
buffer: Mutex::new(RefCell::new(buffer)),
}
}
pub fn configure(&self, _cs: CriticalSection<'_>) {
if self.index() == 0 {
let mpsiz = match self.descriptor.max_packet_size {
8 => 0b11,
16 => 0b10,
32 => 0b01,
64 => 0b00,
other => panic!("Unsupported EP0 size: {}", other),
};
let regs = self.usb.endpoint0_out();
write_reg!(endpoint0_out, regs, DOEPTSIZ0, STUPCNT: 1, PKTCNT: 1, XFRSIZ: self.descriptor.max_packet_size as u32);
modify_reg!(endpoint0_out, regs, DOEPCTL0, MPSIZ: mpsiz as u32, EPENA: 1, CNAK: 1);
} else {
let regs = self.usb.endpoint_out(self.index() as usize);
write_reg!(endpoint_out, regs, DOEPCTL,
SD0PID_SEVNFRM: 1,
CNAK: 1,
EPENA: 1,
USBAEP: 1,
EPTYP: self.descriptor.ep_type.to_bm_attributes() as u32,
MPSIZ: self.descriptor.max_packet_size as u32
);
}
}
pub fn deconfigure(&self, _cs: CriticalSection<'_>) {
let regs = self.usb.endpoint_out(self.index() as usize);
// deactivating endpoint
modify_reg!(endpoint_out, regs, DOEPCTL, USBAEP: 0);
// disabling endpoint
if read_reg!(endpoint_out, regs, DOEPCTL, EPENA) != 0 && self.index() != 0 {
modify_reg!(endpoint_out, regs, DOEPCTL, EPDIS: 1)
}
// clean EP interrupts
write_reg!(endpoint_out, regs, DOEPINT, 0xff);
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
let ep = self.usb.endpoint_out(self.index() as usize);
let device = self.usb.device();
match self.descriptor.ep_type {
// Isochronous endpoints must set the correct even/odd frame bit to
// correspond with the next frame's number.
EndpointType::Isochronous { .. } => {
// Previous frame number is OTG_DSTS.FNSOF
let frame_number = read_reg!(otg_device, device, DSTS, FNSOF);
if frame_number & 0x1 == 1 {
// Previous frame number is odd, so upcoming frame is even
modify_reg!(endpoint_out, ep, DOEPCTL, SD0PID_SEVNFRM: 1);
} else {
// Previous frame number is even, so upcoming frame is odd
modify_reg!(endpoint_out, ep, DOEPCTL, SODDFRM: 1);
}
}
_ => {}
}
critical_section::with(|cs| self.buffer.borrow_ref_mut(cs).read_packet(buf))
}
pub fn buffer_state(&self) -> EndpointBufferState {
critical_section::with(|cs| self.buffer.borrow_ref(cs).state())
}
}
impl Deref for EndpointIn {
type Target = Endpoint;
fn deref(&self) -> &Self::Target {
&self.common
}
}
impl DerefMut for EndpointIn {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.common
}
}
impl Deref for EndpointOut {
type Target = Endpoint;
fn deref(&self) -> &Self::Target {
&self.common
}
}
impl DerefMut for EndpointOut {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.common
}
}
+185
View File
@@ -0,0 +1,185 @@
#![allow(dead_code)]
use crate::target::{fifo_read_into, UsbRegisters};
use crate::UsbPeripheral;
use core::marker::PhantomData;
use core::slice;
use usb_device::{Result, UsbError};
use vcell::VolatileCell;
#[derive(Eq, PartialEq)]
pub enum EndpointBufferState {
Empty,
DataOut,
DataSetup,
}
pub struct EndpointBuffer {
buffer: &'static mut [VolatileCell<u32>],
data_size: u16,
has_data: bool,
is_setup: bool,
}
impl EndpointBuffer {
pub fn new(buffer: &'static mut [u32]) -> Self {
Self {
buffer: unsafe { &mut *(buffer as *mut [u32] as *mut [VolatileCell<u32>]) },
data_size: 0,
has_data: false,
is_setup: false,
}
}
pub fn read_packet(&mut self, mut buf: &mut [u8]) -> Result<usize> {
if !self.has_data {
return Err(UsbError::WouldBlock);
}
let data_size = self.data_size as usize;
if buf.len() < data_size {
return Err(UsbError::BufferOverflow);
}
let mut index = 0;
let mut current_size = data_size;
while current_size >= 4 {
let word = self.buffer[index].get();
index += 1;
let bytes = word.to_ne_bytes();
buf[..4].copy_from_slice(&bytes);
buf = &mut buf[4..];
current_size -= 4;
}
if current_size > 0 {
let word = self.buffer[index].get();
let bytes = word.to_ne_bytes();
buf[..current_size].copy_from_slice(&bytes[..current_size]);
}
self.has_data = false;
Ok(data_size)
}
pub fn fill_from_fifo(
&mut self,
usb: UsbRegisters,
data_size: u16,
is_setup: bool,
) -> Result<()> {
if self.has_data {
return Err(UsbError::WouldBlock);
}
if data_size as usize > self.capacity() {
return Err(UsbError::BufferOverflow);
}
let words = (data_size as usize + 3) / 4;
fifo_read_into(usb, &self.buffer[..words]);
self.is_setup = is_setup;
self.data_size = data_size;
self.has_data = true;
Ok(())
}
pub fn state(&self) -> EndpointBufferState {
if self.has_data {
if self.is_setup {
EndpointBufferState::DataSetup
} else {
EndpointBufferState::DataOut
}
} else {
EndpointBufferState::Empty
}
}
pub fn capacity(&self) -> usize {
self.buffer.len() * 4
}
}
impl Default for EndpointBuffer {
fn default() -> Self {
EndpointBuffer::new(&mut [])
}
}
pub struct EndpointMemoryAllocator<USB> {
next_free_offset: usize,
max_size_words: usize,
memory: &'static mut [u32],
tx_fifo_size_words: [u16; 9],
_marker: PhantomData<USB>,
}
impl<USB: UsbPeripheral> EndpointMemoryAllocator<USB> {
pub fn new(memory: &'static mut [u32]) -> Self {
Self {
next_free_offset: 0,
max_size_words: 0,
memory,
tx_fifo_size_words: [0; 9],
_marker: PhantomData,
}
}
pub fn allocate_rx_buffer(&mut self, size: usize) -> Result<EndpointBuffer> {
let size_words = (size + 3) / 4;
let offset = self.next_free_offset;
if offset + size_words > self.memory.len() {
return Err(UsbError::EndpointMemoryOverflow);
}
self.next_free_offset += size_words;
self.max_size_words = core::cmp::max(self.max_size_words, size_words);
let buffer = unsafe {
let ptr = self.memory.as_mut_ptr().add(offset);
slice::from_raw_parts_mut(ptr, size_words)
};
Ok(EndpointBuffer::new(buffer))
}
pub fn allocate_tx_buffer(&mut self, ep_number: u8, size: usize) -> Result<()> {
let ep_number = ep_number as usize;
assert!(ep_number < self.tx_fifo_size_words.len());
if self.tx_fifo_size_words[ep_number] != 0 {
return Err(UsbError::InvalidEndpoint);
}
let used = 30
+ self.total_rx_buffer_size_words() as usize
+ self.tx_fifo_size_words.iter().sum::<u16>() as usize;
let size_words = core::cmp::max((size + 3) / 4, 16);
if (used + size_words) > USB::FIFO_DEPTH_WORDS {
return Err(UsbError::EndpointMemoryOverflow);
}
self.tx_fifo_size_words[ep_number] = size_words as u16;
Ok(())
}
/// Returns the size of memory allocated for OUT endpoints in words
pub fn total_rx_buffer_size_words(&self) -> u16 {
self.next_free_offset as u16
}
pub fn tx_fifo_size_words(&self, ep_number: usize) -> u16 {
self.tx_fifo_size_words[ep_number]
}
pub fn max_buffer_size_words(&self) -> usize {
self.max_size_words
}
}
+70
View File
@@ -0,0 +1,70 @@
//! USB peripheral driver for Synopsys USB OTG peripherals.
#![no_std]
#[cfg(all(feature = "fs", feature = "hs"))]
compile_error!("choose only one USB mode");
#[cfg(not(any(feature = "fs", feature = "hs")))]
compile_error!("select USB mode feature (fs/hs)");
mod endpoint;
mod endpoint_memory;
mod target;
/// USB peripheral driver.
pub mod bus;
pub use crate::bus::UsbBus;
mod ral;
mod transition;
/// A trait for device-specific USB peripherals. Implement this to add support for a new hardware
/// platform. Peripherals that have this trait must have the same register block as STM32 USB OTG
/// peripherals.
pub unsafe trait UsbPeripheral: Send + Sync {
/// Pointer to the register block
const REGISTERS: *const ();
/// true for High Speed variants of the peripheral, false for Full Speed
const HIGH_SPEED: bool;
/// FIFO size in 32-bit words
const FIFO_DEPTH_WORDS: usize;
/// Number of (bidirectional) endpoints
const ENDPOINT_COUNT: usize;
/// Enables USB device on its peripheral bus
fn enable();
/// AHB frequency in hertz
fn ahb_frequency_hz(&self) -> u32;
/// Returns PHY type that should be used for USB peripheral
fn phy_type(&self) -> PhyType {
PhyType::InternalFullSpeed
}
/// Performs initial setup of the internal high-speed PHY
///
/// This function should turn on LDO and PLL and wait for PHY clock to become stable.
fn setup_internal_hs_phy(&self) {}
}
/// USB PHY type
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PhyType {
/// Internal Full-Speed PHY
///
/// Available on most High-Speed peripherals.
InternalFullSpeed,
/// Internal High-Speed PHY
///
/// Available on a few STM32 chips.
InternalHighSpeed,
/// External ULPI High-Speed PHY
ExternalHighSpeed,
}
+8
View File
@@ -0,0 +1,8 @@
#![allow(unused)]
pub mod otg_fs_device_f401_f405_f407_f411_f427_f429;
pub mod otg_fs_global_f401_f405_f407_f411_f427_f429;
pub mod otg_fs_host;
pub mod otg_hs_device;
pub mod otg_hs_global;
pub mod otg_hs_host;
pub mod otg_s_pwrclk;
@@ -0,0 +1,83 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f427, stm32f429
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_device_v1::Instance;
pub use super::super::peripherals::otg_fs_device_v1::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_fs_device_v1::{
DAINT, DAINTMSK, DCFG, DCTL, DIEPCTL0, DIEPCTL1, DIEPCTL2, DIEPCTL3, DIEPEMPMSK, DIEPINT0,
DIEPINT1, DIEPINT2, DIEPINT3, DIEPMSK, DIEPTSIZ0, DIEPTSIZ1, DIEPTSIZ2, DIEPTSIZ3, DOEPCTL0,
DOEPCTL1, DOEPCTL2, DOEPCTL3, DOEPINT0, DOEPINT1, DOEPINT2, DOEPINT3, DOEPMSK, DOEPTSIZ0,
DOEPTSIZ1, DOEPTSIZ2, DOEPTSIZ3, DSTS, DTXFSTS0, DTXFSTS1, DTXFSTS2, DTXFSTS3, DVBUSDIS,
DVBUSPULSE,
};
/// Access functions for the OTG_FS_DEVICE peripheral instance
pub mod OTG_FS_DEVICE {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000800,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_DEVICE
pub const reset: ResetValues = ResetValues {
DCFG: 0x02200000,
DCTL: 0x00000000,
DSTS: 0x00000010,
DIEPMSK: 0x00000000,
DOEPMSK: 0x00000000,
DAINT: 0x00000000,
DAINTMSK: 0x00000000,
DVBUSDIS: 0x000017D7,
DVBUSPULSE: 0x000005B8,
DIEPEMPMSK: 0x00000000,
DIEPCTL0: 0x00000000,
DIEPCTL1: 0x00000000,
DIEPCTL2: 0x00000000,
DIEPCTL3: 0x00000000,
DOEPCTL0: 0x00008000,
DOEPCTL1: 0x00000000,
DOEPCTL2: 0x00000000,
DOEPCTL3: 0x00000000,
DIEPINT0: 0x00000080,
DIEPINT1: 0x00000080,
DIEPINT2: 0x00000080,
DIEPINT3: 0x00000080,
DOEPINT0: 0x00000080,
DOEPINT1: 0x00000080,
DOEPINT2: 0x00000080,
DOEPINT3: 0x00000080,
DIEPTSIZ0: 0x00000000,
DOEPTSIZ0: 0x00000000,
DIEPTSIZ1: 0x00000000,
DIEPTSIZ2: 0x00000000,
DIEPTSIZ3: 0x00000000,
DTXFSTS0: 0x00000000,
DTXFSTS1: 0x00000000,
DTXFSTS2: 0x00000000,
DTXFSTS3: 0x00000000,
DOEPTSIZ1: 0x00000000,
DOEPTSIZ2: 0x00000000,
DOEPTSIZ3: 0x00000000,
};
}
/// Raw pointer to OTG_FS_DEVICE
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_DEVICE: *const RegisterBlock = 0x50000800 as *const _;
@@ -0,0 +1,60 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f427, stm32f429
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_global_v1::Instance;
pub use super::super::peripherals::otg_fs_global_v1::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_fs_global_v1::{
CID, DIEPTXF0, DIEPTXF1, DIEPTXF2, DIEPTXF3, GAHBCFG, GCCFG, GINTMSK, GINTSTS, GNPTXSTS,
GOTGCTL, GOTGINT, GRSTCTL, GRXFSIZ, GRXSTSP, GRXSTSR, GUSBCFG, HPTXFSIZ,
};
/// Access functions for the OTG_FS_GLOBAL peripheral instance
pub mod OTG_FS_GLOBAL {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000000,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_GLOBAL
pub const reset: ResetValues = ResetValues {
GOTGCTL: 0x00000800,
GOTGINT: 0x00000000,
GAHBCFG: 0x00000000,
GUSBCFG: 0x00000A00,
GRSTCTL: 0x20000000,
GINTSTS: 0x04000020,
GINTMSK: 0x00000000,
GRXSTSR: 0x00000000,
GRXFSIZ: 0x00000200,
DIEPTXF0: 0x00000200,
GNPTXSTS: 0x00080200,
GCCFG: 0x00000000,
CID: 0x00001000,
HPTXFSIZ: 0x02000600,
DIEPTXF1: 0x02000400,
DIEPTXF2: 0x02000400,
DIEPTXF3: 0x02000400,
GRXSTSP: 0x00000000,
};
}
/// Raw pointer to OTG_FS_GLOBAL
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_GLOBAL: *const RegisterBlock = 0x50000000 as *const _;
@@ -0,0 +1,83 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f412, stm32f413, stm32f427, stm32f429, stm32f446, stm32f469
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_host::Instance;
pub use super::super::peripherals::otg_fs_host::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_fs_host::{
HAINT, HAINTMSK, HCCHAR0, HCCHAR1, HCCHAR2, HCCHAR3, HCCHAR4, HCCHAR5, HCCHAR6, HCCHAR7, HCFG,
HCINT0, HCINT1, HCINT2, HCINT3, HCINT4, HCINT5, HCINT6, HCINT7, HCINTMSK0, HCINTMSK1,
HCINTMSK2, HCINTMSK3, HCINTMSK4, HCINTMSK5, HCINTMSK6, HCINTMSK7, HCTSIZ0, HCTSIZ1, HCTSIZ2,
HCTSIZ3, HCTSIZ4, HCTSIZ5, HCTSIZ6, HCTSIZ7, HFIR, HFNUM, HPRT, HPTXSTS,
};
/// Access functions for the OTG_FS_HOST peripheral instance
pub mod OTG_FS_HOST {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000400,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_HOST
pub const reset: ResetValues = ResetValues {
HCFG: 0x00000000,
HFIR: 0x0000EA60,
HFNUM: 0x00003FFF,
HPTXSTS: 0x00080100,
HAINT: 0x00000000,
HAINTMSK: 0x00000000,
HPRT: 0x00000000,
HCCHAR0: 0x00000000,
HCCHAR1: 0x00000000,
HCCHAR2: 0x00000000,
HCCHAR3: 0x00000000,
HCCHAR4: 0x00000000,
HCCHAR5: 0x00000000,
HCCHAR6: 0x00000000,
HCCHAR7: 0x00000000,
HCINT0: 0x00000000,
HCINT1: 0x00000000,
HCINT2: 0x00000000,
HCINT3: 0x00000000,
HCINT4: 0x00000000,
HCINT5: 0x00000000,
HCINT6: 0x00000000,
HCINT7: 0x00000000,
HCINTMSK0: 0x00000000,
HCINTMSK1: 0x00000000,
HCINTMSK2: 0x00000000,
HCINTMSK3: 0x00000000,
HCINTMSK4: 0x00000000,
HCINTMSK5: 0x00000000,
HCINTMSK6: 0x00000000,
HCINTMSK7: 0x00000000,
HCTSIZ0: 0x00000000,
HCTSIZ1: 0x00000000,
HCTSIZ2: 0x00000000,
HCTSIZ3: 0x00000000,
HCTSIZ4: 0x00000000,
HCTSIZ5: 0x00000000,
HCTSIZ6: 0x00000000,
HCTSIZ7: 0x00000000,
};
}
/// Raw pointer to OTG_FS_HOST
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_HOST: *const RegisterBlock = 0x50000400 as *const _;
@@ -0,0 +1,109 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go high speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_hs_device::Instance;
pub use super::super::peripherals::otg_hs_device::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_hs_device::{
DAINT, DAINTMSK, DCFG, DCTL, DEACHINT, DEACHINTMSK, DIEPCTL0, DIEPCTL1, DIEPCTL2, DIEPCTL3,
DIEPCTL4, DIEPCTL5, DIEPDMA1, DIEPDMA2, DIEPDMA3, DIEPDMA4, DIEPDMA5, DIEPEACHMSK1, DIEPEMPMSK,
DIEPINT0, DIEPINT1, DIEPINT2, DIEPINT3, DIEPINT4, DIEPINT5, DIEPMSK, DIEPTSIZ0, DIEPTSIZ1,
DIEPTSIZ2, DIEPTSIZ3, DIEPTSIZ4, DIEPTSIZ5, DOEPCTL0, DOEPCTL1, DOEPCTL2, DOEPCTL3, DOEPCTL4,
DOEPCTL5, DOEPEACHMSK1, DOEPINT0, DOEPINT1, DOEPINT2, DOEPINT3, DOEPINT4, DOEPINT5, DOEPMSK,
DOEPTSIZ0, DOEPTSIZ1, DOEPTSIZ2, DOEPTSIZ3, DOEPTSIZ4, DOEPTSIZ5, DSTS, DTHRCTL, DTXFSTS0,
DTXFSTS1, DTXFSTS2, DTXFSTS3, DTXFSTS4, DTXFSTS5, DVBUSDIS, DVBUSPULSE,
};
/// Access functions for the OTG_HS_DEVICE peripheral instance
pub mod OTG_HS_DEVICE {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040800,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_DEVICE
pub const reset: ResetValues = ResetValues {
DCFG: 0x02200000,
DCTL: 0x00000000,
DSTS: 0x00000010,
DIEPMSK: 0x00000000,
DOEPMSK: 0x00000000,
DAINT: 0x00000000,
DAINTMSK: 0x00000000,
DVBUSDIS: 0x000017D7,
DVBUSPULSE: 0x000005B8,
DTHRCTL: 0x00000000,
DIEPEMPMSK: 0x00000000,
DEACHINT: 0x00000000,
DEACHINTMSK: 0x00000000,
DIEPEACHMSK1: 0x00000000,
DOEPEACHMSK1: 0x00000000,
DIEPCTL0: 0x00000000,
DIEPCTL1: 0x00000000,
DIEPCTL2: 0x00000000,
DIEPCTL3: 0x00000000,
DIEPCTL4: 0x00000000,
DIEPCTL5: 0x00000000,
DIEPINT0: 0x00000080,
DIEPINT1: 0x00000080,
DIEPINT2: 0x00000080,
DIEPINT3: 0x00000080,
DIEPINT4: 0x00000080,
DIEPINT5: 0x00000080,
DIEPTSIZ0: 0x00000000,
DIEPDMA1: 0x00000000,
DIEPDMA2: 0x00000000,
DIEPDMA3: 0x00000000,
DIEPDMA4: 0x00000000,
DIEPDMA5: 0x00000000,
DTXFSTS0: 0x00000000,
DTXFSTS1: 0x00000000,
DTXFSTS2: 0x00000000,
DTXFSTS3: 0x00000000,
DTXFSTS4: 0x00000000,
DTXFSTS5: 0x00000000,
DIEPTSIZ1: 0x00000000,
DIEPTSIZ2: 0x00000000,
DIEPTSIZ3: 0x00000000,
DIEPTSIZ4: 0x00000000,
DIEPTSIZ5: 0x00000000,
DOEPCTL0: 0x00008000,
DOEPCTL1: 0x00000000,
DOEPCTL2: 0x00000000,
DOEPCTL3: 0x00000000,
DOEPCTL4: 0x00000000,
DOEPCTL5: 0x00000000,
DOEPINT0: 0x00000080,
DOEPINT1: 0x00000080,
DOEPINT2: 0x00000080,
DOEPINT3: 0x00000080,
DOEPINT4: 0x00000080,
DOEPINT5: 0x00000080,
DOEPTSIZ0: 0x00000000,
DOEPTSIZ1: 0x00000000,
DOEPTSIZ2: 0x00000000,
DOEPTSIZ3: 0x00000000,
DOEPTSIZ4: 0x00000000,
DOEPTSIZ5: 0x00000000,
};
}
/// Raw pointer to OTG_HS_DEVICE
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_DEVICE: *const RegisterBlock = 0x40040800 as *const _;
@@ -0,0 +1,64 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go high speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_hs_global::Instance;
pub use super::super::peripherals::otg_hs_global::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_hs_global::{
CID, DIEPTXF1, DIEPTXF2, DIEPTXF3, DIEPTXF4, DIEPTXF5, GAHBCFG, GCCFG, GINTMSK, GINTSTS,
GNPTXFSIZ, GNPTXSTS, GOTGCTL, GOTGINT, GRSTCTL, GRXFSIZ, GRXSTSP, GRXSTSR, GUSBCFG, HPTXFSIZ,
PHYCR,
};
/// Access functions for the OTG_HS_GLOBAL peripheral instance
pub mod OTG_HS_GLOBAL {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040000,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_GLOBAL
pub const reset: ResetValues = ResetValues {
GOTGCTL: 0x00000800,
GOTGINT: 0x00000000,
GAHBCFG: 0x00000000,
GUSBCFG: 0x00000A00,
GRSTCTL: 0x20000000,
GINTSTS: 0x04000020,
GINTMSK: 0x00000000,
GRXSTSR: 0x00000000,
GRXSTSP: 0x00000000,
GRXFSIZ: 0x00000200,
GNPTXFSIZ: 0x00000200,
GNPTXSTS: 0x00080200,
PHYCR: 0x00000000,
GCCFG: 0x00000000,
CID: 0x00001200,
HPTXFSIZ: 0x02000600,
DIEPTXF1: 0x02000400,
DIEPTXF2: 0x02000400,
DIEPTXF3: 0x02000400,
DIEPTXF4: 0x02000400,
DIEPTXF5: 0x02000400,
};
}
/// Raw pointer to OTG_HS_GLOBAL
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_GLOBAL: *const RegisterBlock = 0x40040000 as *const _;
@@ -0,0 +1,127 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go high speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446, stm32f469
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_hs_host::Instance;
pub use super::super::peripherals::otg_hs_host::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_hs_host::{
HAINT, HAINTMSK, HCCHAR0, HCCHAR1, HCCHAR10, HCCHAR11, HCCHAR2, HCCHAR3, HCCHAR4, HCCHAR5,
HCCHAR6, HCCHAR7, HCCHAR8, HCCHAR9, HCDMA0, HCDMA1, HCDMA10, HCDMA11, HCDMA2, HCDMA3, HCDMA4,
HCDMA5, HCDMA6, HCDMA7, HCDMA8, HCDMA9, HCFG, HCINT0, HCINT1, HCINT10, HCINT11, HCINT2, HCINT3,
HCINT4, HCINT5, HCINT6, HCINT7, HCINT8, HCINT9, HCINTMSK0, HCINTMSK1, HCINTMSK10, HCINTMSK11,
HCINTMSK2, HCINTMSK3, HCINTMSK4, HCINTMSK5, HCINTMSK6, HCINTMSK7, HCINTMSK8, HCINTMSK9,
HCSPLT0, HCSPLT1, HCSPLT10, HCSPLT11, HCSPLT2, HCSPLT3, HCSPLT4, HCSPLT5, HCSPLT6, HCSPLT7,
HCSPLT8, HCSPLT9, HCTSIZ0, HCTSIZ1, HCTSIZ10, HCTSIZ11, HCTSIZ2, HCTSIZ3, HCTSIZ4, HCTSIZ5,
HCTSIZ6, HCTSIZ7, HCTSIZ8, HCTSIZ9, HFIR, HFNUM, HPRT, HPTXSTS,
};
/// Access functions for the OTG_HS_HOST peripheral instance
pub mod OTG_HS_HOST {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040400,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_HOST
pub const reset: ResetValues = ResetValues {
HCFG: 0x00000000,
HFIR: 0x0000EA60,
HFNUM: 0x00003FFF,
HPTXSTS: 0x00080100,
HAINT: 0x00000000,
HAINTMSK: 0x00000000,
HPRT: 0x00000000,
HCCHAR0: 0x00000000,
HCCHAR1: 0x00000000,
HCCHAR2: 0x00000000,
HCCHAR3: 0x00000000,
HCCHAR4: 0x00000000,
HCCHAR5: 0x00000000,
HCCHAR6: 0x00000000,
HCCHAR7: 0x00000000,
HCCHAR8: 0x00000000,
HCCHAR9: 0x00000000,
HCCHAR10: 0x00000000,
HCCHAR11: 0x00000000,
HCSPLT0: 0x00000000,
HCSPLT1: 0x00000000,
HCSPLT2: 0x00000000,
HCSPLT3: 0x00000000,
HCSPLT4: 0x00000000,
HCSPLT5: 0x00000000,
HCSPLT6: 0x00000000,
HCSPLT7: 0x00000000,
HCSPLT8: 0x00000000,
HCSPLT9: 0x00000000,
HCSPLT10: 0x00000000,
HCSPLT11: 0x00000000,
HCINT0: 0x00000000,
HCINT1: 0x00000000,
HCINT2: 0x00000000,
HCINT3: 0x00000000,
HCINT4: 0x00000000,
HCINT5: 0x00000000,
HCINT6: 0x00000000,
HCINT7: 0x00000000,
HCINT8: 0x00000000,
HCINT9: 0x00000000,
HCINT10: 0x00000000,
HCINT11: 0x00000000,
HCINTMSK0: 0x00000000,
HCINTMSK1: 0x00000000,
HCINTMSK2: 0x00000000,
HCINTMSK3: 0x00000000,
HCINTMSK4: 0x00000000,
HCINTMSK5: 0x00000000,
HCINTMSK6: 0x00000000,
HCINTMSK7: 0x00000000,
HCINTMSK8: 0x00000000,
HCINTMSK9: 0x00000000,
HCINTMSK10: 0x00000000,
HCINTMSK11: 0x00000000,
HCTSIZ0: 0x00000000,
HCTSIZ1: 0x00000000,
HCTSIZ2: 0x00000000,
HCTSIZ3: 0x00000000,
HCTSIZ4: 0x00000000,
HCTSIZ5: 0x00000000,
HCTSIZ6: 0x00000000,
HCTSIZ7: 0x00000000,
HCTSIZ8: 0x00000000,
HCTSIZ9: 0x00000000,
HCTSIZ10: 0x00000000,
HCTSIZ11: 0x00000000,
HCDMA0: 0x00000000,
HCDMA1: 0x00000000,
HCDMA2: 0x00000000,
HCDMA3: 0x00000000,
HCDMA4: 0x00000000,
HCDMA5: 0x00000000,
HCDMA6: 0x00000000,
HCDMA7: 0x00000000,
HCDMA8: 0x00000000,
HCDMA9: 0x00000000,
HCDMA10: 0x00000000,
HCDMA11: 0x00000000,
};
}
/// Raw pointer to OTG_HS_HOST
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_HOST: *const RegisterBlock = 0x40040400 as *const _;
@@ -0,0 +1,70 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_pwrclk::Instance;
pub use super::super::peripherals::otg_fs_pwrclk::PCGCCTL;
pub use super::super::peripherals::otg_fs_pwrclk::{RegisterBlock, ResetValues};
/// Access functions for the OTG_FS_PWRCLK peripheral instance
pub mod OTG_FS_PWRCLK {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000e00,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_PWRCLK
pub const reset: ResetValues = ResetValues {
PCGCCTL: 0x00000000,
};
}
/// Raw pointer to OTG_FS_PWRCLK
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_PWRCLK: *const RegisterBlock = 0x50000e00 as *const _;
/// Access functions for the OTG_HS_PWRCLK peripheral instance
pub mod OTG_HS_PWRCLK {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040e00,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_PWRCLK
pub const reset: ResetValues = ResetValues {
PCGCCTL: 0x00000000,
};
}
/// Raw pointer to OTG_HS_PWRCLK
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_PWRCLK: *const RegisterBlock = 0x40040e00 as *const _;
+118
View File
@@ -0,0 +1,118 @@
#![allow(non_snake_case)]
pub mod instances;
pub mod peripherals;
pub mod stm32f429;
pub use ral_registers as register;
pub use ral_registers::{modify_reg, read_reg, write_reg};
pub mod otg_global {
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_global::OTG_FS_GLOBAL as OTG_GLOBAL;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_global::*;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_global::OTG_HS_GLOBAL as OTG_GLOBAL;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_global::*;
}
pub mod otg_device {
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::OTG_FS_DEVICE as OTG_DEVICE;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::*;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::OTG_HS_DEVICE as OTG_DEVICE;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::*;
}
pub mod otg_pwrclk {
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_s_pwrclk::OTG_FS_PWRCLK as OTG_PWRCLK;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_s_pwrclk::OTG_HS_PWRCLK as OTG_PWRCLK;
pub use super::stm32f429::otg_s_pwrclk::*;
}
pub mod otg_global_dieptxfx {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_global::DIEPTXF1 as DIEPTXFx;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_fs_global::DIEPTXF1 as DIEPTXFx;
pub struct RegisterBlock {
pub DIEPTXFx: RWRegister<u32>,
}
}
pub mod endpoint_in {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::{
DIEPCTL1 as DIEPCTL, DIEPINT1 as DIEPINT, DIEPTSIZ1 as DIEPTSIZ, DTXFSTS1 as DTXFSTS,
};
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::{
DIEPCTL1 as DIEPCTL, DIEPINT1 as DIEPINT, DIEPTSIZ1 as DIEPTSIZ, DTXFSTS1 as DTXFSTS,
};
pub struct RegisterBlock {
pub DIEPCTL: RWRegister<u32>,
_reserved0: u32,
pub DIEPINT: RWRegister<u32>,
_reserved1: u32,
pub DIEPTSIZ: RWRegister<u32>,
_reserved2: u32,
pub DTXFSTS: RWRegister<u32>,
_reserved3: u32,
}
}
pub mod endpoint0_out {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::{DOEPCTL0, DOEPINT0, DOEPTSIZ0};
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::{DOEPCTL0, DOEPINT0, DOEPTSIZ0};
pub struct RegisterBlock {
pub DOEPCTL0: RWRegister<u32>,
_reserved0: u32,
pub DOEPINT0: RWRegister<u32>,
_reserved1: u32,
pub DOEPTSIZ0: RWRegister<u32>,
_reserved2: [u32; 3],
}
}
pub mod endpoint_out {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::{
DOEPCTL1 as DOEPCTL, DOEPINT1 as DOEPINT, DOEPTSIZ1 as DOEPTSIZ,
};
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::{
DOEPCTL1 as DOEPCTL, DOEPINT1 as DOEPINT, DOEPTSIZ1 as DOEPTSIZ,
};
pub struct RegisterBlock {
pub DOEPCTL: RWRegister<u32>,
_reserved0: u32,
pub DOEPINT: RWRegister<u32>,
_reserved1: u32,
pub DOEPTSIZ: RWRegister<u32>,
_reserved2: [u32; 3],
}
}
@@ -0,0 +1,8 @@
#![allow(unused)]
pub mod otg_fs_device_v1;
pub mod otg_fs_global_v1;
pub mod otg_fs_host;
pub mod otg_fs_pwrclk;
pub mod otg_hs_device;
pub mod otg_hs_global;
pub mod otg_hs_host;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,78 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f412, stm32f413, stm32f427, stm32f429, stm32f446, stm32f469
use super::super::register::RWRegister;
#[cfg(not(feature = "nosync"))]
use core::marker::PhantomData;
/// OTG_FS power and clock gating control register
pub mod PCGCCTL {
/// Stop PHY clock
pub mod STPPCLK {
/// Offset (0 bits)
pub const offset: u32 = 0;
/// Mask (1 bit: 1 << 0)
pub const mask: u32 = 1 << offset;
/// Read-only values (empty)
pub mod R {}
/// Write-only values (empty)
pub mod W {}
/// Read-write values (empty)
pub mod RW {}
}
/// Gate HCLK
pub mod GATEHCLK {
/// Offset (1 bits)
pub const offset: u32 = 1;
/// Mask (1 bit: 1 << 1)
pub const mask: u32 = 1 << offset;
/// Read-only values (empty)
pub mod R {}
/// Write-only values (empty)
pub mod W {}
/// Read-write values (empty)
pub mod RW {}
}
/// PHY Suspended
pub mod PHYSUSP {
/// Offset (4 bits)
pub const offset: u32 = 4;
/// Mask (1 bit: 1 << 4)
pub const mask: u32 = 1 << offset;
/// Read-only values (empty)
pub mod R {}
/// Write-only values (empty)
pub mod W {}
/// Read-write values (empty)
pub mod RW {}
}
}
#[repr(C)]
pub struct RegisterBlock {
/// OTG_FS power and clock gating control register
pub PCGCCTL: RWRegister<u32>,
}
pub struct ResetValues {
pub PCGCCTL: u32,
}
#[cfg(not(feature = "nosync"))]
pub struct Instance {
pub(crate) addr: u32,
pub(crate) _marker: PhantomData<*const RegisterBlock>,
}
#[cfg(not(feature = "nosync"))]
impl ::core::ops::Deref for Instance {
type Target = RegisterBlock;
#[inline(always)]
fn deref(&self) -> &RegisterBlock {
unsafe { &*(self.addr as *const _) }
}
}
#[cfg(feature = "rtfm")]
unsafe impl Send for Instance {}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+7
View File
@@ -0,0 +1,7 @@
pub use super::instances::otg_fs_device_f401_f405_f407_f411_f427_f429 as otg_fs_device;
pub use super::instances::otg_fs_global_f401_f405_f407_f411_f427_f429 as otg_fs_global;
pub use super::instances::otg_fs_host;
pub use super::instances::otg_hs_device;
pub use super::instances::otg_hs_global;
pub use super::instances::otg_hs_host;
pub use super::instances::otg_s_pwrclk;
+92
View File
@@ -0,0 +1,92 @@
//! Target-specific definitions
use vcell::VolatileCell;
use crate::ral::register::RWRegister;
use crate::ral::{
endpoint0_out, endpoint_in, endpoint_out, otg_device, otg_global, otg_global_dieptxfx,
otg_pwrclk,
};
use crate::UsbPeripheral;
pub fn fifo_write(usb: UsbRegisters, channel: impl Into<usize>, mut buf: &[u8]) {
let fifo = usb.fifo(channel.into());
while buf.len() >= 4 {
let mut u32_bytes = [0u8; 4];
u32_bytes.copy_from_slice(&buf[..4]);
buf = &buf[4..];
fifo.write(u32::from_ne_bytes(u32_bytes));
}
if !buf.is_empty() {
let mut u32_bytes = [0u8; 4];
u32_bytes[..buf.len()].copy_from_slice(buf);
fifo.write(u32::from_ne_bytes(u32_bytes));
}
}
pub fn fifo_read_into(usb: UsbRegisters, buf: &[VolatileCell<u32>]) {
let fifo = usb.fifo(0);
for p in buf {
let word = fifo.read();
p.set(word);
}
}
/// Wrapper around device-specific peripheral that provides unified register interface
#[derive(Copy, Clone)]
pub struct UsbRegisters(usize);
impl UsbRegisters {
#[inline(always)]
pub fn new<USB: UsbPeripheral>() -> Self {
Self(USB::REGISTERS as usize)
}
#[inline(always)]
pub fn global(&self) -> &'static otg_global::RegisterBlock {
unsafe { &*(self.0 as *const _) }
}
#[inline(always)]
pub fn device(&self) -> &'static otg_device::RegisterBlock {
unsafe { &*((self.0 + 0x800) as *const _) }
}
#[inline(always)]
pub fn pwrclk(&self) -> &'static otg_pwrclk::RegisterBlock {
unsafe { &*((self.0 + 0xe00) as *const _) }
}
#[inline(always)]
pub fn fifo(&self, channel: usize) -> &'static RWRegister<u32> {
assert!(channel <= 15);
let address = self.0 + 0x1000 + channel * 0x1000;
unsafe { &*(address as *const RWRegister<u32>) }
}
#[inline(always)]
pub fn dieptxfx(&self, index: usize) -> &'static otg_global_dieptxfx::RegisterBlock {
let address = self.0 + 0x100 + 4 * index;
unsafe { &*(address as *const _) }
}
#[inline(always)]
pub fn endpoint_in(&self, index: usize) -> &'static endpoint_in::RegisterBlock {
let address = self.0 + 0x900 + 0x20 * index;
unsafe { &*(address as *const _) }
}
#[inline(always)]
pub fn endpoint0_out(&self) -> &'static endpoint0_out::RegisterBlock {
let address = self.0 + 0xb00;
unsafe { &*(address as *const _) }
}
#[inline(always)]
pub fn endpoint_out(&self, index: usize) -> &'static endpoint_out::RegisterBlock {
let address = self.0 + 0xb00 + 0x20 * index;
unsafe { &*(address as *const _) }
}
}
+41
View File
@@ -0,0 +1,41 @@
use usb_device::endpoint::{EndpointAddress, EndpointType};
/// USB endpoint descriptor information.
pub struct EndpointDescriptor {
/// Endpoint address.
pub address: EndpointAddress,
/// Endpoint transfer type.
pub ep_type: EndpointType,
/// Maximum packet size.
pub max_packet_size: u16,
/// Poll interval for interrupt endpoints.
pub interval: u8,
}
/// Configuration for an endpoint allocation.
pub struct EndpointConfig {
/// The transfer type of the endpoint to be allocated.
pub ep_type: EndpointType,
/// Maximum packet size for the endpoint to be allocated.
pub max_packet_size: u16,
/// Poll interval for interrupt endpoints.
pub interval: u8,
/// Requests a specific endpoint number. Allocation shall fail if the number is not available.
pub number: Option<u8>,
/// Specifies that the endpoint is the "pair" of another endpoint.
///
/// If `ep` is an endpoint in the opposite direction, this means that the endpoint to be
/// allocated uses the same transfer type as `ep` but in the opposite direction in all alternate
/// settings for the interface.
///
/// If `ep` is an endpoint in the same direction, this means that the two endpoints belong to
/// different alternate settings for the interface and may never be enabled at the same time.
pub pair_of: Option<EndpointAddress>,
}