Merge branch 'master' into usb_msc

This commit is contained in:
chemicstry 2023-02-27 01:19:52 +02:00
commit dd9ec05cf9
142 changed files with 3965 additions and 1165 deletions

View File

@ -17,7 +17,7 @@ jobs:
strategy: strategy:
matrix: matrix:
crates: crates:
- stm32 #- stm32 # runs out of disk space...
- rest - rest
# This will ensure at most one doc build job is running at a time # This will ensure at most one doc build job is running at a time
@ -46,7 +46,7 @@ jobs:
- name: Install docserver - name: Install docserver
run: | run: |
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.3/builder" wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder"
chmod +x /usr/local/bin/builder chmod +x /usr/local/bin/builder
- name: build-stm32 - name: build-stm32
@ -70,12 +70,14 @@ jobs:
builder ./embassy-lora crates/embassy-lora/git.zup builder ./embassy-lora crates/embassy-lora/git.zup
builder ./embassy-net crates/embassy-net/git.zup builder ./embassy-net crates/embassy-net/git.zup
builder ./embassy-net-driver crates/embassy-net-driver/git.zup builder ./embassy-net-driver crates/embassy-net-driver/git.zup
builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup
builder ./embassy-nrf crates/embassy-nrf/git.zup builder ./embassy-nrf crates/embassy-nrf/git.zup
builder ./embassy-rp crates/embassy-rp/git.zup builder ./embassy-rp crates/embassy-rp/git.zup
builder ./embassy-sync crates/embassy-sync/git.zup builder ./embassy-sync crates/embassy-sync/git.zup
builder ./embassy-time crates/embassy-time/git.zup builder ./embassy-time crates/embassy-time/git.zup
builder ./embassy-usb crates/embassy-usb/git.zup builder ./embassy-usb crates/embassy-usb/git.zup
builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup
builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup
- name: upload - name: upload
run: | run: |

7
ci.sh
View File

@ -46,8 +46,8 @@ cargo batch \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1,reset-pin-as-gpio \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits,nfc-pins-as-gpio \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \
@ -63,7 +63,8 @@ cargo batch \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \

View File

@ -15,10 +15,10 @@ mod waker;
use core::cell::Cell; use core::cell::Cell;
use core::future::Future; use core::future::Future;
use core::mem;
use core::pin::Pin; use core::pin::Pin;
use core::ptr::NonNull; use core::ptr::NonNull;
use core::task::{Context, Poll}; use core::task::{Context, Poll};
use core::{mem, ptr};
use atomic_polyfill::{AtomicU32, Ordering}; use atomic_polyfill::{AtomicU32, Ordering};
use critical_section::CriticalSection; use critical_section::CriticalSection;
@ -43,14 +43,11 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
/// Raw task header for use in task pointers. /// Raw task header for use in task pointers.
/// pub(crate) struct TaskHeader {
/// This is an opaque struct, used for raw pointers to tasks, for use
/// with funtions like [`wake_task`] and [`task_from_waker`].
pub struct TaskHeader {
pub(crate) state: AtomicU32, pub(crate) state: AtomicU32,
pub(crate) run_queue_item: RunQueueItem, pub(crate) run_queue_item: RunQueueItem,
pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 pub(crate) executor: Cell<Option<&'static Executor>>,
pub(crate) poll_fn: UninitCell<unsafe fn(NonNull<TaskHeader>)>, // Valid if STATE_SPAWNED poll_fn: Cell<Option<unsafe fn(TaskRef)>>,
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
pub(crate) expires_at: Cell<Instant>, pub(crate) expires_at: Cell<Instant>,
@ -58,20 +55,34 @@ pub struct TaskHeader {
pub(crate) timer_queue_item: timer_queue::TimerQueueItem, pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
} }
impl TaskHeader { /// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
pub(crate) const fn new() -> Self { #[derive(Clone, Copy)]
Self { pub struct TaskRef {
state: AtomicU32::new(0), ptr: NonNull<TaskHeader>,
run_queue_item: RunQueueItem::new(), }
executor: Cell::new(ptr::null()),
poll_fn: UninitCell::uninit(),
#[cfg(feature = "integrated-timers")] impl TaskRef {
expires_at: Cell::new(Instant::from_ticks(0)), fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
#[cfg(feature = "integrated-timers")] Self {
timer_queue_item: timer_queue::TimerQueueItem::new(), ptr: NonNull::from(task).cast(),
} }
} }
/// Safety: The pointer must have been obtained with `Task::as_ptr`
pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self {
Self {
ptr: NonNull::new_unchecked(ptr as *mut TaskHeader),
}
}
pub(crate) fn header(self) -> &'static TaskHeader {
unsafe { self.ptr.as_ref() }
}
/// The returned pointer is valid for the entire TaskStorage.
pub(crate) fn as_ptr(self) -> *const TaskHeader {
self.ptr.as_ptr()
}
} }
/// Raw storage in which a task can be spawned. /// Raw storage in which a task can be spawned.
@ -101,7 +112,18 @@ impl<F: Future + 'static> TaskStorage<F> {
/// Create a new TaskStorage, in not-spawned state. /// Create a new TaskStorage, in not-spawned state.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
raw: TaskHeader::new(), raw: TaskHeader {
state: AtomicU32::new(0),
run_queue_item: RunQueueItem::new(),
executor: Cell::new(None),
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
poll_fn: Cell::new(None),
#[cfg(feature = "integrated-timers")]
expires_at: Cell::new(Instant::from_ticks(0)),
#[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(),
},
future: UninitCell::uninit(), future: UninitCell::uninit(),
} }
} }
@ -120,29 +142,17 @@ impl<F: Future + 'static> TaskStorage<F> {
/// Once the task has finished running, you may spawn it again. It is allowed to spawn it /// Once the task has finished running, you may spawn it again. It is allowed to spawn it
/// on a different executor. /// on a different executor.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
if self.spawn_mark_used() { let task = AvailableTask::claim(self);
return unsafe { SpawnToken::<F>::new(self.spawn_initialize(future)) }; match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<F>::new(task) }
}
None => SpawnToken::new_failed(),
}
} }
SpawnToken::<F>::new_failed() unsafe fn poll(p: TaskRef) {
}
fn spawn_mark_used(&'static self) -> bool {
let state = STATE_SPAWNED | STATE_RUN_QUEUED;
self.raw
.state
.compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull<TaskHeader> {
// Initialize the task
self.raw.poll_fn.write(Self::poll);
self.future.write(future());
NonNull::new_unchecked(self as *const TaskStorage<F> as *const TaskHeader as *mut TaskHeader)
}
unsafe fn poll(p: NonNull<TaskHeader>) {
let this = &*(p.as_ptr() as *const TaskStorage<F>); let this = &*(p.as_ptr() as *const TaskStorage<F>);
let future = Pin::new_unchecked(this.future.as_mut()); let future = Pin::new_unchecked(this.future.as_mut());
@ -164,6 +174,28 @@ impl<F: Future + 'static> TaskStorage<F> {
unsafe impl<F: Future + 'static> Sync for TaskStorage<F> {} unsafe impl<F: Future + 'static> Sync for TaskStorage<F> {}
struct AvailableTask<F: Future + 'static> {
task: &'static TaskStorage<F>,
}
impl<F: Future + 'static> AvailableTask<F> {
fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
task.raw
.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
.ok()
.map(|_| Self { task })
}
fn initialize(self, future: impl FnOnce() -> F) -> TaskRef {
unsafe {
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
self.task.future.write(future());
}
TaskRef::new(self.task)
}
}
/// Raw storage that can hold up to N tasks of the same type. /// Raw storage that can hold up to N tasks of the same type.
/// ///
/// This is essentially a `[TaskStorage<F>; N]`. /// This is essentially a `[TaskStorage<F>; N]`.
@ -187,13 +219,14 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
/// is currently free. If none is free, a "poisoned" SpawnToken is returned, /// is currently free. If none is free, a "poisoned" SpawnToken is returned,
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
for task in &self.pool { let task = self.pool.iter().find_map(AvailableTask::claim);
if task.spawn_mark_used() { match task {
return unsafe { SpawnToken::<F>::new(task.spawn_initialize(future)) }; Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<F>::new(task) }
} }
None => SpawnToken::new_failed(),
} }
SpawnToken::<F>::new_failed()
} }
/// Like spawn(), but allows the task to be send-spawned if the args are Send even if /// Like spawn(), but allows the task to be send-spawned if the args are Send even if
@ -235,13 +268,14 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`. // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`.
for task in &self.pool { let task = self.pool.iter().find_map(AvailableTask::claim);
if task.spawn_mark_used() { match task {
return SpawnToken::<FutFn>::new(task.spawn_initialize(future)); Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<FutFn>::new(task) }
} }
None => SpawnToken::new_failed(),
} }
SpawnToken::<FutFn>::new_failed()
} }
} }
@ -307,7 +341,7 @@ impl Executor {
/// - `task` must be set up to run in this executor. /// - `task` must be set up to run in this executor.
/// - `task` must NOT be already enqueued (in this executor or another one). /// - `task` must NOT be already enqueued (in this executor or another one).
#[inline(always)] #[inline(always)]
unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull<TaskHeader>) { unsafe fn enqueue(&self, cs: CriticalSection, task: TaskRef) {
#[cfg(feature = "rtos-trace")] #[cfg(feature = "rtos-trace")]
trace::task_ready_begin(task.as_ptr() as u32); trace::task_ready_begin(task.as_ptr() as u32);
@ -325,8 +359,8 @@ impl Executor {
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread. /// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
/// In this case, the task's Future must be Send. This is because this is effectively /// In this case, the task's Future must be Send. This is because this is effectively
/// sending the task to the executor thread. /// sending the task to the executor thread.
pub(super) unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) { pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
task.as_ref().executor.set(self); task.header().executor.set(Some(self));
#[cfg(feature = "rtos-trace")] #[cfg(feature = "rtos-trace")]
trace::task_new(task.as_ptr() as u32); trace::task_new(task.as_ptr() as u32);
@ -359,7 +393,7 @@ impl Executor {
self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task));
self.run_queue.dequeue_all(|p| { self.run_queue.dequeue_all(|p| {
let task = p.as_ref(); let task = p.header();
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
task.expires_at.set(Instant::MAX); task.expires_at.set(Instant::MAX);
@ -378,7 +412,7 @@ impl Executor {
trace::task_exec_begin(p.as_ptr() as u32); trace::task_exec_begin(p.as_ptr() as u32);
// Run the task // Run the task
task.poll_fn.read()(p as _); task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "rtos-trace")] #[cfg(feature = "rtos-trace")]
trace::task_exec_end(); trace::task_exec_end();
@ -417,16 +451,12 @@ impl Executor {
} }
} }
/// Wake a task by raw pointer. /// Wake a task by `TaskRef`.
/// ///
/// You can obtain task pointers from `Waker`s using [`task_from_waker`]. /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
/// pub fn wake_task(task: TaskRef) {
/// # Safety
///
/// `task` must be a valid task pointer obtained from [`task_from_waker`].
pub unsafe fn wake_task(task: NonNull<TaskHeader>) {
critical_section::with(|cs| { critical_section::with(|cs| {
let header = task.as_ref(); let header = task.header();
let state = header.state.load(Ordering::Relaxed); let state = header.state.load(Ordering::Relaxed);
// If already scheduled, or if not started, // If already scheduled, or if not started,
@ -438,8 +468,10 @@ pub unsafe fn wake_task(task: NonNull<TaskHeader>) {
header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed); header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed);
// We have just marked the task as scheduled, so enqueue it. // We have just marked the task as scheduled, so enqueue it.
let executor = &*header.executor.get(); unsafe {
let executor = header.executor.get().unwrap_unchecked();
executor.enqueue(cs, task); executor.enqueue(cs, task);
}
}) })
} }
@ -450,7 +482,7 @@ struct TimerQueue;
impl embassy_time::queue::TimerQueue for TimerQueue { impl embassy_time::queue::TimerQueue for TimerQueue {
fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) { fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) {
let task = waker::task_from_waker(waker); let task = waker::task_from_waker(waker);
let task = unsafe { task.as_ref() }; let task = task.header();
let expires_at = task.expires_at.get(); let expires_at = task.expires_at.get();
task.expires_at.set(expires_at.min(at)); task.expires_at.set(expires_at.min(at));
} }

View File

@ -4,7 +4,7 @@ use core::ptr::NonNull;
use atomic_polyfill::{AtomicPtr, Ordering}; use atomic_polyfill::{AtomicPtr, Ordering};
use critical_section::CriticalSection; use critical_section::CriticalSection;
use super::TaskHeader; use super::{TaskHeader, TaskRef};
pub(crate) struct RunQueueItem { pub(crate) struct RunQueueItem {
next: AtomicPtr<TaskHeader>, next: AtomicPtr<TaskHeader>,
@ -46,25 +46,26 @@ impl RunQueue {
/// ///
/// `item` must NOT be already enqueued in any queue. /// `item` must NOT be already enqueued in any queue.
#[inline(always)] #[inline(always)]
pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull<TaskHeader>) -> bool { pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: TaskRef) -> bool {
let prev = self.head.load(Ordering::Relaxed); let prev = self.head.load(Ordering::Relaxed);
task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed); task.header().run_queue_item.next.store(prev, Ordering::Relaxed);
self.head.store(task.as_ptr(), Ordering::Relaxed); self.head.store(task.as_ptr() as _, Ordering::Relaxed);
prev.is_null() prev.is_null()
} }
/// Empty the queue, then call `on_task` for each task that was in the queue. /// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one. /// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull<TaskHeader>)) { pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
// Atomically empty the queue. // Atomically empty the queue.
let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
// Iterate the linked list of tasks that were previously in the queue. // Iterate the linked list of tasks that were previously in the queue.
while let Some(task) = NonNull::new(ptr) { while let Some(task) = NonNull::new(ptr) {
let task = unsafe { TaskRef::from_ptr(task.as_ptr()) };
// If the task re-enqueues itself, the `next` pointer will get overwritten. // If the task re-enqueues itself, the `next` pointer will get overwritten.
// Therefore, first read the next pointer, and only then process the task. // Therefore, first read the next pointer, and only then process the task.
let next = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed); let next = task.header().run_queue_item.next.load(Ordering::Relaxed);
on_task(task); on_task(task);

View File

@ -1,45 +1,39 @@
use core::cell::Cell; use core::cell::Cell;
use core::cmp::min; use core::cmp::min;
use core::ptr;
use core::ptr::NonNull;
use atomic_polyfill::Ordering; use atomic_polyfill::Ordering;
use embassy_time::Instant; use embassy_time::Instant;
use super::{TaskHeader, STATE_TIMER_QUEUED}; use super::{TaskRef, STATE_TIMER_QUEUED};
pub(crate) struct TimerQueueItem { pub(crate) struct TimerQueueItem {
next: Cell<*mut TaskHeader>, next: Cell<Option<TaskRef>>,
} }
impl TimerQueueItem { impl TimerQueueItem {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self { next: Cell::new(None) }
next: Cell::new(ptr::null_mut()),
}
} }
} }
pub(crate) struct TimerQueue { pub(crate) struct TimerQueue {
head: Cell<*mut TaskHeader>, head: Cell<Option<TaskRef>>,
} }
impl TimerQueue { impl TimerQueue {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self { head: Cell::new(None) }
head: Cell::new(ptr::null_mut()),
}
} }
pub(crate) unsafe fn update(&self, p: NonNull<TaskHeader>) { pub(crate) unsafe fn update(&self, p: TaskRef) {
let task = p.as_ref(); let task = p.header();
if task.expires_at.get() != Instant::MAX { if task.expires_at.get() != Instant::MAX {
let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
let is_new = old_state & STATE_TIMER_QUEUED == 0; let is_new = old_state & STATE_TIMER_QUEUED == 0;
if is_new { if is_new {
task.timer_queue_item.next.set(self.head.get()); task.timer_queue_item.next.set(self.head.get());
self.head.set(p.as_ptr()); self.head.set(Some(p));
} }
} }
} }
@ -47,7 +41,7 @@ impl TimerQueue {
pub(crate) unsafe fn next_expiration(&self) -> Instant { pub(crate) unsafe fn next_expiration(&self) -> Instant {
let mut res = Instant::MAX; let mut res = Instant::MAX;
self.retain(|p| { self.retain(|p| {
let task = p.as_ref(); let task = p.header();
let expires = task.expires_at.get(); let expires = task.expires_at.get();
res = min(res, expires); res = min(res, expires);
expires != Instant::MAX expires != Instant::MAX
@ -55,9 +49,9 @@ impl TimerQueue {
res res
} }
pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull<TaskHeader>)) { pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(TaskRef)) {
self.retain(|p| { self.retain(|p| {
let task = p.as_ref(); let task = p.header();
if task.expires_at.get() <= now { if task.expires_at.get() <= now {
on_task(p); on_task(p);
false false
@ -67,11 +61,10 @@ impl TimerQueue {
}); });
} }
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull<TaskHeader>) -> bool) { pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
let mut prev = &self.head; let mut prev = &self.head;
while !prev.get().is_null() { while let Some(p) = prev.get() {
let p = NonNull::new_unchecked(prev.get()); let task = p.header();
let task = &*p.as_ptr();
if f(p) { if f(p) {
// Skip to next // Skip to next
prev = &task.timer_queue_item.next; prev = &task.timer_queue_item.next;

View File

@ -25,9 +25,3 @@ impl<T> UninitCell<T> {
ptr::drop_in_place(self.as_mut_ptr()) ptr::drop_in_place(self.as_mut_ptr())
} }
} }
impl<T: Copy> UninitCell<T> {
pub unsafe fn read(&self) -> T {
ptr::read(self.as_mut_ptr())
}
}

View File

@ -1,8 +1,7 @@
use core::mem; use core::mem;
use core::ptr::NonNull;
use core::task::{RawWaker, RawWakerVTable, Waker}; use core::task::{RawWaker, RawWakerVTable, Waker};
use super::{wake_task, TaskHeader}; use super::{wake_task, TaskHeader, TaskRef};
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
@ -11,14 +10,14 @@ unsafe fn clone(p: *const ()) -> RawWaker {
} }
unsafe fn wake(p: *const ()) { unsafe fn wake(p: *const ()) {
wake_task(NonNull::new_unchecked(p as *mut TaskHeader)) wake_task(TaskRef::from_ptr(p as *const TaskHeader))
} }
unsafe fn drop(_: *const ()) { unsafe fn drop(_: *const ()) {
// nop // nop
} }
pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker { pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE))
} }
@ -33,7 +32,7 @@ pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker {
/// # Panics /// # Panics
/// ///
/// Panics if the waker is not created by the Embassy executor. /// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> { pub fn task_from_waker(waker: &Waker) -> TaskRef {
// safety: OK because WakerHack has the same layout as Waker. // safety: OK because WakerHack has the same layout as Waker.
// This is not really guaranteed because the structs are `repr(Rust)`, it is // This is not really guaranteed because the structs are `repr(Rust)`, it is
// indeed the case in the current implementation. // indeed the case in the current implementation.
@ -43,8 +42,8 @@ pub fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> {
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
} }
// safety: we never create a waker with a null data pointer. // safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) } unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) }
} }
struct WakerHack { struct WakerHack {

View File

@ -1,7 +1,6 @@
use core::future::poll_fn; use core::future::poll_fn;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::mem; use core::mem;
use core::ptr::NonNull;
use core::task::Poll; use core::task::Poll;
use super::raw; use super::raw;
@ -22,12 +21,12 @@ use super::raw;
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
pub struct SpawnToken<S> { pub struct SpawnToken<S> {
raw_task: Option<NonNull<raw::TaskHeader>>, raw_task: Option<raw::TaskRef>,
phantom: PhantomData<*mut S>, phantom: PhantomData<*mut S>,
} }
impl<S> SpawnToken<S> { impl<S> SpawnToken<S> {
pub(crate) unsafe fn new(raw_task: NonNull<raw::TaskHeader>) -> Self { pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
Self { Self {
raw_task: Some(raw_task), raw_task: Some(raw_task),
phantom: PhantomData, phantom: PhantomData,
@ -90,10 +89,10 @@ impl Spawner {
/// ///
/// Panics if the current executor is not an Embassy executor. /// Panics if the current executor is not an Embassy executor.
pub async fn for_current_executor() -> Self { pub async fn for_current_executor() -> Self {
poll_fn(|cx| unsafe { poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker()); let task = raw::task_from_waker(cx.waker());
let executor = (*task.as_ptr()).executor.get(); let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
Poll::Ready(Self::new(&*executor)) Poll::Ready(Self::new(executor))
}) })
.await .await
} }
@ -166,10 +165,10 @@ impl SendSpawner {
/// ///
/// Panics if the current executor is not an Embassy executor. /// Panics if the current executor is not an Embassy executor.
pub async fn for_current_executor() -> Self { pub async fn for_current_executor() -> Self {
poll_fn(|cx| unsafe { poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker()); let task = raw::task_from_waker(cx.waker());
let executor = (*task.as_ptr()).executor.get(); let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
Poll::Ready(Self::new(&*executor)) Poll::Ready(Self::new(executor))
}) })
.await .await
} }

View File

@ -24,6 +24,7 @@ pub fn yield_now() -> impl Future<Output = ()> {
YieldNowFuture { yielded: false } YieldNowFuture { yielded: false }
} }
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct YieldNowFuture { struct YieldNowFuture {
yielded: bool, yielded: bool,
} }

View File

@ -1,10 +1,12 @@
#[macro_export] #[macro_export]
macro_rules! peripherals { macro_rules! peripherals {
($($(#[$cfg:meta])? $name:ident),*$(,)?) => { ($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
/// Types for the peripheral singletons.
pub mod peripherals { pub mod peripherals {
$( $(
$(#[$cfg])? $(#[$cfg])?
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[doc = concat!(stringify!($name), " peripheral")]
pub struct $name { _private: () } pub struct $name { _private: () }
$(#[$cfg])? $(#[$cfg])?
@ -25,9 +27,13 @@ macro_rules! peripherals {
)* )*
} }
/// Struct containing all the peripheral singletons.
///
/// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`].
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct Peripherals { pub struct Peripherals {
$( $(
#[doc = concat!(stringify!($name), " peripheral")]
$(#[$cfg])? $(#[$cfg])?
pub $name: peripherals::$name, pub $name: peripherals::$name,
)* )*

View File

@ -3,16 +3,17 @@ use core::ops::{Deref, DerefMut};
/// An exclusive reference to a peripheral. /// An exclusive reference to a peripheral.
/// ///
/// This is functionally the same as a `&'a mut T`. The reason for having a /// This is functionally the same as a `&'a mut T`. There's a few advantages in having
/// dedicated struct is memory efficiency: /// a dedicated struct instead:
/// ///
/// Peripheral singletons are typically either zero-sized (for concrete peripherals /// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete
/// like `PA9` or `Spi4`) or very small (for example `AnyPin` which is 1 byte). /// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte).
/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. /// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized.
/// PeripheralRef stores a copy of `T` instead, so it's the same size. /// PeripheralRef stores a copy of `T` instead, so it's the same size.
/// /// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`,
/// but it is the size of `T` not the size /// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic
/// of a pointer. This is useful if T is a zero sized type. /// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes
/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization.
pub struct PeripheralRef<'a, T> { pub struct PeripheralRef<'a, T> {
inner: T, inner: T,
_lifetime: PhantomData<&'a mut T>, _lifetime: PhantomData<&'a mut T>,

View File

@ -260,10 +260,10 @@ impl From<embassy_stm32::spi::Error> for RadioError {
impl<'d, RS> Timings for SubGhzRadio<'d, RS> { impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
fn get_rx_window_offset_ms(&self) -> i32 { fn get_rx_window_offset_ms(&self) -> i32 {
-500 -3
} }
fn get_rx_window_duration_ms(&self) -> u32 { fn get_rx_window_duration_ms(&self) -> u32 {
3000 1003
} }
} }

View File

@ -55,10 +55,10 @@ where
BUS: Error + Format + 'static, BUS: Error + Format + 'static,
{ {
fn get_rx_window_offset_ms(&self) -> i32 { fn get_rx_window_offset_ms(&self) -> i32 {
-500 -50
} }
fn get_rx_window_duration_ms(&self) -> u32 { fn get_rx_window_duration_ms(&self) -> u32 {
800 1050
} }
} }

View File

@ -70,10 +70,10 @@ where
RFS: RadioSwitch + 'static, RFS: RadioSwitch + 'static,
{ {
fn get_rx_window_offset_ms(&self) -> i32 { fn get_rx_window_offset_ms(&self) -> i32 {
-500 -3
} }
fn get_rx_window_duration_ms(&self) -> u32 { fn get_rx_window_duration_ms(&self) -> u32 {
800 1003
} }
} }

View File

@ -6,7 +6,10 @@ pub fn run(name: syn::Ident) -> Result<TokenStream, TokenStream> {
let name_interrupt = format_ident!("{}", name); let name_interrupt = format_ident!("{}", name);
let name_handler = format!("__EMBASSY_{}_HANDLER", name); let name_handler = format!("__EMBASSY_{}_HANDLER", name);
let doc = format!("{} interrupt singleton.", name);
let result = quote! { let result = quote! {
#[doc = #doc]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct #name_interrupt(()); pub struct #name_interrupt(());
unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt { unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt {

View File

@ -3,6 +3,12 @@ name = "embassy-net-driver-channel"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/"
features = ["defmt"]
target = "thumbv7em-none-eabi"
[dependencies] [dependencies]
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }

View File

@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/"
features = ["defmt"] features = ["defmt"]
target = "thumbv7em-none-eabi" target = "thumbv7em-none-eabi"

View File

@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/"
features = ["defmt", "tcp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"] features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"]
target = "thumbv7em-none-eabi" target = "thumbv7em-none-eabi"
[features] [features]
@ -22,7 +22,7 @@ unstable-traits = []
udp = ["smoltcp/socket-udp"] udp = ["smoltcp/socket-udp"]
tcp = ["smoltcp/socket-tcp"] tcp = ["smoltcp/socket-tcp"]
dns = ["smoltcp/socket-dns"] dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"]
dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"] dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"]
proto-ipv6 = ["smoltcp/proto-ipv6"] proto-ipv6 = ["smoltcp/proto-ipv6"]
medium-ethernet = ["smoltcp/medium-ethernet"] medium-ethernet = ["smoltcp/medium-ethernet"]
@ -33,7 +33,14 @@ medium-ip = ["smoltcp/medium-ip"]
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
smoltcp = { version = "0.9.0", default-features = false, features = [
"proto-ipv4",
"socket",
"async",
]}
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" }
embassy-time = { version = "0.1.0", path = "../embassy-time" } embassy-time = { version = "0.1.0", path = "../embassy-time" }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embedded-io = { version = "0.4.0", optional = true } embedded-io = { version = "0.4.0", optional = true }
@ -45,16 +52,5 @@ generic-array = { version = "0.14.4", default-features = false }
stable_deref_trait = { version = "1.2.0", default-features = false } stable_deref_trait = { version = "1.2.0", default-features = false }
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
atomic-pool = "1.0" atomic-pool = "1.0"
embedded-nal-async = { version = "0.3.0", optional = true } embedded-nal-async = { version = "0.4.0", optional = true }
atomic-polyfill = { version = "1.0" } atomic-polyfill = { version = "1.0" }
[dependencies.smoltcp]
version = "0.8.0"
git = "https://github.com/smoltcp-rs/smoltcp"
rev = "5740b765749b95c18aace5de8dc21cab75ba33d4"
default-features = false
features = [
"proto-ipv4",
"socket",
"async",
]

View File

@ -57,13 +57,13 @@ where
), ),
}; };
smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4); smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4);
#[cfg(feature = "proto-ipv6")]
{
smolcaps.checksum.ipv6 = convert(caps.checksum.ipv6);
}
smolcaps.checksum.tcp = convert(caps.checksum.tcp); smolcaps.checksum.tcp = convert(caps.checksum.tcp);
smolcaps.checksum.udp = convert(caps.checksum.udp); smolcaps.checksum.udp = convert(caps.checksum.udp);
smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4);
#[cfg(feature = "proto-ipv6")]
{
smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6);
}
smolcaps smolcaps
} }

97
embassy-net/src/dns.rs Normal file
View File

@ -0,0 +1,97 @@
//! DNS socket with async support.
use heapless::Vec;
pub use smoltcp::socket::dns::{DnsQuery, Socket};
pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError};
pub use smoltcp::wire::{DnsQueryType, IpAddress};
use crate::{Driver, Stack};
/// Errors returned by DnsSocket.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Invalid name
InvalidName,
/// Name too long
NameTooLong,
/// Name lookup failed
Failed,
}
impl From<GetQueryResultError> for Error {
fn from(_: GetQueryResultError) -> Self {
Self::Failed
}
}
impl From<StartQueryError> for Error {
fn from(e: StartQueryError) -> Self {
match e {
StartQueryError::NoFreeSlot => Self::Failed,
StartQueryError::InvalidName => Self::InvalidName,
StartQueryError::NameTooLong => Self::NameTooLong,
}
}
}
/// Async socket for making DNS queries.
pub struct DnsSocket<'a, D>
where
D: Driver + 'static,
{
stack: &'a Stack<D>,
}
impl<'a, D> DnsSocket<'a, D>
where
D: Driver + 'static,
{
/// Create a new DNS socket using the provided stack.
///
/// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated.
pub fn new(stack: &'a Stack<D>) -> Self {
Self { stack }
}
/// Make a query for a given name and return the corresponding IP addresses.
pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result<Vec<IpAddress, 1>, Error> {
self.stack.dns_query(name, qtype).await
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
where
D: Driver + 'static,
{
type Error = Error;
async fn get_host_by_name(
&self,
host: &str,
addr_type: embedded_nal_async::AddrType,
) -> Result<embedded_nal_async::IpAddr, Self::Error> {
use embedded_nal_async::{AddrType, IpAddr};
let qtype = match addr_type {
AddrType::IPv6 => DnsQueryType::Aaaa,
_ => DnsQueryType::A,
};
let addrs = self.query(host, qtype).await?;
if let Some(first) = addrs.get(0) {
Ok(match first {
IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()),
#[cfg(feature = "proto-ipv6")]
IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()),
})
} else {
Err(Error::Failed)
}
}
async fn get_host_by_address(
&self,
_addr: embedded_nal_async::IpAddr,
) -> Result<heapless::String<256>, Self::Error> {
todo!()
}
}

View File

@ -8,7 +8,11 @@
// This mod MUST go first, so that the others see its macros. // This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt; pub(crate) mod fmt;
pub mod device; pub use embassy_net_driver as driver;
mod device;
#[cfg(feature = "dns")]
pub mod dns;
#[cfg(feature = "tcp")] #[cfg(feature = "tcp")]
pub mod tcp; pub mod tcp;
#[cfg(feature = "udp")] #[cfg(feature = "udp")]
@ -44,15 +48,23 @@ use crate::device::DriverAdapter;
const LOCAL_PORT_MIN: u16 = 1025; const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535; const LOCAL_PORT_MAX: u16 = 65535;
#[cfg(feature = "dns")]
const MAX_QUERIES: usize = 4;
pub struct StackResources<const SOCK: usize> { pub struct StackResources<const SOCK: usize> {
sockets: [SocketStorage<'static>; SOCK], sockets: [SocketStorage<'static>; SOCK],
#[cfg(feature = "dns")]
queries: [Option<dns::DnsQuery>; MAX_QUERIES],
} }
impl<const SOCK: usize> StackResources<SOCK> { impl<const SOCK: usize> StackResources<SOCK> {
pub fn new() -> Self { pub fn new() -> Self {
#[cfg(feature = "dns")]
const INIT: Option<dns::DnsQuery> = None;
Self { Self {
sockets: [SocketStorage::EMPTY; SOCK], sockets: [SocketStorage::EMPTY; SOCK],
#[cfg(feature = "dns")]
queries: [INIT; MAX_QUERIES],
} }
} }
} }
@ -105,6 +117,10 @@ struct Inner<D: Driver> {
config: Option<StaticConfig>, config: Option<StaticConfig>,
#[cfg(feature = "dhcpv4")] #[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>, dhcp_socket: Option<SocketHandle>,
#[cfg(feature = "dns")]
dns_socket: SocketHandle,
#[cfg(feature = "dns")]
dns_waker: WakerRegistration,
} }
pub(crate) struct SocketStack { pub(crate) struct SocketStack {
@ -143,13 +159,6 @@ impl<D: Driver + 'static> Stack<D> {
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
let mut inner = Inner {
device,
link_up: false,
config: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
};
let mut socket = SocketStack { let mut socket = SocketStack {
sockets, sockets,
iface, iface,
@ -157,8 +166,25 @@ impl<D: Driver + 'static> Stack<D> {
next_local_port, next_local_port,
}; };
let mut inner = Inner {
device,
link_up: false,
config: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
#[cfg(feature = "dns")]
dns_socket: socket.sockets.add(dns::Socket::new(
&[],
managed::ManagedSlice::Borrowed(&mut resources.queries),
)),
#[cfg(feature = "dns")]
dns_waker: WakerRegistration::new(),
};
match config { match config {
Config::Static(config) => inner.apply_config(&mut socket, config), Config::Static(config) => {
inner.apply_config(&mut socket, config);
}
#[cfg(feature = "dhcpv4")] #[cfg(feature = "dhcpv4")]
Config::Dhcp(config) => { Config::Dhcp(config) => {
let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new(); let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new();
@ -206,6 +232,76 @@ impl<D: Driver + 'static> Stack<D> {
.await; .await;
unreachable!() unreachable!()
} }
/// Make a query for a given name and return the corresponding IP addresses.
#[cfg(feature = "dns")]
pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result<Vec<IpAddress, 1>, dns::Error> {
// For A and AAAA queries we try detect whether `name` is just an IP address
match qtype {
dns::DnsQueryType::A => {
if let Ok(ip) = name.parse().map(IpAddress::Ipv4) {
return Ok([ip].into_iter().collect());
}
}
#[cfg(feature = "proto-ipv6")]
dns::DnsQueryType::Aaaa => {
if let Ok(ip) = name.parse().map(IpAddress::Ipv6) {
return Ok([ip].into_iter().collect());
}
}
_ => {}
}
let query = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.start_query(s.iface.context(), name, qtype) {
Ok(handle) => Poll::Ready(Ok(handle)),
Err(dns::StartQueryError::NoFreeSlot) => {
i.dns_waker.register(cx.waker());
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
})
})
.await?;
use embassy_hal_common::drop::OnDrop;
let drop = OnDrop::new(|| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
socket.cancel_query(query);
s.waker.wake();
i.dns_waker.wake();
})
});
let res = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.get_query_result(query) {
Ok(addrs) => {
i.dns_waker.wake();
Poll::Ready(Ok(addrs))
}
Err(dns::GetQueryResultError::Pending) => {
socket.register_query_waker(query, cx.waker());
Poll::Pending
}
Err(e) => {
i.dns_waker.wake();
Poll::Ready(Err(e.into()))
}
}
})
})
.await;
drop.defuse();
res
}
} }
impl SocketStack { impl SocketStack {
@ -247,6 +343,13 @@ impl<D: Driver + 'static> Inner<D> {
debug!(" DNS server {}: {}", i, s); debug!(" DNS server {}: {}", i, s);
} }
#[cfg(feature = "dns")]
{
let socket = s.sockets.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket);
let servers: Vec<IpAddress, 3> = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect();
socket.update_servers(&servers[..]);
}
self.config = Some(config) self.config = Some(config)
} }
@ -322,6 +425,7 @@ impl<D: Driver + 'static> Inner<D> {
//if old_link_up || self.link_up { //if old_link_up || self.link_up {
// self.poll_configurator(timestamp) // self.poll_configurator(timestamp)
//} //}
//
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
let t = Timer::at(instant_from_smoltcp(poll_at)); let t = Timer::at(instant_from_smoltcp(poll_at));

View File

@ -63,6 +63,10 @@ impl<'a> TcpWriter<'a> {
pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
self.io.write(buf).await self.io.write(buf).await
} }
pub async fn flush(&mut self) -> Result<(), Error> {
self.io.flush().await
}
} }
impl<'a> TcpSocket<'a> { impl<'a> TcpSocket<'a> {
@ -146,6 +150,10 @@ impl<'a> TcpSocket<'a> {
self.io.write(buf).await self.io.write(buf).await
} }
pub async fn flush(&mut self) -> Result<(), Error> {
self.io.flush().await
}
pub fn set_timeout(&mut self, duration: Option<Duration>) { pub fn set_timeout(&mut self, duration: Option<Duration>) {
self.io.with_mut(|s, _| s.set_timeout(duration)) self.io.with_mut(|s, _| s.set_timeout(duration))
} }
@ -254,10 +262,19 @@ impl<'d> TcpIo<'d> {
.await .await
} }
#[allow(unused)]
async fn flush(&mut self) -> Result<(), Error> { async fn flush(&mut self) -> Result<(), Error> {
poll_fn(move |_| { poll_fn(move |cx| {
Poll::Ready(Ok(())) // TODO: Is there a better implementation for this? self.with_mut(|s, _| {
// If there are outstanding send operations, register for wake up and wait
// smoltcp issues wake-ups when octets are dequeued from the send buffer
if s.send_queue() > 0 {
s.register_send_waker(cx.waker());
Poll::Pending
// No outstanding sends, socket is flushed
} else {
Poll::Ready(Ok(()))
}
})
}) })
.await .await
} }
@ -389,7 +406,7 @@ pub mod client {
fn new<D: Driver>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> { fn new<D: Driver>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?;
Ok(Self { Ok(Self {
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().0, &mut bufs.as_mut().1) }, socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) },
state, state,
bufs, bufs,
}) })

View File

@ -34,22 +34,30 @@ unstable-pac = []
# Implement embedded-hal-async traits if `nightly` is set as well. # Implement embedded-hal-async traits if `nightly` is set as well.
unstable-traits = ["embedded-hal-1"] unstable-traits = ["embedded-hal-1"]
nrf52805 = ["nrf52805-pac", "_ppi"] nrf52805 = ["nrf52805-pac", "_nrf52"]
nrf52810 = ["nrf52810-pac", "_ppi"] nrf52810 = ["nrf52810-pac", "_nrf52"]
nrf52811 = ["nrf52811-pac", "_ppi"] nrf52811 = ["nrf52811-pac", "_nrf52"]
nrf52820 = ["nrf52820-pac", "_ppi"] nrf52820 = ["nrf52820-pac", "_nrf52"]
nrf52832 = ["nrf52832-pac", "_ppi"] nrf52832 = ["nrf52832-pac", "_nrf52"]
nrf52833 = ["nrf52833-pac", "_ppi", "_gpio-p1"] nrf52833 = ["nrf52833-pac", "_nrf52", "_gpio-p1"]
nrf52840 = ["nrf52840-pac", "_ppi", "_gpio-p1"] nrf52840 = ["nrf52840-pac", "_nrf52", "_gpio-p1"]
nrf5340-app-s = ["_nrf5340-app"] nrf5340-app-s = ["_nrf5340-app", "_s"]
nrf5340-app-ns = ["_nrf5340-app"] nrf5340-app-ns = ["_nrf5340-app", "_ns"]
nrf5340-net = ["_nrf5340-net"] nrf5340-net = ["_nrf5340-net"]
nrf9160-s = ["_nrf9160"] nrf9160-s = ["_nrf9160", "_s"]
nrf9160-ns = ["_nrf9160"] nrf9160-ns = ["_nrf9160", "_ns"]
gpiote = [] gpiote = []
time-driver-rtc1 = ["_time-driver"] time-driver-rtc1 = ["_time-driver"]
# Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53)
nfc-pins-as-gpio = []
# Allow using the RST pin as a regular GPIO pin.
# nrf52805, nrf52810, nrf52811, nrf52832: P0_21
# nrf52820, nrf52833, nrf52840: P0_18
reset-pin-as-gpio = []
# Features starting with `_` are for internal use only. They're not intended # Features starting with `_` are for internal use only. They're not intended
# to be enabled by other crates, and are not covered by semver guarantees. # to be enabled by other crates, and are not covered by semver guarantees.
@ -57,9 +65,14 @@ _nrf5340-app = ["_nrf5340", "nrf5340-app-pac"]
_nrf5340-net = ["_nrf5340", "nrf5340-net-pac"] _nrf5340-net = ["_nrf5340", "nrf5340-net-pac"]
_nrf5340 = ["_gpio-p1", "_dppi"] _nrf5340 = ["_gpio-p1", "_dppi"]
_nrf9160 = ["nrf9160-pac", "_dppi"] _nrf9160 = ["nrf9160-pac", "_dppi"]
_nrf52 = ["_ppi"]
_time-driver = ["dep:embassy-time", "embassy-time?/tick-hz-32_768"] _time-driver = ["dep:embassy-time", "embassy-time?/tick-hz-32_768"]
# trustzone state.
_s = []
_ns = []
_ppi = [] _ppi = []
_dppi = [] _dppi = []
_gpio-p1 = [] _gpio-p1 = []

58
embassy-nrf/README.md Normal file
View File

@ -0,0 +1,58 @@
# Embassy nRF HAL
HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed.
The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs
for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to
complete operations in low power mod and handling interrupts, so that applications can focus on more important matters.
## EasyDMA considerations
On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting
with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI).
However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust
slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen:
```no_run
// As we pass a slice to the function whose contents will not ever change,
// the compiler writes it into the flash and thus the pointer to it will
// reference static memory. Since EasyDMA requires slices to reside in RAM,
// this function call will fail.
let result = spim.write_from_ram(&[1, 2, 3]);
assert_eq!(result, Err(Error::BufferNotInRAM));
// The data is still static and located in flash. However, since we are assigning
// it to a variable, the compiler will load it into memory. Passing a reference to the
// variable will yield a pointer that references dynamic memory, thus making EasyDMA happy.
// This function call succeeds.
let data = [1, 2, 3];
let result = spim.write_from_ram(&data);
assert!(result.is_ok());
```
Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions:
- Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM.
- Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission.
Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will
fail and notify you, or the more convenient versions without the suffix which are potentially a little bit
more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage
as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840).
Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as
mutable slices always reside in RAM.
## Minimum supported Rust version (MSRV)
Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -1,4 +1,4 @@
//! Async buffered UART //! Async buffered UART driver.
//! //!
//! WARNING!!! The functionality provided here is intended to be used only //! WARNING!!! The functionality provided here is intended to be used only
//! in situations where hardware flow control are available i.e. CTS and RTS. //! in situations where hardware flow control are available i.e. CTS and RTS.
@ -69,7 +69,7 @@ struct StateInner<'d, U: UarteInstance, T: TimerInstance> {
tx_waker: WakerRegistration, tx_waker: WakerRegistration,
} }
/// Interface to a UARTE instance /// Buffered UARTE driver.
pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> {
inner: RefCell<PeripheralMutex<'d, StateInner<'d, U, T>>>, inner: RefCell<PeripheralMutex<'d, StateInner<'d, U, T>>>,
} }
@ -199,6 +199,9 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
}); });
} }
/// Split the UART in reader and writer parts.
///
/// This allows reading and writing concurrently from independent tasks.
pub fn split<'u>(&'u mut self) -> (BufferedUarteRx<'u, 'd, U, T>, BufferedUarteTx<'u, 'd, U, T>) { pub fn split<'u>(&'u mut self) -> (BufferedUarteRx<'u, 'd, U, T>, BufferedUarteTx<'u, 'd, U, T>) {
(BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self })
} }
@ -320,10 +323,12 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
} }
} }
/// Reader part of the buffered UARTE driver.
pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> { pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> {
inner: &'u BufferedUarte<'d, U, T>, inner: &'u BufferedUarte<'d, U, T>,
} }
/// Writer part of the buffered UARTE driver.
pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> {
inner: &'u BufferedUarte<'d, U, T>, inner: &'u BufferedUarte<'d, U, T>,
} }

View File

@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256;
pub const FLASH_SIZE: usize = 192 * 1024; pub const FLASH_SIZE: usize = 192 * 1024;
pub const RESET_PIN: u32 = 21;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// RTC // RTC
RTC0, RTC0,
@ -108,6 +110,7 @@ embassy_hal_common::peripherals! {
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
#[cfg(feature="reset-pin-as-gpio")]
P0_21, P0_21,
P0_22, P0_22,
P0_23, P0_23,
@ -162,6 +165,7 @@ impl_pin!(P0_17, 0, 17);
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_21, 0, 21); impl_pin!(P0_21, 0, 21);
impl_pin!(P0_22, 0, 22); impl_pin!(P0_22, 0, 22);
impl_pin!(P0_23, 0, 23); impl_pin!(P0_23, 0, 23);

View File

@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256;
pub const FLASH_SIZE: usize = 192 * 1024; pub const FLASH_SIZE: usize = 192 * 1024;
pub const RESET_PIN: u32 = 21;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// RTC // RTC
RTC0, RTC0,
@ -111,6 +113,7 @@ embassy_hal_common::peripherals! {
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
#[cfg(feature="reset-pin-as-gpio")]
P0_21, P0_21,
P0_22, P0_22,
P0_23, P0_23,
@ -170,6 +173,7 @@ impl_pin!(P0_17, 0, 17);
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_21, 0, 21); impl_pin!(P0_21, 0, 21);
impl_pin!(P0_22, 0, 22); impl_pin!(P0_22, 0, 22);
impl_pin!(P0_23, 0, 23); impl_pin!(P0_23, 0, 23);

View File

@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256;
pub const FLASH_SIZE: usize = 192 * 1024; pub const FLASH_SIZE: usize = 192 * 1024;
pub const RESET_PIN: u32 = 21;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// RTC // RTC
RTC0, RTC0,
@ -111,6 +113,7 @@ embassy_hal_common::peripherals! {
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
#[cfg(feature="reset-pin-as-gpio")]
P0_21, P0_21,
P0_22, P0_22,
P0_23, P0_23,
@ -172,6 +175,7 @@ impl_pin!(P0_17, 0, 17);
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_21, 0, 21); impl_pin!(P0_21, 0, 21);
impl_pin!(P0_22, 0, 22); impl_pin!(P0_22, 0, 22);
impl_pin!(P0_23, 0, 23); impl_pin!(P0_23, 0, 23);

View File

@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512;
pub const FLASH_SIZE: usize = 256 * 1024; pub const FLASH_SIZE: usize = 256 * 1024;
pub const RESET_PIN: u32 = 18;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// USB // USB
USBD, USBD,
@ -106,6 +108,7 @@ embassy_hal_common::peripherals! {
P0_15, P0_15,
P0_16, P0_16,
P0_17, P0_17,
#[cfg(feature="reset-pin-as-gpio")]
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
@ -168,6 +171,7 @@ impl_pin!(P0_14, 0, 14);
impl_pin!(P0_15, 0, 15); impl_pin!(P0_15, 0, 15);
impl_pin!(P0_16, 0, 16); impl_pin!(P0_16, 0, 16);
impl_pin!(P0_17, 0, 17); impl_pin!(P0_17, 0, 17);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);

View File

@ -10,6 +10,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 255;
// nrf52832xxAB = 256kb // nrf52832xxAB = 256kb
pub const FLASH_SIZE: usize = 512 * 1024; pub const FLASH_SIZE: usize = 512 * 1024;
pub const RESET_PIN: u32 = 21;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// RTC // RTC
RTC0, RTC0,
@ -109,7 +111,9 @@ embassy_hal_common::peripherals! {
P0_06, P0_06,
P0_07, P0_07,
P0_08, P0_08,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_09, P0_09,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_10, P0_10,
P0_11, P0_11,
P0_12, P0_12,
@ -121,6 +125,7 @@ embassy_hal_common::peripherals! {
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
#[cfg(feature="reset-pin-as-gpio")]
P0_21, P0_21,
P0_22, P0_22,
P0_23, P0_23,
@ -178,7 +183,9 @@ impl_pin!(P0_05, 0, 5);
impl_pin!(P0_06, 0, 6); impl_pin!(P0_06, 0, 6);
impl_pin!(P0_07, 0, 7); impl_pin!(P0_07, 0, 7);
impl_pin!(P0_08, 0, 8); impl_pin!(P0_08, 0, 8);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_09, 0, 9); impl_pin!(P0_09, 0, 9);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_10, 0, 10); impl_pin!(P0_10, 0, 10);
impl_pin!(P0_11, 0, 11); impl_pin!(P0_11, 0, 11);
impl_pin!(P0_12, 0, 12); impl_pin!(P0_12, 0, 12);
@ -190,6 +197,7 @@ impl_pin!(P0_17, 0, 17);
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_21, 0, 21); impl_pin!(P0_21, 0, 21);
impl_pin!(P0_22, 0, 22); impl_pin!(P0_22, 0, 22);
impl_pin!(P0_23, 0, 23); impl_pin!(P0_23, 0, 23);

View File

@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512;
pub const FLASH_SIZE: usize = 512 * 1024; pub const FLASH_SIZE: usize = 512 * 1024;
pub const RESET_PIN: u32 = 18;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// USB // USB
USBD, USBD,
@ -111,7 +113,9 @@ embassy_hal_common::peripherals! {
P0_06, P0_06,
P0_07, P0_07,
P0_08, P0_08,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_09, P0_09,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_10, P0_10,
P0_11, P0_11,
P0_12, P0_12,
@ -120,6 +124,7 @@ embassy_hal_common::peripherals! {
P0_15, P0_15,
P0_16, P0_16,
P0_17, P0_17,
#[cfg(feature="reset-pin-as-gpio")]
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
@ -207,7 +212,9 @@ impl_pin!(P0_05, 0, 5);
impl_pin!(P0_06, 0, 6); impl_pin!(P0_06, 0, 6);
impl_pin!(P0_07, 0, 7); impl_pin!(P0_07, 0, 7);
impl_pin!(P0_08, 0, 8); impl_pin!(P0_08, 0, 8);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_09, 0, 9); impl_pin!(P0_09, 0, 9);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_10, 0, 10); impl_pin!(P0_10, 0, 10);
impl_pin!(P0_11, 0, 11); impl_pin!(P0_11, 0, 11);
impl_pin!(P0_12, 0, 12); impl_pin!(P0_12, 0, 12);
@ -216,6 +223,7 @@ impl_pin!(P0_14, 0, 14);
impl_pin!(P0_15, 0, 15); impl_pin!(P0_15, 0, 15);
impl_pin!(P0_16, 0, 16); impl_pin!(P0_16, 0, 16);
impl_pin!(P0_17, 0, 17); impl_pin!(P0_17, 0, 17);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);

View File

@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512;
pub const FLASH_SIZE: usize = 1024 * 1024; pub const FLASH_SIZE: usize = 1024 * 1024;
pub const RESET_PIN: u32 = 18;
embassy_hal_common::peripherals! { embassy_hal_common::peripherals! {
// USB // USB
USBD, USBD,
@ -117,7 +119,9 @@ embassy_hal_common::peripherals! {
P0_06, P0_06,
P0_07, P0_07,
P0_08, P0_08,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_09, P0_09,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_10, P0_10,
P0_11, P0_11,
P0_12, P0_12,
@ -126,6 +130,7 @@ embassy_hal_common::peripherals! {
P0_15, P0_15,
P0_16, P0_16,
P0_17, P0_17,
#[cfg(feature="reset-pin-as-gpio")]
P0_18, P0_18,
P0_19, P0_19,
P0_20, P0_20,
@ -212,7 +217,9 @@ impl_pin!(P0_05, 0, 5);
impl_pin!(P0_06, 0, 6); impl_pin!(P0_06, 0, 6);
impl_pin!(P0_07, 0, 7); impl_pin!(P0_07, 0, 7);
impl_pin!(P0_08, 0, 8); impl_pin!(P0_08, 0, 8);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_09, 0, 9); impl_pin!(P0_09, 0, 9);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_10, 0, 10); impl_pin!(P0_10, 0, 10);
impl_pin!(P0_11, 0, 11); impl_pin!(P0_11, 0, 11);
impl_pin!(P0_12, 0, 12); impl_pin!(P0_12, 0, 12);
@ -221,6 +228,7 @@ impl_pin!(P0_14, 0, 14);
impl_pin!(P0_15, 0, 15); impl_pin!(P0_15, 0, 15);
impl_pin!(P0_16, 0, 16); impl_pin!(P0_16, 0, 16);
impl_pin!(P0_17, 0, 17); impl_pin!(P0_17, 0, 17);
#[cfg(feature = "reset-pin-as-gpio")]
impl_pin!(P0_18, 0, 18); impl_pin!(P0_18, 0, 18);
impl_pin!(P0_19, 0, 19); impl_pin!(P0_19, 0, 19);
impl_pin!(P0_20, 0, 20); impl_pin!(P0_20, 0, 20);

View File

@ -1,3 +1,4 @@
/// Peripheral Access Crate
#[allow(unused_imports)] #[allow(unused_imports)]
#[rustfmt::skip] #[rustfmt::skip]
pub mod pac { pub mod pac {
@ -230,10 +231,10 @@ embassy_hal_common::peripherals! {
NVMC, NVMC,
// UARTE, TWI & SPI // UARTE, TWI & SPI
UARTETWISPI0, SERIAL0,
UARTETWISPI1, SERIAL1,
UARTETWISPI2, SERIAL2,
UARTETWISPI3, SERIAL3,
// SAADC // SAADC
SAADC, SAADC,
@ -303,7 +304,9 @@ embassy_hal_common::peripherals! {
// GPIO port 0 // GPIO port 0
P0_00, P0_00,
P0_01, P0_01,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_02, P0_02,
#[cfg(feature = "nfc-pins-as-gpio")]
P0_03, P0_03,
P0_04, P0_04,
P0_05, P0_05,
@ -356,30 +359,30 @@ embassy_hal_common::peripherals! {
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
impl_usb!(USBD, USBD, USBD); impl_usb!(USBD, USBD, USBD);
impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); impl_uarte!(SERIAL0, UARTE0, SERIAL0);
impl_uarte!(UARTETWISPI1, UARTE1, SERIAL1); impl_uarte!(SERIAL1, UARTE1, SERIAL1);
impl_uarte!(UARTETWISPI2, UARTE2, SERIAL2); impl_uarte!(SERIAL2, UARTE2, SERIAL2);
impl_uarte!(UARTETWISPI3, UARTE3, SERIAL3); impl_uarte!(SERIAL3, UARTE3, SERIAL3);
impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); impl_spim!(SERIAL0, SPIM0, SERIAL0);
impl_spim!(UARTETWISPI1, SPIM1, SERIAL1); impl_spim!(SERIAL1, SPIM1, SERIAL1);
impl_spim!(UARTETWISPI2, SPIM2, SERIAL2); impl_spim!(SERIAL2, SPIM2, SERIAL2);
impl_spim!(UARTETWISPI3, SPIM3, SERIAL3); impl_spim!(SERIAL3, SPIM3, SERIAL3);
impl_spis!(UARTETWISPI0, SPIS0, SERIAL0); impl_spis!(SERIAL0, SPIS0, SERIAL0);
impl_spis!(UARTETWISPI1, SPIS1, SERIAL1); impl_spis!(SERIAL1, SPIS1, SERIAL1);
impl_spis!(UARTETWISPI2, SPIS2, SERIAL2); impl_spis!(SERIAL2, SPIS2, SERIAL2);
impl_spis!(UARTETWISPI3, SPIS3, SERIAL3); impl_spis!(SERIAL3, SPIS3, SERIAL3);
impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); impl_twim!(SERIAL0, TWIM0, SERIAL0);
impl_twim!(UARTETWISPI1, TWIM1, SERIAL1); impl_twim!(SERIAL1, TWIM1, SERIAL1);
impl_twim!(UARTETWISPI2, TWIM2, SERIAL2); impl_twim!(SERIAL2, TWIM2, SERIAL2);
impl_twim!(UARTETWISPI3, TWIM3, SERIAL3); impl_twim!(SERIAL3, TWIM3, SERIAL3);
impl_twis!(UARTETWISPI0, TWIS0, SERIAL0); impl_twis!(SERIAL0, TWIS0, SERIAL0);
impl_twis!(UARTETWISPI1, TWIS1, SERIAL1); impl_twis!(SERIAL1, TWIS1, SERIAL1);
impl_twis!(UARTETWISPI2, TWIS2, SERIAL2); impl_twis!(SERIAL2, TWIS2, SERIAL2);
impl_twis!(UARTETWISPI3, TWIS3, SERIAL3); impl_twis!(SERIAL3, TWIS3, SERIAL3);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);
@ -392,7 +395,9 @@ impl_timer!(TIMER2, TIMER2, TIMER2);
impl_pin!(P0_00, 0, 0); impl_pin!(P0_00, 0, 0);
impl_pin!(P0_01, 0, 1); impl_pin!(P0_01, 0, 1);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_02, 0, 2); impl_pin!(P0_02, 0, 2);
#[cfg(feature = "nfc-pins-as-gpio")]
impl_pin!(P0_03, 0, 3); impl_pin!(P0_03, 0, 3);
impl_pin!(P0_04, 0, 4); impl_pin!(P0_04, 0, 4);
impl_pin!(P0_05, 0, 5); impl_pin!(P0_05, 0, 5);

View File

@ -1,3 +1,4 @@
/// Peripheral Access Crate
#[allow(unused_imports)] #[allow(unused_imports)]
#[rustfmt::skip] #[rustfmt::skip]
pub mod pac { pub mod pac {
@ -118,10 +119,10 @@ embassy_hal_common::peripherals! {
NVMC, NVMC,
// UARTE, TWI & SPI // UARTE, TWI & SPI
UARTETWISPI0, SERIAL0,
UARTETWISPI1, SERIAL1,
UARTETWISPI2, SERIAL2,
UARTETWISPI3, SERIAL3,
// SAADC // SAADC
SAADC, SAADC,
@ -241,11 +242,11 @@ embassy_hal_common::peripherals! {
P1_15, P1_15,
} }
impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); impl_uarte!(SERIAL0, UARTE0, SERIAL0);
impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); impl_spim!(SERIAL0, SPIM0, SERIAL0);
impl_spis!(UARTETWISPI0, SPIS0, SERIAL0); impl_spis!(SERIAL0, SPIS0, SERIAL0);
impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); impl_twim!(SERIAL0, TWIM0, SERIAL0);
impl_twis!(UARTETWISPI0, TWIS0, SERIAL0); impl_twis!(SERIAL0, TWIS0, SERIAL0);
impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER1, TIMER1, TIMER1);

View File

@ -1,3 +1,4 @@
/// Peripheral Access Crate
#[allow(unused_imports)] #[allow(unused_imports)]
#[rustfmt::skip] #[rustfmt::skip]
pub mod pac { pub mod pac {
@ -178,10 +179,10 @@ embassy_hal_common::peripherals! {
NVMC, NVMC,
// UARTE, TWI & SPI // UARTE, TWI & SPI
UARTETWISPI0, SERIAL0,
UARTETWISPI1, SERIAL1,
UARTETWISPI2, SERIAL2,
UARTETWISPI3, SERIAL3,
// SAADC // SAADC
SAADC, SAADC,
@ -270,30 +271,30 @@ embassy_hal_common::peripherals! {
PDM, PDM,
} }
impl_uarte!(UARTETWISPI0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_uarte!(SERIAL0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_uarte!(UARTETWISPI1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_uarte!(SERIAL1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_uarte!(UARTETWISPI2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_uarte!(SERIAL2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_uarte!(UARTETWISPI3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_uarte!(SERIAL3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_spim!(UARTETWISPI0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_spim!(SERIAL0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_spim!(UARTETWISPI1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_spim!(SERIAL1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_spim!(SERIAL2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_spim!(SERIAL3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_spis!(UARTETWISPI0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_spis!(SERIAL0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_spis!(UARTETWISPI1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_spis!(SERIAL1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_spis!(UARTETWISPI2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_spis!(SERIAL2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_spis!(UARTETWISPI3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_spis!(SERIAL3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_twim!(SERIAL0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_twim!(SERIAL1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_twim!(SERIAL2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_twim!(SERIAL3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_twis!(UARTETWISPI0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); impl_twis!(SERIAL0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_twis!(UARTETWISPI1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); impl_twis!(SERIAL1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_twis!(UARTETWISPI2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); impl_twis!(SERIAL2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_twis!(UARTETWISPI3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_twis!(SERIAL3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM1, PWM1, PWM1);

View File

@ -1,4 +1,4 @@
//! General purpose input/output for nRF. //! General purpose input/output (GPIO) driver.
#![macro_use] #![macro_use]
use core::convert::Infallible; use core::convert::Infallible;
@ -88,9 +88,9 @@ impl From<bool> for Level {
} }
} }
impl Into<bool> for Level { impl From<Level> for bool {
fn into(self) -> bool { fn from(level: Level) -> bool {
match self { match level {
Level::Low => false, Level::Low => false,
Level::High => true, Level::High => true,
} }

View File

@ -1,3 +1,5 @@
//! GPIO task/event (GPIOTE) driver.
use core::convert::Infallible; use core::convert::Infallible;
use core::future::{poll_fn, Future}; use core::future::{poll_fn, Future};
use core::task::{Context, Poll}; use core::task::{Context, Poll};
@ -11,29 +13,38 @@ use crate::interrupt::{Interrupt, InterruptExt};
use crate::ppi::{Event, Task}; use crate::ppi::{Event, Task};
use crate::{interrupt, pac, peripherals}; use crate::{interrupt, pac, peripherals};
pub const CHANNEL_COUNT: usize = 8; /// Amount of GPIOTE channels in the chip.
const CHANNEL_COUNT: usize = 8;
#[cfg(any(feature = "nrf52833", feature = "nrf52840"))] #[cfg(any(feature = "nrf52833", feature = "nrf52840"))]
pub const PIN_COUNT: usize = 48; const PIN_COUNT: usize = 48;
#[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))] #[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))]
pub const PIN_COUNT: usize = 32; const PIN_COUNT: usize = 32;
#[allow(clippy::declare_interior_mutable_const)] #[allow(clippy::declare_interior_mutable_const)]
const NEW_AW: AtomicWaker = AtomicWaker::new(); const NEW_AW: AtomicWaker = AtomicWaker::new();
static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT];
static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT];
/// Polarity for listening to events for GPIOTE input channels.
pub enum InputChannelPolarity { pub enum InputChannelPolarity {
/// Don't listen for any pin changes.
None, None,
/// Listen for high to low changes.
HiToLo, HiToLo,
/// Listen for low to high changes.
LoToHi, LoToHi,
/// Listen for any change, either low to high or high to low.
Toggle, Toggle,
} }
/// Polarity of the `task out` operation. /// Polarity of the OUT task operation for GPIOTE output channels.
pub enum OutputChannelPolarity { pub enum OutputChannelPolarity {
/// Set the pin high.
Set, Set,
/// Set the pin low.
Clear, Clear,
/// Toggle the pin.
Toggle, Toggle,
} }
@ -162,6 +173,7 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> {
} }
impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
/// Create a new GPIOTE input channel driver.
pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self {
into_ref!(ch); into_ref!(ch);
@ -188,6 +200,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
InputChannel { ch, pin } InputChannel { ch, pin }
} }
/// Asynchronously wait for an event in this channel.
pub async fn wait(&self) { pub async fn wait(&self) {
let g = regs(); let g = regs();
let num = self.ch.number(); let num = self.ch.number();
@ -231,6 +244,7 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> {
} }
impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
/// Create a new GPIOTE output channel driver.
pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self {
into_ref!(ch); into_ref!(ch);
let g = regs(); let g = regs();
@ -258,20 +272,20 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
OutputChannel { ch, _pin: pin } OutputChannel { ch, _pin: pin }
} }
/// Triggers `task out` (as configured with task_out_polarity, defaults to Toggle). /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle).
pub fn out(&self) { pub fn out(&self) {
let g = regs(); let g = regs();
g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) }); g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) });
} }
/// Triggers `task set` (set associated pin high). /// Triggers the SET task (set associated pin high).
#[cfg(not(feature = "nrf51"))] #[cfg(not(feature = "nrf51"))]
pub fn set(&self) { pub fn set(&self) {
let g = regs(); let g = regs();
g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) }); g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) });
} }
/// Triggers `task clear` (set associated pin low). /// Triggers the CLEAR task (set associated pin low).
#[cfg(not(feature = "nrf51"))] #[cfg(not(feature = "nrf51"))]
pub fn clear(&self) { pub fn clear(&self) {
let g = regs(); let g = regs();
@ -301,6 +315,7 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
// ======================= // =======================
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct PortInputFuture<'a> { pub(crate) struct PortInputFuture<'a> {
pin: PeripheralRef<'a, AnyPin>, pin: PeripheralRef<'a, AnyPin>,
} }
@ -336,48 +351,58 @@ impl<'a> Future for PortInputFuture<'a> {
} }
impl<'d, T: GpioPin> Input<'d, T> { impl<'d, T: GpioPin> Input<'d, T> {
/// Wait until the pin is high. If it is already high, return immediately.
pub async fn wait_for_high(&mut self) { pub async fn wait_for_high(&mut self) {
self.pin.wait_for_high().await self.pin.wait_for_high().await
} }
/// Wait until the pin is low. If it is already low, return immediately.
pub async fn wait_for_low(&mut self) { pub async fn wait_for_low(&mut self) {
self.pin.wait_for_low().await self.pin.wait_for_low().await
} }
/// Wait for the pin to undergo a transition from low to high.
pub async fn wait_for_rising_edge(&mut self) { pub async fn wait_for_rising_edge(&mut self) {
self.pin.wait_for_rising_edge().await self.pin.wait_for_rising_edge().await
} }
/// Wait for the pin to undergo a transition from high to low.
pub async fn wait_for_falling_edge(&mut self) { pub async fn wait_for_falling_edge(&mut self) {
self.pin.wait_for_falling_edge().await self.pin.wait_for_falling_edge().await
} }
/// Wait for the pin to undergo any transition, i.e low to high OR high to low.
pub async fn wait_for_any_edge(&mut self) { pub async fn wait_for_any_edge(&mut self) {
self.pin.wait_for_any_edge().await self.pin.wait_for_any_edge().await
} }
} }
impl<'d, T: GpioPin> Flex<'d, T> { impl<'d, T: GpioPin> Flex<'d, T> {
/// Wait until the pin is high. If it is already high, return immediately.
pub async fn wait_for_high(&mut self) { pub async fn wait_for_high(&mut self) {
self.pin.conf().modify(|_, w| w.sense().high()); self.pin.conf().modify(|_, w| w.sense().high());
PortInputFuture::new(&mut self.pin).await PortInputFuture::new(&mut self.pin).await
} }
/// Wait until the pin is low. If it is already low, return immediately.
pub async fn wait_for_low(&mut self) { pub async fn wait_for_low(&mut self) {
self.pin.conf().modify(|_, w| w.sense().low()); self.pin.conf().modify(|_, w| w.sense().low());
PortInputFuture::new(&mut self.pin).await PortInputFuture::new(&mut self.pin).await
} }
/// Wait for the pin to undergo a transition from low to high.
pub async fn wait_for_rising_edge(&mut self) { pub async fn wait_for_rising_edge(&mut self) {
self.wait_for_low().await; self.wait_for_low().await;
self.wait_for_high().await; self.wait_for_high().await;
} }
/// Wait for the pin to undergo a transition from high to low.
pub async fn wait_for_falling_edge(&mut self) { pub async fn wait_for_falling_edge(&mut self) {
self.wait_for_high().await; self.wait_for_high().await;
self.wait_for_low().await; self.wait_for_low().await;
} }
/// Wait for the pin to undergo any transition, i.e low to high OR high to low.
pub async fn wait_for_any_edge(&mut self) { pub async fn wait_for_any_edge(&mut self) {
if self.is_high() { if self.is_high() {
self.pin.conf().modify(|_, w| w.sense().low()); self.pin.conf().modify(|_, w| w.sense().low());
@ -394,8 +419,17 @@ mod sealed {
pub trait Channel {} pub trait Channel {}
} }
/// GPIOTE channel trait.
///
/// Implemented by all GPIOTE channels.
pub trait Channel: sealed::Channel + Sized { pub trait Channel: sealed::Channel + Sized {
/// Get the channel number.
fn number(&self) -> usize; fn number(&self) -> usize;
/// Convert this channel to a type-erased `AnyChannel`.
///
/// This allows using several channels in situations that might require
/// them to be the same type, like putting them in an array.
fn degrade(self) -> AnyChannel { fn degrade(self) -> AnyChannel {
AnyChannel { AnyChannel {
number: self.number() as u8, number: self.number() as u8,
@ -403,6 +437,12 @@ pub trait Channel: sealed::Channel + Sized {
} }
} }
/// Type-erased channel.
///
/// Obtained by calling `Channel::degrade`.
///
/// This allows using several channels in situations that might require
/// them to be the same type, like putting them in an array.
pub struct AnyChannel { pub struct AnyChannel {
number: u8, number: u8,
} }

View File

@ -1,6 +1,6 @@
#![macro_use] //! Inter-IC Sound (I2S) driver.
//! Support for I2S audio #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
use core::marker::PhantomData; use core::marker::PhantomData;
@ -19,16 +19,23 @@ use crate::pac::i2s::RegisterBlock;
use crate::util::{slice_in_ram_or, slice_ptr_parts}; use crate::util::{slice_in_ram_or, slice_ptr_parts};
use crate::{Peripheral, EASY_DMA_SIZE}; use crate::{Peripheral, EASY_DMA_SIZE};
/// Type alias for `MultiBuffering` with 2 buffers.
pub type DoubleBuffering<S, const NS: usize> = MultiBuffering<S, 2, NS>; pub type DoubleBuffering<S, const NS: usize> = MultiBuffering<S, 2, NS>;
/// I2S transfer error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// The buffer is too long.
BufferTooLong, BufferTooLong,
/// The buffer is empty.
BufferZeroLength, BufferZeroLength,
BufferNotInDataMemory, /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
BufferNotInRAM,
/// The buffer address is not aligned.
BufferMisaligned, BufferMisaligned,
/// The buffer length is not a multiple of the alignment.
BufferLengthMisaligned, BufferLengthMisaligned,
} }
@ -36,34 +43,16 @@ pub enum Error {
#[derive(Clone)] #[derive(Clone)]
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// Sample width
pub sample_width: SampleWidth, pub sample_width: SampleWidth,
/// Alignment
pub align: Align, pub align: Align,
/// Sample format
pub format: Format, pub format: Format,
/// Channel configuration.
pub channels: Channels, pub channels: Channels,
} }
impl Config {
pub fn sample_width(mut self, sample_width: SampleWidth) -> Self {
self.sample_width = sample_width;
self
}
pub fn align(mut self, align: Align) -> Self {
self.align = align;
self
}
pub fn format(mut self, format: Format) -> Self {
self.format = format;
self
}
pub fn channels(mut self, channels: Channels) -> Self {
self.channels = channels;
self
}
}
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -75,7 +64,7 @@ impl Default for Config {
} }
} }
/// I2S Mode /// I2S clock configuration.
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct MasterClock { pub struct MasterClock {
freq: MckFreq, freq: MckFreq,
@ -83,12 +72,14 @@ pub struct MasterClock {
} }
impl MasterClock { impl MasterClock {
/// Create a new `MasterClock`.
pub fn new(freq: MckFreq, ratio: Ratio) -> Self { pub fn new(freq: MckFreq, ratio: Ratio) -> Self {
Self { freq, ratio } Self { freq, ratio }
} }
} }
impl MasterClock { impl MasterClock {
/// Get the sample rate for this clock configuration.
pub fn sample_rate(&self) -> u32 { pub fn sample_rate(&self) -> u32 {
self.freq.to_frequency() / self.ratio.to_divisor() self.freq.to_frequency() / self.ratio.to_divisor()
} }
@ -97,18 +88,31 @@ impl MasterClock {
/// Master clock generator frequency. /// Master clock generator frequency.
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum MckFreq { pub enum MckFreq {
/// 32 Mhz / 8 = 4000.00 kHz
_32MDiv8, _32MDiv8,
/// 32 Mhz / 10 = 3200.00 kHz
_32MDiv10, _32MDiv10,
/// 32 Mhz / 11 = 2909.09 kHz
_32MDiv11, _32MDiv11,
/// 32 Mhz / 15 = 2133.33 kHz
_32MDiv15, _32MDiv15,
/// 32 Mhz / 16 = 2000.00 kHz
_32MDiv16, _32MDiv16,
/// 32 Mhz / 21 = 1523.81 kHz
_32MDiv21, _32MDiv21,
/// 32 Mhz / 23 = 1391.30 kHz
_32MDiv23, _32MDiv23,
/// 32 Mhz / 30 = 1066.67 kHz
_32MDiv30, _32MDiv30,
/// 32 Mhz / 31 = 1032.26 kHz
_32MDiv31, _32MDiv31,
/// 32 Mhz / 32 = 1000.00 kHz
_32MDiv32, _32MDiv32,
/// 32 Mhz / 42 = 761.90 kHz
_32MDiv42, _32MDiv42,
/// 32 Mhz / 63 = 507.94 kHz
_32MDiv63, _32MDiv63,
/// 32 Mhz / 125 = 256.00 kHz
_32MDiv125, _32MDiv125,
} }
@ -146,14 +150,23 @@ impl From<MckFreq> for usize {
/// ///
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Ratio { pub enum Ratio {
/// Divide by 32
_32x, _32x,
/// Divide by 48
_48x, _48x,
/// Divide by 64
_64x, _64x,
/// Divide by 96
_96x, _96x,
/// Divide by 128
_128x, _128x,
/// Divide by 192
_192x, _192x,
/// Divide by 256
_256x, _256x,
/// Divide by 384
_384x, _384x,
/// Divide by 512
_512x, _512x,
} }
@ -165,6 +178,7 @@ impl Ratio {
usize::from(*self) as u8 usize::from(*self) as u8
} }
/// Return the divisor for this ratio
pub fn to_divisor(&self) -> u32 { pub fn to_divisor(&self) -> u32 {
Self::RATIOS[usize::from(*self)] Self::RATIOS[usize::from(*self)]
} }
@ -183,11 +197,17 @@ impl From<Ratio> for usize {
/// For custom master clock configuration, please refer to [MasterClock]. /// For custom master clock configuration, please refer to [MasterClock].
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ApproxSampleRate { pub enum ApproxSampleRate {
/// 11025 Hz
_11025, _11025,
/// 16000 Hz
_16000, _16000,
/// 22050 Hz
_22050, _22050,
/// 32000 Hz
_32000, _32000,
/// 44100 Hz
_44100, _44100,
/// 48000 Hz
_48000, _48000,
} }
@ -211,6 +231,7 @@ impl From<ApproxSampleRate> for MasterClock {
} }
impl ApproxSampleRate { impl ApproxSampleRate {
/// Get the sample rate as an integer.
pub fn sample_rate(&self) -> u32 { pub fn sample_rate(&self) -> u32 {
MasterClock::from(*self).sample_rate() MasterClock::from(*self).sample_rate()
} }
@ -223,20 +244,32 @@ impl ApproxSampleRate {
/// For custom master clock configuration, please refer to [Mode]. /// For custom master clock configuration, please refer to [Mode].
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ExactSampleRate { pub enum ExactSampleRate {
/// 8000 Hz
_8000, _8000,
/// 10582 Hz
_10582, _10582,
/// 12500 Hz
_12500, _12500,
/// 15625 Hz
_15625, _15625,
/// 15873 Hz
_15873, _15873,
/// 25000 Hz
_25000, _25000,
/// 31250 Hz
_31250, _31250,
/// 50000 Hz
_50000, _50000,
/// 62500 Hz
_62500, _62500,
/// 100000 Hz
_100000, _100000,
/// 125000 Hz
_125000, _125000,
} }
impl ExactSampleRate { impl ExactSampleRate {
/// Get the sample rate as an integer.
pub fn sample_rate(&self) -> u32 { pub fn sample_rate(&self) -> u32 {
MasterClock::from(*self).sample_rate() MasterClock::from(*self).sample_rate()
} }
@ -263,8 +296,11 @@ impl From<ExactSampleRate> for MasterClock {
/// Sample width. /// Sample width.
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum SampleWidth { pub enum SampleWidth {
/// 8 bit samples.
_8bit, _8bit,
/// 16 bit samples.
_16bit, _16bit,
/// 24 bit samples.
_24bit, _24bit,
} }
@ -277,7 +313,9 @@ impl From<SampleWidth> for u8 {
/// Channel used for the most significant sample value in a frame. /// Channel used for the most significant sample value in a frame.
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Align { pub enum Align {
/// Left-align samples.
Left, Left,
/// Right-align samples.
Right, Right,
} }
@ -293,7 +331,9 @@ impl From<Align> for bool {
/// Frame format. /// Frame format.
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Format { pub enum Format {
/// I2S frame format
I2S, I2S,
/// Aligned frame format
Aligned, Aligned,
} }
@ -309,8 +349,11 @@ impl From<Format> for bool {
/// Channels /// Channels
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Channels { pub enum Channels {
/// Stereo (2 channels).
Stereo, Stereo,
/// Mono, left channel only.
MonoLeft, MonoLeft,
/// Mono, right channel only.
MonoRight, MonoRight,
} }
@ -320,7 +363,7 @@ impl From<Channels> for u8 {
} }
} }
/// Interface to the I2S peripheral using EasyDMA to offload the transmission and reception workload. /// I2S driver.
pub struct I2S<'d, T: Instance> { pub struct I2S<'d, T: Instance> {
i2s: PeripheralRef<'d, T>, i2s: PeripheralRef<'d, T>,
irq: PeripheralRef<'d, T::Interrupt>, irq: PeripheralRef<'d, T::Interrupt>,
@ -566,7 +609,7 @@ impl<'d, T: Instance> I2S<'d, T> {
{ {
trace!("SEND: {}", buffer_ptr as *const S as u32); trace!("SEND: {}", buffer_ptr as *const S as u32);
slice_in_ram_or(buffer_ptr, Error::BufferNotInDataMemory)?; slice_in_ram_or(buffer_ptr, Error::BufferNotInRAM)?;
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
@ -1003,7 +1046,10 @@ impl<T: Instance> Device<T> {
/// Sample details /// Sample details
pub trait Sample: Sized + Copy + Default { pub trait Sample: Sized + Copy + Default {
/// Width of this sample type.
const WIDTH: usize; const WIDTH: usize;
/// Scale of this sample.
const SCALE: Self; const SCALE: Self;
} }
@ -1022,12 +1068,13 @@ impl Sample for i32 {
const SCALE: Self = 1 << (Self::WIDTH - 1); const SCALE: Self = 1 << (Self::WIDTH - 1);
} }
/// A 4-bytes aligned buffer. /// A 4-bytes aligned buffer. Needed for DMA access.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(align(4))] #[repr(align(4))]
pub struct AlignedBuffer<T: Sample, const N: usize>([T; N]); pub struct AlignedBuffer<T: Sample, const N: usize>([T; N]);
impl<T: Sample, const N: usize> AlignedBuffer<T, N> { impl<T: Sample, const N: usize> AlignedBuffer<T, N> {
/// Create a new `AlignedBuffer`.
pub fn new(array: [T; N]) -> Self { pub fn new(array: [T; N]) -> Self {
Self(array) Self(array)
} }
@ -1052,12 +1099,14 @@ impl<T: Sample, const N: usize> DerefMut for AlignedBuffer<T, N> {
} }
} }
/// Set of multiple buffers, for multi-buffering transfers.
pub struct MultiBuffering<S: Sample, const NB: usize, const NS: usize> { pub struct MultiBuffering<S: Sample, const NB: usize, const NS: usize> {
buffers: [AlignedBuffer<S, NS>; NB], buffers: [AlignedBuffer<S, NS>; NB],
index: usize, index: usize,
} }
impl<S: Sample, const NB: usize, const NS: usize> MultiBuffering<S, NB, NS> { impl<S: Sample, const NB: usize, const NS: usize> MultiBuffering<S, NB, NS> {
/// Create a new `MultiBuffering`.
pub fn new() -> Self { pub fn new() -> Self {
assert!(NB > 1); assert!(NB > 1);
Self { Self {
@ -1119,7 +1168,9 @@ pub(crate) mod sealed {
} }
} }
/// I2S peripehral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }

View File

@ -1,53 +1,11 @@
//! # Embassy nRF HAL
//!
//! HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed.
//!
//! The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs
//! for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to
//! complete operations in low power mod and handling interrupts, so that applications can focus on more important matters.
//!
//! ## EasyDMA considerations
//!
//! On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting
//! with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI).
//! However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust
//! slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen:
//!
//! ```no_run
//! // As we pass a slice to the function whose contents will not ever change,
//! // the compiler writes it into the flash and thus the pointer to it will
//! // reference static memory. Since EasyDMA requires slices to reside in RAM,
//! // this function call will fail.
//! let result = spim.write_from_ram(&[1, 2, 3]);
//! assert_eq!(result, Err(Error::DMABufferNotInDataMemory));
//!
//! // The data is still static and located in flash. However, since we are assigning
//! // it to a variable, the compiler will load it into memory. Passing a reference to the
//! // variable will yield a pointer that references dynamic memory, thus making EasyDMA happy.
//! // This function call succeeds.
//! let data = [1, 2, 3];
//! let result = spim.write_from_ram(&data);
//! assert!(result.is_ok());
//! ```
//!
//! Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions:
//! - Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM.
//! - Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission.
//!
//! Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will
//! fail and notify you, or the more convenient versions without the suffix which are potentially a little bit
//! more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage
//! as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840).
//!
//! Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as
//! mutable slices always reside in RAM.
#![no_std] #![no_std]
#![cfg_attr( #![cfg_attr(
feature = "nightly", feature = "nightly",
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections) feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections)
)] )]
#![cfg_attr(feature = "nightly", allow(incomplete_features))] #![cfg_attr(feature = "nightly", allow(incomplete_features))]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#[cfg(not(any( #[cfg(not(any(
feature = "nrf51", feature = "nrf51",
@ -66,6 +24,12 @@
)))] )))]
compile_error!("No chip feature activated. You must activate exactly one of the following features: nrf52810, nrf52811, nrf52832, nrf52833, nrf52840"); compile_error!("No chip feature activated. You must activate exactly one of the following features: nrf52810, nrf52811, nrf52832, nrf52833, nrf52840");
#[cfg(all(feature = "reset-pin-as-gpio", not(feature = "_nrf52")))]
compile_error!("feature `reset-pin-as-gpio` is only valid for nRF52 series chips.");
#[cfg(all(feature = "nfc-pins-as-gpio", not(any(feature = "_nrf52", feature = "_nrf5340-app"))))]
compile_error!("feature `nfc-pins-as-gpio` is only valid for nRF52, or nRF53's application core.");
// This mod MUST go first, so that the others see its macros. // This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt; pub(crate) mod fmt;
pub(crate) mod util; pub(crate) mod util;
@ -181,6 +145,19 @@ pub mod config {
ExternalFullSwing, ExternalFullSwing,
} }
/// SWD access port protection setting.
#[non_exhaustive]
pub enum Debug {
/// Debugging is allowed (APPROTECT is disabled). Default.
Allowed,
/// Debugging is not allowed (APPROTECT is enabled).
Disallowed,
/// APPROTECT is not configured (neither to enable it or disable it).
/// This can be useful if you're already doing it by other means and
/// you don't want embassy-nrf to touch UICR.
NotConfigured,
}
/// Configuration for peripherals. Default configuration should work on any nRF chip. /// Configuration for peripherals. Default configuration should work on any nRF chip.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
@ -194,6 +171,8 @@ pub mod config {
/// Time driver interrupt priority. Should be lower priority than softdevice if used. /// Time driver interrupt priority. Should be lower priority than softdevice if used.
#[cfg(feature = "_time-driver")] #[cfg(feature = "_time-driver")]
pub time_interrupt_priority: crate::interrupt::Priority, pub time_interrupt_priority: crate::interrupt::Priority,
/// Enable or disable the debug port.
pub debug: Debug,
} }
impl Default for Config { impl Default for Config {
@ -208,17 +187,204 @@ pub mod config {
gpiote_interrupt_priority: crate::interrupt::Priority::P0, gpiote_interrupt_priority: crate::interrupt::Priority::P0,
#[cfg(feature = "_time-driver")] #[cfg(feature = "_time-driver")]
time_interrupt_priority: crate::interrupt::Priority::P0, time_interrupt_priority: crate::interrupt::Priority::P0,
// In NS mode, default to NotConfigured, assuming the S firmware will do it.
#[cfg(feature = "_ns")]
debug: Debug::NotConfigured,
#[cfg(not(feature = "_ns"))]
debug: Debug::Allowed,
} }
} }
} }
} }
#[cfg(feature = "_nrf9160")]
#[allow(unused)]
mod consts {
pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32;
pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF802C as *mut u32;
pub const APPROTECT_ENABLED: u32 = 0x0000_0000;
}
#[cfg(feature = "_nrf5340-app")]
#[allow(unused)]
mod consts {
pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32;
pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF801C as *mut u32;
pub const UICR_NFCPINS: *mut u32 = 0x00FF8028 as *mut u32;
pub const APPROTECT_ENABLED: u32 = 0x0000_0000;
pub const APPROTECT_DISABLED: u32 = 0x50FA50FA;
}
#[cfg(feature = "_nrf5340-net")]
#[allow(unused)]
mod consts {
pub const UICR_APPROTECT: *mut u32 = 0x01FF8000 as *mut u32;
pub const APPROTECT_ENABLED: u32 = 0x0000_0000;
pub const APPROTECT_DISABLED: u32 = 0x50FA50FA;
}
#[cfg(feature = "_nrf52")]
#[allow(unused)]
mod consts {
pub const UICR_PSELRESET1: *mut u32 = 0x10001200 as *mut u32;
pub const UICR_PSELRESET2: *mut u32 = 0x10001204 as *mut u32;
pub const UICR_NFCPINS: *mut u32 = 0x1000120C as *mut u32;
pub const UICR_APPROTECT: *mut u32 = 0x10001208 as *mut u32;
pub const APPROTECT_ENABLED: u32 = 0x0000_0000;
pub const APPROTECT_DISABLED: u32 = 0x0000_005a;
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum WriteResult {
/// Word was written successfully, needs reset.
Written,
/// Word was already set to the value we wanted to write, nothing was done.
Noop,
/// Word is already set to something else, we couldn't write the desired value.
Failed,
}
unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult {
let curr_val = address.read_volatile();
if curr_val == value {
return WriteResult::Noop;
}
// We can only change `1` bits to `0` bits.
if curr_val & value != value {
return WriteResult::Failed;
}
// Writing to UICR can only change `1` bits to `0` bits.
// If this write would change `0` bits to `1` bits, we can't do it.
// It is only possible to do when erasing UICR, which is forbidden if
// APPROTECT is enabled.
if (!curr_val) & value != 0 {
panic!("Cannot write UICR address={:08x} value={:08x}", address as u32, value)
}
let nvmc = &*pac::NVMC::ptr();
nvmc.config.write(|w| w.wen().wen());
while nvmc.ready.read().ready().is_busy() {}
address.write_volatile(value);
while nvmc.ready.read().ready().is_busy() {}
nvmc.config.reset();
while nvmc.ready.read().ready().is_busy() {}
WriteResult::Written
}
/// Initialize peripherals with the provided configuration. This should only be called once at startup. /// Initialize peripherals with the provided configuration. This should only be called once at startup.
pub fn init(config: config::Config) -> Peripherals { pub fn init(config: config::Config) -> Peripherals {
// Do this first, so that it panics if user is calling `init` a second time // Do this first, so that it panics if user is calling `init` a second time
// before doing anything important. // before doing anything important.
let peripherals = Peripherals::take(); let peripherals = Peripherals::take();
let mut needs_reset = false;
// Setup debug protection.
match config.debug {
config::Debug::Allowed => {
#[cfg(feature = "_nrf52")]
unsafe {
let variant = (0x1000_0104 as *mut u32).read_volatile();
// Get the letter for the build code (b'A' .. b'F')
let build_code = (variant >> 8) as u8;
if build_code >= b'F' {
// Chips with build code F and higher (revision 3 and higher) have an
// improved APPROTECT ("hardware and software controlled access port protection")
// which needs explicit action by the firmware to keep it unlocked
// UICR.APPROTECT = SwDisabled
let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED);
needs_reset |= res == WriteResult::Written;
// APPROTECT.DISABLE = SwDisabled
(0x4000_0558 as *mut u32).write_volatile(consts::APPROTECT_DISABLED);
} else {
// nothing to do on older chips, debug is allowed by default.
}
}
#[cfg(feature = "_nrf5340")]
unsafe {
let p = &*pac::CTRLAP::ptr();
let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED);
needs_reset |= res == WriteResult::Written;
p.approtect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED));
#[cfg(feature = "_nrf5340-app")]
{
let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED);
needs_reset |= res == WriteResult::Written;
p.secureapprotect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED));
}
}
// nothing to do on the nrf9160, debug is allowed by default.
}
config::Debug::Disallowed => unsafe {
// UICR.APPROTECT = Enabled
let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED);
needs_reset |= res == WriteResult::Written;
#[cfg(any(feature = "_nrf5340-app", feature = "_nrf9160"))]
{
let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED);
needs_reset |= res == WriteResult::Written;
}
},
config::Debug::NotConfigured => {}
}
#[cfg(feature = "_nrf52")]
unsafe {
let value = if cfg!(feature = "reset-pin-as-gpio") {
!0
} else {
chip::RESET_PIN
};
let res1 = uicr_write(consts::UICR_PSELRESET1, value);
let res2 = uicr_write(consts::UICR_PSELRESET2, value);
needs_reset |= res1 == WriteResult::Written || res2 == WriteResult::Written;
if res1 == WriteResult::Failed || res2 == WriteResult::Failed {
#[cfg(not(feature = "reset-pin-as-gpio"))]
warn!(
"You have requested enabling chip reset functionality on the reset pin, by not enabling the Cargo feature `reset-pin-as-gpio`.\n\
However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\
To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`."
);
#[cfg(feature = "reset-pin-as-gpio")]
warn!(
"You have requested using the reset pin as GPIO, by enabling the Cargo feature `reset-pin-as-gpio`.\n\
However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\
To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`."
);
}
}
#[cfg(any(feature = "_nrf52", feature = "_nrf5340-app"))]
unsafe {
let value = if cfg!(feature = "nfc-pins-as-gpio") { 0 } else { !0 };
let res = uicr_write(consts::UICR_NFCPINS, value);
needs_reset |= res == WriteResult::Written;
if res == WriteResult::Failed {
// with nfc-pins-as-gpio, this can never fail because we're writing all zero bits.
#[cfg(not(feature = "nfc-pins-as-gpio"))]
warn!(
"You have requested to use P0.09 and P0.10 pins for NFC, by not enabling the Cargo feature `nfc-pins-as-gpio`.\n\
However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\
To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`."
);
}
}
if needs_reset {
cortex_m::peripheral::SCB::sys_reset();
}
let r = unsafe { &*pac::CLOCK::ptr() }; let r = unsafe { &*pac::CLOCK::ptr() };
// Start HFCLK. // Start HFCLK.

View File

@ -1,4 +1,4 @@
//! Non-Volatile Memory Controller (NVMC) module. //! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver.
use core::{ptr, slice}; use core::{ptr, slice};
@ -85,23 +85,23 @@ impl<'d> Nvmc<'d> {
} }
fn enable_erase(&self) { fn enable_erase(&self) {
#[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] #[cfg(not(feature = "_ns"))]
Self::regs().config.write(|w| w.wen().een()); Self::regs().config.write(|w| w.wen().een());
#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] #[cfg(feature = "_ns")]
Self::regs().configns.write(|w| w.wen().een()); Self::regs().configns.write(|w| w.wen().een());
} }
fn enable_read(&self) { fn enable_read(&self) {
#[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] #[cfg(not(feature = "_ns"))]
Self::regs().config.write(|w| w.wen().ren()); Self::regs().config.write(|w| w.wen().ren());
#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] #[cfg(feature = "_ns")]
Self::regs().configns.write(|w| w.wen().ren()); Self::regs().configns.write(|w| w.wen().ren());
} }
fn enable_write(&self) { fn enable_write(&self) {
#[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] #[cfg(not(feature = "_ns"))]
Self::regs().config.write(|w| w.wen().wen()); Self::regs().config.write(|w| w.wen().wen());
#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] #[cfg(feature = "_ns")]
Self::regs().configns.write(|w| w.wen().wen()); Self::regs().configns.write(|w| w.wen().wen());
} }
} }

View File

@ -1,4 +1,4 @@
//! PDM mirophone interface //! Pulse Density Modulation (PDM) mirophone driver.
use core::marker::PhantomData; use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
@ -22,12 +22,16 @@ pub struct Pdm<'d> {
phantom: PhantomData<&'d PDM>, phantom: PhantomData<&'d PDM>,
} }
/// PDM error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Buffer is too long.
BufferTooLong, BufferTooLong,
/// Buffer is empty
BufferZeroLength, BufferZeroLength,
/// PDM is not running
NotRunning, NotRunning,
} }
@ -119,6 +123,7 @@ impl<'d> Pdm<'d> {
r.events_started.reset(); r.events_started.reset();
} }
/// Sample data into the given buffer.
pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> { pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Err(Error::BufferZeroLength); return Err(Error::BufferZeroLength);
@ -215,14 +220,21 @@ impl Default for Config {
} }
} }
/// PDM operation mode.
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum OperationMode { pub enum OperationMode {
/// Mono (1 channel)
Mono, Mono,
/// Stereo (2 channels)
Stereo, Stereo,
} }
/// PDM edge polarity
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum Edge { pub enum Edge {
/// Left edge is rising
LeftRising, LeftRising,
/// Left edge is falling
LeftFalling, LeftFalling,
} }

View File

@ -11,12 +11,14 @@ fn regs() -> &'static pac::dppic::RegisterBlock {
} }
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
/// Configure PPI channel to trigger `task` on `event`.
pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self { pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self {
Ppi::new_many_to_many(ch, [event], [task]) Ppi::new_many_to_many(ch, [event], [task])
} }
} }
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
/// Configure PPI channel to trigger both `task1` and `task2` on `event`.
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self { pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
Ppi::new_many_to_many(ch, [event], [task1, task2]) Ppi::new_many_to_many(ch, [event], [task1, task2])
} }
@ -25,6 +27,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usize> impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usize>
Ppi<'d, C, EVENT_COUNT, TASK_COUNT> Ppi<'d, C, EVENT_COUNT, TASK_COUNT>
{ {
/// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires.
pub fn new_many_to_many( pub fn new_many_to_many(
ch: impl Peripheral<P = C> + 'd, ch: impl Peripheral<P = C> + 'd,
events: [Event; EVENT_COUNT], events: [Event; EVENT_COUNT],

View File

@ -1,6 +1,6 @@
#![macro_use] #![macro_use]
//! HAL interface for the PPI and DPPI peripheral. //! Programmable Peripheral Interconnect (PPI/DPPI) driver.
//! //!
//! The (Distributed) Programmable Peripheral Interconnect interface allows for an autonomous interoperability //! The (Distributed) Programmable Peripheral Interconnect interface allows for an autonomous interoperability
//! between peripherals through their events and tasks. There are fixed PPI channels and fully //! between peripherals through their events and tasks. There are fixed PPI channels and fully

View File

@ -48,7 +48,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
#[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task #[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
/// Configure PPI channel to trigger `task1` and `task2` on `event`. /// Configure PPI channel to trigger both `task1` and `task2` on `event`.
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self { pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
into_ref!(ch); into_ref!(ch);

View File

@ -1,3 +1,5 @@
//! Pulse Width Modulation (PWM) driver.
#![macro_use] #![macro_use]
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
@ -32,6 +34,7 @@ pub struct SequencePwm<'d, T: Instance> {
ch3: Option<PeripheralRef<'d, AnyPin>>, ch3: Option<PeripheralRef<'d, AnyPin>>,
} }
/// PWM error
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
@ -41,7 +44,7 @@ pub enum Error {
/// Min Sequence count is 1 /// Min Sequence count is 1
SequenceTimesAtLeastOne, SequenceTimesAtLeastOne,
/// EasyDMA can only read from data memory, read only buffers in flash will fail. /// EasyDMA can only read from data memory, read only buffers in flash will fail.
DMABufferNotInDataMemory, BufferNotInRAM,
} }
const MAX_SEQUENCE_LEN: usize = 32767; const MAX_SEQUENCE_LEN: usize = 32767;
@ -358,6 +361,7 @@ pub struct Sequence<'s> {
} }
impl<'s> Sequence<'s> { impl<'s> Sequence<'s> {
/// Create a new `Sequence`
pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { pub fn new(words: &'s [u16], config: SequenceConfig) -> Self {
Self { words, config } Self { words, config }
} }
@ -367,7 +371,7 @@ impl<'s> Sequence<'s> {
/// Takes at one sequence along with its configuration. /// Takes at one sequence along with its configuration.
#[non_exhaustive] #[non_exhaustive]
pub struct SingleSequencer<'d, 's, T: Instance> { pub struct SingleSequencer<'d, 's, T: Instance> {
pub sequencer: Sequencer<'d, 's, T>, sequencer: Sequencer<'d, 's, T>,
} }
impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> {
@ -428,8 +432,8 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> {
let sequence0 = &self.sequence0; let sequence0 = &self.sequence0;
let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0); let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0);
slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(sequence0.words, Error::BufferNotInRAM)?;
slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(alt_sequence.words, Error::BufferNotInRAM)?;
if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN { if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN {
return Err(Error::SequenceTooLong); return Err(Error::SequenceTooLong);
@ -536,13 +540,21 @@ pub enum SequenceMode {
/// PWM Base clock is system clock (16MHz) divided by prescaler /// PWM Base clock is system clock (16MHz) divided by prescaler
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Prescaler { pub enum Prescaler {
/// Divide by 1
Div1, Div1,
/// Divide by 2
Div2, Div2,
/// Divide by 4
Div4, Div4,
/// Divide by 8
Div8, Div8,
/// Divide by 16
Div16, Div16,
/// Divide by 32
Div32, Div32,
/// Divide by 64
Div64, Div64,
/// Divide by 128
Div128, Div128,
} }
@ -828,7 +840,9 @@ pub(crate) mod sealed {
} }
} }
/// PWM peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }

View File

@ -1,4 +1,4 @@
//! Quadrature decoder interface //! Quadrature decoder (QDEC) driver.
use core::future::poll_fn; use core::future::poll_fn;
use core::task::Poll; use core::task::Poll;
@ -12,17 +12,23 @@ use crate::interrupt::InterruptExt;
use crate::peripherals::QDEC; use crate::peripherals::QDEC;
use crate::{interrupt, pac, Peripheral}; use crate::{interrupt, pac, Peripheral};
/// Quadrature decoder /// Quadrature decoder driver.
pub struct Qdec<'d> { pub struct Qdec<'d> {
_p: PeripheralRef<'d, QDEC>, _p: PeripheralRef<'d, QDEC>,
} }
/// QDEC config
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// Number of samples
pub num_samples: NumSamples, pub num_samples: NumSamples,
/// Sample period
pub period: SamplePeriod, pub period: SamplePeriod,
/// Set LED output pin polarity
pub led_polarity: LedPolarity, pub led_polarity: LedPolarity,
/// Enable/disable input debounce filters
pub debounce: bool, pub debounce: bool,
/// Time period the LED is switched ON prior to sampling (0..511 us).
pub led_pre_usecs: u16, pub led_pre_usecs: u16,
} }
@ -41,6 +47,7 @@ impl Default for Config {
static WAKER: AtomicWaker = AtomicWaker::new(); static WAKER: AtomicWaker = AtomicWaker::new();
impl<'d> Qdec<'d> { impl<'d> Qdec<'d> {
/// Create a new QDEC.
pub fn new( pub fn new(
qdec: impl Peripheral<P = QDEC> + 'd, qdec: impl Peripheral<P = QDEC> + 'd,
irq: impl Peripheral<P = interrupt::QDEC> + 'd, irq: impl Peripheral<P = interrupt::QDEC> + 'd,
@ -52,6 +59,7 @@ impl<'d> Qdec<'d> {
Self::new_inner(qdec, irq, a.map_into(), b.map_into(), None, config) Self::new_inner(qdec, irq, a.map_into(), b.map_into(), None, config)
} }
/// Create a new QDEC, with a pin for LED output.
pub fn new_with_led( pub fn new_with_led(
qdec: impl Peripheral<P = QDEC> + 'd, qdec: impl Peripheral<P = QDEC> + 'd,
irq: impl Peripheral<P = interrupt::QDEC> + 'd, irq: impl Peripheral<P = interrupt::QDEC> + 'd,
@ -170,36 +178,61 @@ impl<'d> Qdec<'d> {
} }
} }
/// Sample period
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum SamplePeriod { pub enum SamplePeriod {
/// 128 us
_128us, _128us,
/// 256 us
_256us, _256us,
/// 512 us
_512us, _512us,
/// 1024 us
_1024us, _1024us,
/// 2048 us
_2048us, _2048us,
/// 4096 us
_4096us, _4096us,
/// 8192 us
_8192us, _8192us,
/// 16384 us
_16384us, _16384us,
/// 32 ms
_32ms, _32ms,
/// 65 ms
_65ms, _65ms,
/// 131 ms
_131ms, _131ms,
} }
/// Number of samples taken.
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum NumSamples { pub enum NumSamples {
/// 10 samples
_10smpl, _10smpl,
/// 40 samples
_40smpl, _40smpl,
/// 80 samples
_80smpl, _80smpl,
/// 120 samples
_120smpl, _120smpl,
/// 160 samples
_160smpl, _160smpl,
/// 200 samples
_200smpl, _200smpl,
/// 240 samples
_240smpl, _240smpl,
/// 280 samples
_280smpl, _280smpl,
/// 1 sample
_1smpl, _1smpl,
} }
/// LED polarity
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum LedPolarity { pub enum LedPolarity {
/// Active high (a high output turns on the LED).
ActiveHigh, ActiveHigh,
/// Active low (a low output turns on the LED).
ActiveLow, ActiveLow,
} }

View File

@ -1,10 +1,12 @@
//! Quad Serial Peripheral Interface (QSPI) flash driver.
#![macro_use] #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
use core::ptr; use core::ptr;
use core::task::Poll; use core::task::Poll;
use embassy_hal_common::drop::DropBomb; use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_hal_common::{into_ref, PeripheralRef};
use crate::gpio::{self, Pin as GpioPin}; use crate::gpio::{self, Pin as GpioPin};
@ -15,6 +17,7 @@ pub use crate::pac::qspi::ifconfig0::{
pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode; pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode;
use crate::{pac, Peripheral}; use crate::{pac, Peripheral};
/// Deep power-down config.
pub struct DeepPowerDownConfig { pub struct DeepPowerDownConfig {
/// Time required for entering DPM, in units of 16us /// Time required for entering DPM, in units of 16us
pub enter_time: u16, pub enter_time: u16,
@ -22,37 +25,62 @@ pub struct DeepPowerDownConfig {
pub exit_time: u16, pub exit_time: u16,
} }
/// QSPI bus frequency.
pub enum Frequency { pub enum Frequency {
/// 32 Mhz
M32 = 0, M32 = 0,
/// 16 Mhz
M16 = 1, M16 = 1,
/// 10.7 Mhz
M10_7 = 2, M10_7 = 2,
/// 8 Mhz
M8 = 3, M8 = 3,
/// 6.4 Mhz
M6_4 = 4, M6_4 = 4,
/// 5.3 Mhz
M5_3 = 5, M5_3 = 5,
/// 4.6 Mhz
M4_6 = 6, M4_6 = 6,
/// 4 Mhz
M4 = 7, M4 = 7,
/// 3.6 Mhz
M3_6 = 8, M3_6 = 8,
/// 3.2 Mhz
M3_2 = 9, M3_2 = 9,
/// 2.9 Mhz
M2_9 = 10, M2_9 = 10,
/// 2.7 Mhz
M2_7 = 11, M2_7 = 11,
/// 2.5 Mhz
M2_5 = 12, M2_5 = 12,
/// 2.3 Mhz
M2_3 = 13, M2_3 = 13,
/// 2.1 Mhz
M2_1 = 14, M2_1 = 14,
/// 2 Mhz
M2 = 15, M2 = 15,
} }
/// QSPI config.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// XIP offset.
pub xip_offset: u32, pub xip_offset: u32,
/// Opcode used for read operations.
pub read_opcode: ReadOpcode, pub read_opcode: ReadOpcode,
/// Opcode used for write operations.
pub write_opcode: WriteOpcode, pub write_opcode: WriteOpcode,
/// Page size for write operations.
pub write_page_size: WritePageSize, pub write_page_size: WritePageSize,
/// Configuration for deep power down. If None, deep power down is disabled.
pub deep_power_down: Option<DeepPowerDownConfig>, pub deep_power_down: Option<DeepPowerDownConfig>,
/// QSPI bus frequency.
pub frequency: Frequency, pub frequency: Frequency,
/// Value is specified in number of 16 MHz periods (62.5 ns) /// Value is specified in number of 16 MHz periods (62.5 ns)
pub sck_delay: u8, pub sck_delay: u8,
/// Whether data is captured on the clock rising edge and data is output on a falling edge (MODE0) or vice-versa (MODE3) /// Whether data is captured on the clock rising edge and data is output on a falling edge (MODE0) or vice-versa (MODE3)
pub spi_mode: SpiMode, pub spi_mode: SpiMode,
/// Addressing mode (24-bit or 32-bit)
pub address_mode: AddressMode, pub address_mode: AddressMode,
} }
@ -72,20 +100,24 @@ impl Default for Config {
} }
} }
/// Error
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Operation address was out of bounds.
OutOfBounds, OutOfBounds,
// TODO add "not in data memory" error and check for it // TODO add "not in data memory" error and check for it
} }
/// QSPI flash driver.
pub struct Qspi<'d, T: Instance, const FLASH_SIZE: usize> { pub struct Qspi<'d, T: Instance, const FLASH_SIZE: usize> {
irq: PeripheralRef<'d, T::Interrupt>, irq: PeripheralRef<'d, T::Interrupt>,
dpm_enabled: bool, dpm_enabled: bool,
} }
impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
/// Create a new QSPI driver.
pub fn new( pub fn new(
_qspi: impl Peripheral<P = T> + 'd, _qspi: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -158,7 +190,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
// Enable it // Enable it
r.enable.write(|w| w.enable().enabled()); r.enable.write(|w| w.enable().enabled());
let mut res = Self { let res = Self {
dpm_enabled: config.deep_power_down.is_some(), dpm_enabled: config.deep_power_down.is_some(),
irq, irq,
}; };
@ -168,7 +200,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
r.tasks_activate.write(|w| w.tasks_activate().bit(true)); r.tasks_activate.write(|w| w.tasks_activate().bit(true));
res.blocking_wait_ready(); Self::blocking_wait_ready();
res res
} }
@ -183,8 +215,9 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
} }
} }
/// Do a custom QSPI instruction.
pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> {
let bomb = DropBomb::new(); let ondrop = OnDrop::new(Self::blocking_wait_ready);
let len = core::cmp::max(req.len(), resp.len()) as u8; let len = core::cmp::max(req.len(), resp.len()) as u8;
self.custom_instruction_start(opcode, req, len)?; self.custom_instruction_start(opcode, req, len)?;
@ -193,16 +226,17 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
self.custom_instruction_finish(resp)?; self.custom_instruction_finish(resp)?;
bomb.defuse(); ondrop.defuse();
Ok(()) Ok(())
} }
/// Do a custom QSPI instruction, blocking version.
pub fn blocking_custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { pub fn blocking_custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> {
let len = core::cmp::max(req.len(), resp.len()) as u8; let len = core::cmp::max(req.len(), resp.len()) as u8;
self.custom_instruction_start(opcode, req, len)?; self.custom_instruction_start(opcode, req, len)?;
self.blocking_wait_ready(); Self::blocking_wait_ready();
self.custom_instruction_finish(resp)?; self.custom_instruction_finish(resp)?;
@ -278,7 +312,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
.await .await
} }
fn blocking_wait_ready(&mut self) { fn blocking_wait_ready() {
loop { loop {
let r = T::regs(); let r = T::regs();
if r.events_ready.read().bits() != 0 { if r.events_ready.read().bits() != 0 {
@ -346,54 +380,60 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
Ok(()) Ok(())
} }
/// Read data from the flash memory.
pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> {
let bomb = DropBomb::new(); let ondrop = OnDrop::new(Self::blocking_wait_ready);
self.start_read(address, data)?; self.start_read(address, data)?;
self.wait_ready().await; self.wait_ready().await;
bomb.defuse(); ondrop.defuse();
Ok(()) Ok(())
} }
/// Write data to the flash memory.
pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> {
let bomb = DropBomb::new(); let ondrop = OnDrop::new(Self::blocking_wait_ready);
self.start_write(address, data)?; self.start_write(address, data)?;
self.wait_ready().await; self.wait_ready().await;
bomb.defuse(); ondrop.defuse();
Ok(()) Ok(())
} }
/// Erase a sector on the flash memory.
pub async fn erase(&mut self, address: usize) -> Result<(), Error> { pub async fn erase(&mut self, address: usize) -> Result<(), Error> {
let bomb = DropBomb::new(); let ondrop = OnDrop::new(Self::blocking_wait_ready);
self.start_erase(address)?; self.start_erase(address)?;
self.wait_ready().await; self.wait_ready().await;
bomb.defuse(); ondrop.defuse();
Ok(()) Ok(())
} }
/// Read data from the flash memory, blocking version.
pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> {
self.start_read(address, data)?; self.start_read(address, data)?;
self.blocking_wait_ready(); Self::blocking_wait_ready();
Ok(()) Ok(())
} }
/// Write data to the flash memory, blocking version.
pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> {
self.start_write(address, data)?; self.start_write(address, data)?;
self.blocking_wait_ready(); Self::blocking_wait_ready();
Ok(()) Ok(())
} }
/// Erase a sector on the flash memory, blocking version.
pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> { pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> {
self.start_erase(address)?; self.start_erase(address)?;
self.blocking_wait_ready(); Self::blocking_wait_ready();
Ok(()) Ok(())
} }
} }
@ -547,7 +587,9 @@ pub(crate) mod sealed {
} }
} }
/// QSPI peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }

View File

@ -1,3 +1,5 @@
//! Random Number Generator (RNG) driver.
use core::future::poll_fn; use core::future::poll_fn;
use core::ptr; use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering}; use core::sync::atomic::{AtomicPtr, Ordering};
@ -128,10 +130,11 @@ impl<'d> Rng<'d> {
/// However, this makes the generation of numbers slower. /// However, this makes the generation of numbers slower.
/// ///
/// Defaults to disabled. /// Defaults to disabled.
pub fn bias_correction(&self, enable: bool) { pub fn set_bias_correction(&self, enable: bool) {
RNG::regs().config.write(|w| w.dercen().bit(enable)) RNG::regs().config.write(|w| w.dercen().bit(enable))
} }
/// Fill the buffer with random bytes.
pub async fn fill_bytes(&mut self, dest: &mut [u8]) { pub async fn fill_bytes(&mut self, dest: &mut [u8]) {
if dest.len() == 0 { if dest.len() == 0 {
return; // Nothing to fill return; // Nothing to fill
@ -175,6 +178,7 @@ impl<'d> Rng<'d> {
drop(on_drop); drop(on_drop);
} }
/// Fill the buffer with random bytes, blocking version.
pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) {
self.start(); self.start();

View File

@ -1,3 +1,5 @@
//! Successive Approximation Analog-to-Digital Converter (SAADC) driver.
#![macro_use] #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
@ -20,6 +22,7 @@ use crate::ppi::{ConfigurableChannel, Event, Ppi, Task};
use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::timer::{Frequency, Instance as TimerInstance, Timer};
use crate::{interrupt, pac, peripherals, Peripheral}; use crate::{interrupt, pac, peripherals, Peripheral};
/// SAADC error
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
@ -102,17 +105,17 @@ impl<'d> ChannelConfig<'d> {
} }
} }
/// The state of a continuously running sampler. While it reflects /// Value returned by the SAADC callback, deciding what happens next.
/// the progress of a sampler, it also signals what should be done
/// next. For example, if the sampler has stopped then the Saadc implementation
/// can then tear down its infrastructure.
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum SamplerState { pub enum CallbackResult {
Sampled, /// The SAADC should keep sampling and calling the callback.
Stopped, Continue,
/// The SAADC should stop sampling, and return.
Stop,
} }
impl<'d, const N: usize> Saadc<'d, N> { impl<'d, const N: usize> Saadc<'d, N> {
/// Create a new SAADC driver.
pub fn new( pub fn new(
saadc: impl Peripheral<P = peripherals::SAADC> + 'd, saadc: impl Peripheral<P = peripherals::SAADC> + 'd,
irq: impl Peripheral<P = interrupt::SAADC> + 'd, irq: impl Peripheral<P = interrupt::SAADC> + 'd,
@ -285,7 +288,7 @@ impl<'d, const N: usize> Saadc<'d, N> {
/// free the buffers from being used by the peripheral. Cancellation will /// free the buffers from being used by the peripheral. Cancellation will
/// also cause the sampling to be stopped. /// also cause the sampling to be stopped.
pub async fn run_task_sampler<S, T: TimerInstance, const N0: usize>( pub async fn run_task_sampler<F, T: TimerInstance, const N0: usize>(
&mut self, &mut self,
timer: &mut T, timer: &mut T,
ppi_ch1: &mut impl ConfigurableChannel, ppi_ch1: &mut impl ConfigurableChannel,
@ -293,9 +296,9 @@ impl<'d, const N: usize> Saadc<'d, N> {
frequency: Frequency, frequency: Frequency,
sample_counter: u32, sample_counter: u32,
bufs: &mut [[[i16; N]; N0]; 2], bufs: &mut [[[i16; N]; N0]; 2],
sampler: S, callback: F,
) where ) where
S: FnMut(&[[i16; N]]) -> SamplerState, F: FnMut(&[[i16; N]]) -> CallbackResult,
{ {
let r = Self::regs(); let r = Self::regs();
@ -321,20 +324,20 @@ impl<'d, const N: usize> Saadc<'d, N> {
|| { || {
sample_ppi.enable(); sample_ppi.enable();
}, },
sampler, callback,
) )
.await; .await;
} }
async fn run_sampler<I, S, const N0: usize>( async fn run_sampler<I, F, const N0: usize>(
&mut self, &mut self,
bufs: &mut [[[i16; N]; N0]; 2], bufs: &mut [[[i16; N]; N0]; 2],
sample_rate_divisor: Option<u16>, sample_rate_divisor: Option<u16>,
mut init: I, mut init: I,
mut sampler: S, mut callback: F,
) where ) where
I: FnMut(), I: FnMut(),
S: FnMut(&[[i16; N]]) -> SamplerState, F: FnMut(&[[i16; N]]) -> CallbackResult,
{ {
// In case the future is dropped, stop the task and wait for it to end. // In case the future is dropped, stop the task and wait for it to end.
let on_drop = OnDrop::new(Self::stop_sampling_immediately); let on_drop = OnDrop::new(Self::stop_sampling_immediately);
@ -395,12 +398,15 @@ impl<'d, const N: usize> Saadc<'d, N> {
r.events_end.reset(); r.events_end.reset();
r.intenset.write(|w| w.end().set()); r.intenset.write(|w| w.end().set());
if sampler(&bufs[current_buffer]) == SamplerState::Sampled { match callback(&bufs[current_buffer]) {
CallbackResult::Continue => {
let next_buffer = 1 - current_buffer; let next_buffer = 1 - current_buffer;
current_buffer = next_buffer; current_buffer = next_buffer;
} else { }
CallbackResult::Stop => {
return Poll::Ready(()); return Poll::Ready(());
}; }
}
} }
if r.events_started.read().bits() != 0 { if r.events_started.read().bits() != 0 {
@ -458,7 +464,7 @@ impl<'d> Saadc<'d, 1> {
sample_rate_divisor: u16, sample_rate_divisor: u16,
sampler: S, sampler: S,
) where ) where
S: FnMut(&[[i16; 1]]) -> SamplerState, S: FnMut(&[[i16; 1]]) -> CallbackResult,
{ {
self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await; self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await;
} }
@ -658,6 +664,10 @@ pub(crate) mod sealed {
/// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal. /// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal.
pub trait Input: sealed::Input + Into<AnyInput> + Peripheral<P = Self> + Sized + 'static { pub trait Input: sealed::Input + Into<AnyInput> + Peripheral<P = Self> + Sized + 'static {
/// Convert this SAADC input to a type-erased `AnyInput`.
///
/// This allows using several inputs in situations that might require
/// them to be the same type, like putting them in an array.
fn degrade_saadc(self) -> AnyInput { fn degrade_saadc(self) -> AnyInput {
AnyInput { AnyInput {
channel: self.channel(), channel: self.channel(),
@ -665,6 +675,10 @@ pub trait Input: sealed::Input + Into<AnyInput> + Peripheral<P = Self> + Sized +
} }
} }
/// A type-erased SAADC input.
///
/// This allows using several inputs in situations that might require
/// them to be the same type, like putting them in an array.
pub struct AnyInput { pub struct AnyInput {
channel: InputChannel, channel: InputChannel,
} }

View File

@ -1,3 +1,5 @@
//! Serial Peripheral Instance in master mode (SPIM) driver.
#![macro_use] #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
@ -16,27 +18,37 @@ use crate::interrupt::{Interrupt, InterruptExt};
use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut};
use crate::{pac, Peripheral}; use crate::{pac, Peripheral};
/// SPIM error
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// TX buffer was too long.
TxBufferTooLong, TxBufferTooLong,
/// RX buffer was too long.
RxBufferTooLong, RxBufferTooLong,
/// EasyDMA can only read from data memory, read only buffers in flash will fail. /// EasyDMA can only read from data memory, read only buffers in flash will fail.
DMABufferNotInDataMemory, BufferNotInRAM,
} }
/// Interface for the SPIM peripheral using EasyDMA to offload the transmission and reception workload. /// SPIM driver.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Spim<'d, T: Instance> { pub struct Spim<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
} }
/// SPIM configuration.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// Frequency
pub frequency: Frequency, pub frequency: Frequency,
/// SPI mode
pub mode: Mode, pub mode: Mode,
/// Overread character.
///
/// When doing bidirectional transfers, if the TX buffer is shorter than the RX buffer,
/// this byte will be transmitted in the MOSI line for the left-over bytes.
pub orc: u8, pub orc: u8,
} }
@ -51,6 +63,7 @@ impl Default for Config {
} }
impl<'d, T: Instance> Spim<'d, T> { impl<'d, T: Instance> Spim<'d, T> {
/// Create a new SPIM driver.
pub fn new( pub fn new(
spim: impl Peripheral<P = T> + 'd, spim: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -70,6 +83,7 @@ impl<'d, T: Instance> Spim<'d, T> {
) )
} }
/// Create a new SPIM driver, capable of TX only (MOSI only).
pub fn new_txonly( pub fn new_txonly(
spim: impl Peripheral<P = T> + 'd, spim: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -81,6 +95,7 @@ impl<'d, T: Instance> Spim<'d, T> {
Self::new_inner(spim, irq, sck.map_into(), None, Some(mosi.map_into()), config) Self::new_inner(spim, irq, sck.map_into(), None, Some(mosi.map_into()), config)
} }
/// Create a new SPIM driver, capable of RX only (MISO only).
pub fn new_rxonly( pub fn new_rxonly(
spim: impl Peripheral<P = T> + 'd, spim: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -194,7 +209,7 @@ impl<'d, T: Instance> Spim<'d, T> {
} }
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(tx, Error::BufferNotInRAM)?;
// NOTE: RAM slice check for rx is not necessary, as a mutable // NOTE: RAM slice check for rx is not necessary, as a mutable
// slice can only be built from data located in RAM. // slice can only be built from data located in RAM.
@ -236,7 +251,7 @@ impl<'d, T: Instance> Spim<'d, T> {
fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> {
match self.blocking_inner_from_ram(rx, tx) { match self.blocking_inner_from_ram(rx, tx) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying SPIM tx buffer into RAM for DMA"); trace!("Copying SPIM tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()];
tx_ram_buf.copy_from_slice(tx); tx_ram_buf.copy_from_slice(tx);
@ -268,7 +283,7 @@ impl<'d, T: Instance> Spim<'d, T> {
async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> {
match self.async_inner_from_ram(rx, tx).await { match self.async_inner_from_ram(rx, tx).await {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying SPIM tx buffer into RAM for DMA"); trace!("Copying SPIM tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()];
tx_ram_buf.copy_from_slice(tx); tx_ram_buf.copy_from_slice(tx);
@ -385,7 +400,9 @@ pub(crate) mod sealed {
} }
} }
/// SPIM peripheral instance
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }
@ -437,7 +454,7 @@ mod eh1 {
match *self { match *self {
Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
Self::DMABufferNotInDataMemory => embedded_hal_1::spi::ErrorKind::Other, Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other,
} }
} }
} }

View File

@ -1,3 +1,5 @@
//! Serial Peripheral Instance in slave mode (SPIS) driver.
#![macro_use] #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
@ -14,28 +16,43 @@ use crate::interrupt::{Interrupt, InterruptExt};
use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut};
use crate::{pac, Peripheral}; use crate::{pac, Peripheral};
/// SPIS error
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// TX buffer was too long.
TxBufferTooLong, TxBufferTooLong,
/// RX buffer was too long.
RxBufferTooLong, RxBufferTooLong,
/// EasyDMA can only read from data memory, read only buffers in flash will fail. /// EasyDMA can only read from data memory, read only buffers in flash will fail.
DMABufferNotInDataMemory, BufferNotInRAM,
} }
/// Interface for the SPIS peripheral using EasyDMA to offload the transmission and reception workload. /// SPIS driver.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Spis<'d, T: Instance> { pub struct Spis<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
} }
/// SPIS configuration.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// SPI mode
pub mode: Mode, pub mode: Mode,
/// Overread character.
///
/// If the master keeps clocking the bus after all the bytes in the TX buffer have
/// already been transmitted, this byte will be constantly transmitted in the MISO line.
pub orc: u8, pub orc: u8,
/// Default byte.
///
/// This is the byte clocked out in the MISO line for ignored transactions (if the master
/// sets CSN low while the semaphore is owned by the firmware)
pub def: u8, pub def: u8,
/// Automatically make the firmware side acquire the semaphore on transfer end.
pub auto_acquire: bool, pub auto_acquire: bool,
} }
@ -51,6 +68,7 @@ impl Default for Config {
} }
impl<'d, T: Instance> Spis<'d, T> { impl<'d, T: Instance> Spis<'d, T> {
/// Create a new SPIS driver.
pub fn new( pub fn new(
spis: impl Peripheral<P = T> + 'd, spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -72,6 +90,7 @@ impl<'d, T: Instance> Spis<'d, T> {
) )
} }
/// Create a new SPIS driver, capable of TX only (MISO only).
pub fn new_txonly( pub fn new_txonly(
spis: impl Peripheral<P = T> + 'd, spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -92,6 +111,7 @@ impl<'d, T: Instance> Spis<'d, T> {
) )
} }
/// Create a new SPIS driver, capable of RX only (MOSI only).
pub fn new_rxonly( pub fn new_rxonly(
spis: impl Peripheral<P = T> + 'd, spis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -212,7 +232,7 @@ impl<'d, T: Instance> Spis<'d, T> {
} }
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(tx, Error::BufferNotInRAM)?;
// NOTE: RAM slice check for rx is not necessary, as a mutable // NOTE: RAM slice check for rx is not necessary, as a mutable
// slice can only be built from data located in RAM. // slice can only be built from data located in RAM.
@ -267,7 +287,7 @@ impl<'d, T: Instance> Spis<'d, T> {
fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> {
match self.blocking_inner_from_ram(rx, tx) { match self.blocking_inner_from_ram(rx, tx) {
Ok(n) => Ok(n), Ok(n) => Ok(n),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying SPIS tx buffer into RAM for DMA"); trace!("Copying SPIS tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()];
tx_ram_buf.copy_from_slice(tx); tx_ram_buf.copy_from_slice(tx);
@ -330,7 +350,7 @@ impl<'d, T: Instance> Spis<'d, T> {
async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> {
match self.async_inner_from_ram(rx, tx).await { match self.async_inner_from_ram(rx, tx).await {
Ok(n) => Ok(n), Ok(n) => Ok(n),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying SPIS tx buffer into RAM for DMA"); trace!("Copying SPIS tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()];
tx_ram_buf.copy_from_slice(tx); tx_ram_buf.copy_from_slice(tx);
@ -468,7 +488,9 @@ pub(crate) mod sealed {
} }
} }
/// SPIS peripheral instance
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }

View File

@ -1,4 +1,4 @@
//! Temperature sensor interface. //! Builtin temperature sensor driver.
use core::future::poll_fn; use core::future::poll_fn;
use core::task::Poll; use core::task::Poll;
@ -12,7 +12,7 @@ use crate::interrupt::InterruptExt;
use crate::peripherals::TEMP; use crate::peripherals::TEMP;
use crate::{interrupt, pac, Peripheral}; use crate::{interrupt, pac, Peripheral};
/// Integrated temperature sensor. /// Builtin temperature sensor driver.
pub struct Temp<'d> { pub struct Temp<'d> {
_irq: PeripheralRef<'d, interrupt::TEMP>, _irq: PeripheralRef<'d, interrupt::TEMP>,
} }
@ -20,6 +20,7 @@ pub struct Temp<'d> {
static WAKER: AtomicWaker = AtomicWaker::new(); static WAKER: AtomicWaker = AtomicWaker::new();
impl<'d> Temp<'d> { impl<'d> Temp<'d> {
/// Create a new temperature sensor driver.
pub fn new(_t: impl Peripheral<P = TEMP> + 'd, irq: impl Peripheral<P = interrupt::TEMP> + 'd) -> Self { pub fn new(_t: impl Peripheral<P = TEMP> + 'd, irq: impl Peripheral<P = interrupt::TEMP> + 'd) -> Self {
into_ref!(_t, irq); into_ref!(_t, irq);

View File

@ -1,3 +1,9 @@
//! Timer driver.
//!
//! Important note! This driver is very low level. For most time-related use cases, like
//! "sleep for X seconds", "do something every X seconds", or measuring time, you should
//! use [`embassy-time`](https://crates.io/crates/embassy-time) instead!
#![macro_use] #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
@ -28,9 +34,13 @@ pub(crate) mod sealed {
pub trait TimerType {} pub trait TimerType {}
} }
/// Basic Timer instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }
/// Extended timer instance.
pub trait ExtendedInstance: Instance + sealed::ExtendedInstance {} pub trait ExtendedInstance: Instance + sealed::ExtendedInstance {}
macro_rules! impl_timer { macro_rules! impl_timer {
@ -61,18 +71,28 @@ macro_rules! impl_timer {
}; };
} }
/// Timer frequency
#[repr(u8)] #[repr(u8)]
pub enum Frequency { pub enum Frequency {
// I'd prefer not to prefix these with `F`, but Rust identifiers can't start with digits. /// 16MHz
F16MHz = 0, F16MHz = 0,
/// 8MHz
F8MHz = 1, F8MHz = 1,
/// 4MHz
F4MHz = 2, F4MHz = 2,
/// 2MHz
F2MHz = 3, F2MHz = 3,
/// 1MHz
F1MHz = 4, F1MHz = 4,
/// 500kHz
F500kHz = 5, F500kHz = 5,
/// 250kHz
F250kHz = 6, F250kHz = 6,
/// 125kHz
F125kHz = 7, F125kHz = 7,
/// 62500Hz
F62500Hz = 8, F62500Hz = 8,
/// 31250Hz
F31250Hz = 9, F31250Hz = 9,
} }
@ -86,7 +106,10 @@ pub enum Frequency {
pub trait TimerType: sealed::TimerType {} pub trait TimerType: sealed::TimerType {}
/// Marker type indicating the timer driver can await expiration (it owns the timer interrupt).
pub enum Awaitable {} pub enum Awaitable {}
/// Marker type indicating the timer driver cannot await expiration (it does not own the timer interrupt).
pub enum NotAwaitable {} pub enum NotAwaitable {}
impl sealed::TimerType for Awaitable {} impl sealed::TimerType for Awaitable {}
@ -94,12 +117,14 @@ impl sealed::TimerType for NotAwaitable {}
impl TimerType for Awaitable {} impl TimerType for Awaitable {}
impl TimerType for NotAwaitable {} impl TimerType for NotAwaitable {}
/// Timer driver.
pub struct Timer<'d, T: Instance, I: TimerType = NotAwaitable> { pub struct Timer<'d, T: Instance, I: TimerType = NotAwaitable> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
_i: PhantomData<I>, _i: PhantomData<I>,
} }
impl<'d, T: Instance> Timer<'d, T, Awaitable> { impl<'d, T: Instance> Timer<'d, T, Awaitable> {
/// Create a new async-capable timer driver.
pub fn new_awaitable(timer: impl Peripheral<P = T> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd) -> Self { pub fn new_awaitable(timer: impl Peripheral<P = T> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd) -> Self {
into_ref!(irq); into_ref!(irq);
@ -107,16 +132,17 @@ impl<'d, T: Instance> Timer<'d, T, Awaitable> {
irq.unpend(); irq.unpend();
irq.enable(); irq.enable();
Self::new_irqless(timer) Self::new_inner(timer)
} }
} }
impl<'d, T: Instance> Timer<'d, T, NotAwaitable> { impl<'d, T: Instance> Timer<'d, T, NotAwaitable> {
/// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. /// Create a `Timer` driver without an interrupt, meaning `Cc::wait` won't work.
/// ///
/// This can be useful for triggering tasks via PPI /// This can be useful for triggering tasks via PPI
/// `Uarte` uses this internally. /// `Uarte` uses this internally.
pub fn new(timer: impl Peripheral<P = T> + 'd) -> Self { pub fn new(timer: impl Peripheral<P = T> + 'd) -> Self {
Self::new_irqless(timer) Self::new_inner(timer)
} }
} }
@ -124,7 +150,7 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> {
/// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work.
/// ///
/// This is used by the public constructors. /// This is used by the public constructors.
fn new_irqless(timer: impl Peripheral<P = T> + 'd) -> Self { fn new_inner(timer: impl Peripheral<P = T> + 'd) -> Self {
into_ref!(timer); into_ref!(timer);
let regs = T::regs(); let regs = T::regs();

View File

@ -1,11 +1,7 @@
//! I2C-compatible Two Wire Interface in master mode (TWIM) driver.
#![macro_use] #![macro_use]
//! HAL interface to the TWIM peripheral.
//!
//! See product specification:
//!
//! - nRF52832: Section 33
//! - nRF52840: Section 6.31
use core::future::{poll_fn, Future}; use core::future::{poll_fn, Future};
use core::sync::atomic::compiler_fence; use core::sync::atomic::compiler_fence;
use core::sync::atomic::Ordering::SeqCst; use core::sync::atomic::Ordering::SeqCst;
@ -23,22 +19,39 @@ use crate::interrupt::{Interrupt, InterruptExt};
use crate::util::{slice_in_ram, slice_in_ram_or}; use crate::util::{slice_in_ram, slice_in_ram_or};
use crate::{gpio, pac, Peripheral}; use crate::{gpio, pac, Peripheral};
/// TWI frequency
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Frequency { pub enum Frequency {
#[doc = "26738688: 100 kbps"] /// 100 kbps
K100 = 26738688, K100 = 26738688,
#[doc = "67108864: 250 kbps"] /// 250 kbps
K250 = 67108864, K250 = 67108864,
#[doc = "104857600: 400 kbps"] /// 400 kbps
K400 = 104857600, K400 = 104857600,
} }
/// TWIM config.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// Frequency
pub frequency: Frequency, pub frequency: Frequency,
/// Enable high drive for the SDA line.
pub sda_high_drive: bool, pub sda_high_drive: bool,
/// Enable internal pullup for the SDA line.
///
/// Note that using external pullups is recommended for I2C, and
/// most boards already have them.
pub sda_pullup: bool, pub sda_pullup: bool,
/// Enable high drive for the SCL line.
pub scl_high_drive: bool, pub scl_high_drive: bool,
/// Enable internal pullup for the SCL line.
///
/// Note that using external pullups is recommended for I2C, and
/// most boards already have them.
pub scl_pullup: bool, pub scl_pullup: bool,
} }
@ -54,29 +67,38 @@ impl Default for Config {
} }
} }
/// TWI error.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// TX buffer was too long.
TxBufferTooLong, TxBufferTooLong,
/// RX buffer was too long.
RxBufferTooLong, RxBufferTooLong,
/// Data transmit failed.
Transmit, Transmit,
/// Data reception failed.
Receive, Receive,
DMABufferNotInDataMemory, /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
BufferNotInRAM,
/// Didn't receive an ACK bit after the address byte. Address might be wrong, or the i2c device chip might not be connected properly.
AddressNack, AddressNack,
/// Didn't receive an ACK bit after a data byte.
DataNack, DataNack,
/// Overrun error.
Overrun, Overrun,
/// Timeout error.
Timeout, Timeout,
} }
/// Interface to a TWIM instance using EasyDMA to offload the transmission and reception workload. /// TWI driver.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Twim<'d, T: Instance> { pub struct Twim<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
} }
impl<'d, T: Instance> Twim<'d, T> { impl<'d, T: Instance> Twim<'d, T> {
/// Create a new TWI driver.
pub fn new( pub fn new(
twim: impl Peripheral<P = T> + 'd, twim: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -153,7 +175,7 @@ impl<'d, T: Instance> Twim<'d, T> {
/// Set TX buffer, checking that it is in RAM and has suitable length. /// Set TX buffer, checking that it is in RAM and has suitable length.
unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
if buffer.len() > EASY_DMA_SIZE { if buffer.len() > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong); return Err(Error::TxBufferTooLong);
@ -233,7 +255,7 @@ impl<'d, T: Instance> Twim<'d, T> {
return Err(Error::DataNack); return Err(Error::DataNack);
} }
if err.overrun().is_received() { if err.overrun().is_received() {
return Err(Error::DataNack); return Err(Error::Overrun);
} }
Ok(()) Ok(())
} }
@ -435,7 +457,7 @@ impl<'d, T: Instance> Twim<'d, T> {
) -> Result<(), Error> { ) -> Result<(), Error> {
match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) { match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying TWIM tx buffer into RAM for DMA"); trace!("Copying TWIM tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()];
tx_ram_buf.copy_from_slice(wr_buffer); tx_ram_buf.copy_from_slice(wr_buffer);
@ -448,7 +470,7 @@ impl<'d, T: Instance> Twim<'d, T> {
fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> {
match self.setup_write_from_ram(address, wr_buffer, inten) { match self.setup_write_from_ram(address, wr_buffer, inten) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying TWIM tx buffer into RAM for DMA"); trace!("Copying TWIM tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()];
tx_ram_buf.copy_from_slice(wr_buffer); tx_ram_buf.copy_from_slice(wr_buffer);
@ -612,6 +634,10 @@ impl<'d, T: Instance> Twim<'d, T> {
// =========================================== // ===========================================
/// Read from an I2C slave.
///
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
self.setup_read(address, buffer, true)?; self.setup_read(address, buffer, true)?;
self.async_wait().await; self.async_wait().await;
@ -621,6 +647,10 @@ impl<'d, T: Instance> Twim<'d, T> {
Ok(()) Ok(())
} }
/// Write to an I2C slave.
///
/// The buffer must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> {
self.setup_write(address, buffer, true)?; self.setup_write(address, buffer, true)?;
self.async_wait().await; self.async_wait().await;
@ -640,6 +670,11 @@ impl<'d, T: Instance> Twim<'d, T> {
Ok(()) Ok(())
} }
/// Write data to an I2C slave, then read data from the slave without
/// triggering a stop condition between the two.
///
/// The buffers must have a length of at most 255 bytes on the nRF52832
/// and at most 65535 bytes on the nRF52840.
pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> {
self.setup_write_read(address, wr_buffer, rd_buffer, true)?; self.setup_write_read(address, wr_buffer, rd_buffer, true)?;
self.async_wait().await; self.async_wait().await;
@ -705,7 +740,9 @@ pub(crate) mod sealed {
} }
} }
/// TWIM peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }
@ -776,7 +813,7 @@ mod eh1 {
Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other,
Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other, Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other,
Self::Receive => embedded_hal_1::i2c::ErrorKind::Other, Self::Receive => embedded_hal_1::i2c::ErrorKind::Other,
Self::DMABufferNotInDataMemory => embedded_hal_1::i2c::ErrorKind::Other, Self::BufferNotInRAM => embedded_hal_1::i2c::ErrorKind::Other,
Self::AddressNack => { Self::AddressNack => {
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address)
} }

View File

@ -1,11 +1,7 @@
//! I2C-compatible Two Wire Interface in slave mode (TWIM) driver.
#![macro_use] #![macro_use]
//! HAL interface to the TWIS peripheral.
//!
//! See product specification:
//!
//! - nRF52832: Section 33
//! - nRF52840: Section 6.31
use core::future::{poll_fn, Future}; use core::future::{poll_fn, Future};
use core::sync::atomic::compiler_fence; use core::sync::atomic::compiler_fence;
use core::sync::atomic::Ordering::SeqCst; use core::sync::atomic::Ordering::SeqCst;
@ -22,14 +18,37 @@ use crate::interrupt::{Interrupt, InterruptExt};
use crate::util::slice_in_ram_or; use crate::util::slice_in_ram_or;
use crate::{gpio, pac, Peripheral}; use crate::{gpio, pac, Peripheral};
/// TWIS config.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// First address
pub address0: u8, pub address0: u8,
/// Second address, optional.
pub address1: Option<u8>, pub address1: Option<u8>,
/// Overread character.
///
/// If the master keeps clocking the bus after all the bytes in the TX buffer have
/// already been transmitted, this byte will be constantly transmitted.
pub orc: u8, pub orc: u8,
/// Enable high drive for the SDA line.
pub sda_high_drive: bool, pub sda_high_drive: bool,
/// Enable internal pullup for the SDA line.
///
/// Note that using external pullups is recommended for I2C, and
/// most boards already have them.
pub sda_pullup: bool, pub sda_pullup: bool,
/// Enable high drive for the SCL line.
pub scl_high_drive: bool, pub scl_high_drive: bool,
/// Enable internal pullup for the SCL line.
///
/// Note that using external pullups is recommended for I2C, and
/// most boards already have them.
pub scl_pullup: bool, pub scl_pullup: bool,
} }
@ -54,36 +73,48 @@ enum Status {
Write, Write,
} }
/// TWIS error.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// TX buffer was too long.
TxBufferTooLong, TxBufferTooLong,
/// RX buffer was too long.
RxBufferTooLong, RxBufferTooLong,
/// Didn't receive an ACK bit after a data byte.
DataNack, DataNack,
/// Bus error.
Bus, Bus,
DMABufferNotInDataMemory, /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
BufferNotInRAM,
/// Overflow
Overflow, Overflow,
/// Overread
OverRead, OverRead,
/// Timeout
Timeout, Timeout,
} }
/// Received command
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Command { pub enum Command {
/// Read
Read, Read,
/// Write+read
WriteRead(usize), WriteRead(usize),
/// Write
Write(usize), Write(usize),
} }
/// Interface to a TWIS instance using EasyDMA to offload the transmission and reception workload. /// TWIS driver.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Twis<'d, T: Instance> { pub struct Twis<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
} }
impl<'d, T: Instance> Twis<'d, T> { impl<'d, T: Instance> Twis<'d, T> {
/// Create a new TWIS driver.
pub fn new( pub fn new(
twis: impl Peripheral<P = T> + 'd, twis: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -174,7 +205,7 @@ impl<'d, T: Instance> Twis<'d, T> {
/// Set TX buffer, checking that it is in RAM and has suitable length. /// Set TX buffer, checking that it is in RAM and has suitable length.
unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
if buffer.len() > EASY_DMA_SIZE { if buffer.len() > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong); return Err(Error::TxBufferTooLong);
@ -535,7 +566,7 @@ impl<'d, T: Instance> Twis<'d, T> {
fn setup_respond(&mut self, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { fn setup_respond(&mut self, wr_buffer: &[u8], inten: bool) -> Result<(), Error> {
match self.setup_respond_from_ram(wr_buffer, inten) { match self.setup_respond_from_ram(wr_buffer, inten) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying TWIS tx buffer into RAM for DMA"); trace!("Copying TWIS tx buffer into RAM for DMA");
let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()];
tx_ram_buf.copy_from_slice(wr_buffer); tx_ram_buf.copy_from_slice(wr_buffer);
@ -737,7 +768,9 @@ pub(crate) mod sealed {
} }
} }
/// TWIS peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }

View File

@ -1,8 +1,6 @@
#![macro_use] //! Universal Asynchronous Receiver Transmitter (UART) driver.
//! Async UART
//! //!
//! Async UART is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. //! The UART driver is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte].
//! The [Uarte] here is useful for those use-cases where reading the UARTE peripheral is //! The [Uarte] here is useful for those use-cases where reading the UARTE peripheral is
//! exclusively awaited on. If the [Uarte] is required to be awaited on with some other future, //! exclusively awaited on. If the [Uarte] is required to be awaited on with some other future,
//! for example when using `futures_util::future::select`, then you should consider //! for example when using `futures_util::future::select`, then you should consider
@ -13,6 +11,8 @@
//! memory may be used given that buffers are passed in directly to its read and write //! memory may be used given that buffers are passed in directly to its read and write
//! methods. //! methods.
#![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll; use core::task::Poll;
@ -32,10 +32,13 @@ use crate::timer::{Frequency, Instance as TimerInstance, Timer};
use crate::util::slice_in_ram_or; use crate::util::slice_in_ram_or;
use crate::{pac, Peripheral}; use crate::{pac, Peripheral};
/// UARTE config.
#[derive(Clone)] #[derive(Clone)]
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// Parity bit.
pub parity: Parity, pub parity: Parity,
/// Baud rate.
pub baudrate: Baudrate, pub baudrate: Baudrate,
} }
@ -48,31 +51,33 @@ impl Default for Config {
} }
} }
/// UART error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Buffer was too long.
BufferTooLong, BufferTooLong,
DMABufferNotInDataMemory, /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
// TODO: add other error variants. BufferNotInRAM,
} }
/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. /// UARTE driver.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct Uarte<'d, T: Instance> { pub struct Uarte<'d, T: Instance> {
tx: UarteTx<'d, T>, tx: UarteTx<'d, T>,
rx: UarteRx<'d, T>, rx: UarteRx<'d, T>,
} }
/// Transmitter interface to the UARTE peripheral obtained /// Transmitter part of the UARTE driver.
/// via [Uarte]::split. ///
/// This can be obtained via [`Uarte::split`], or created directly.
pub struct UarteTx<'d, T: Instance> { pub struct UarteTx<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
} }
/// Receiver interface to the UARTE peripheral obtained /// Receiver part of the UARTE driver.
/// via [Uarte]::split. ///
/// This can be obtained via [`Uarte::split`], or created directly.
pub struct UarteRx<'d, T: Instance> { pub struct UarteRx<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
} }
@ -165,16 +170,16 @@ impl<'d, T: Instance> Uarte<'d, T> {
} }
} }
/// Split the Uarte into a transmitter and receiver, which is /// Split the Uarte into the transmitter and receiver parts.
/// particularly useful when having two tasks correlating to ///
/// transmitting and receiving. /// This is useful to concurrently transmit and receive from independent tasks.
pub fn split(self) -> (UarteTx<'d, T>, UarteRx<'d, T>) { pub fn split(self) -> (UarteTx<'d, T>, UarteRx<'d, T>) {
(self.tx, self.rx) (self.tx, self.rx)
} }
/// Split the Uarte into a transmitter and receiver that will /// Split the Uarte into the transmitter and receiver with idle support parts.
/// return on idle, which is determined as the time it takes ///
/// for two bytes to be received. /// This is useful to concurrently transmit and receive from independent tasks.
pub fn split_with_idle<U: TimerInstance>( pub fn split_with_idle<U: TimerInstance>(
self, self,
timer: impl Peripheral<P = U> + 'd, timer: impl Peripheral<P = U> + 'd,
@ -247,10 +252,12 @@ impl<'d, T: Instance> Uarte<'d, T> {
} }
} }
/// Read bytes until the buffer is filled.
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
self.rx.read(buffer).await self.rx.read(buffer).await
} }
/// Write all bytes in the buffer.
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
self.tx.write(buffer).await self.tx.write(buffer).await
} }
@ -260,10 +267,12 @@ impl<'d, T: Instance> Uarte<'d, T> {
self.tx.write_from_ram(buffer).await self.tx.write_from_ram(buffer).await
} }
/// Read bytes until the buffer is filled.
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
self.rx.blocking_read(buffer) self.rx.blocking_read(buffer)
} }
/// Write all bytes in the buffer.
pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> {
self.tx.blocking_write(buffer) self.tx.blocking_write(buffer)
} }
@ -355,10 +364,11 @@ impl<'d, T: Instance> UarteTx<'d, T> {
Self { _p: uarte } Self { _p: uarte }
} }
/// Write all bytes in the buffer.
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
match self.write_from_ram(buffer).await { match self.write_from_ram(buffer).await {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying UARTE tx buffer into RAM for DMA"); trace!("Copying UARTE tx buffer into RAM for DMA");
let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()];
ram_buf.copy_from_slice(buffer); ram_buf.copy_from_slice(buffer);
@ -368,12 +378,13 @@ impl<'d, T: Instance> UarteTx<'d, T> {
} }
} }
/// Same as [`write`](Self::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub async fn write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { pub async fn write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Ok(()); return Ok(());
} }
slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
if buffer.len() > EASY_DMA_SIZE { if buffer.len() > EASY_DMA_SIZE {
return Err(Error::BufferTooLong); return Err(Error::BufferTooLong);
} }
@ -423,10 +434,11 @@ impl<'d, T: Instance> UarteTx<'d, T> {
Ok(()) Ok(())
} }
/// Write all bytes in the buffer.
pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> {
match self.blocking_write_from_ram(buffer) { match self.blocking_write_from_ram(buffer) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(Error::DMABufferNotInDataMemory) => { Err(Error::BufferNotInRAM) => {
trace!("Copying UARTE tx buffer into RAM for DMA"); trace!("Copying UARTE tx buffer into RAM for DMA");
let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()];
ram_buf.copy_from_slice(buffer); ram_buf.copy_from_slice(buffer);
@ -436,12 +448,13 @@ impl<'d, T: Instance> UarteTx<'d, T> {
} }
} }
/// Same as [`write_from_ram`](Self::write_from_ram) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
pub fn blocking_write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { pub fn blocking_write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Ok(()); return Ok(());
} }
slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
if buffer.len() > EASY_DMA_SIZE { if buffer.len() > EASY_DMA_SIZE {
return Err(Error::BufferTooLong); return Err(Error::BufferTooLong);
} }
@ -549,6 +562,7 @@ impl<'d, T: Instance> UarteRx<'d, T> {
Self { _p: uarte } Self { _p: uarte }
} }
/// Read bytes until the buffer is filled.
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Ok(()); return Ok(());
@ -602,6 +616,7 @@ impl<'d, T: Instance> UarteRx<'d, T> {
Ok(()) Ok(())
} }
/// Read bytes until the buffer is filled.
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Ok(()); return Ok(());
@ -653,6 +668,9 @@ impl<'a, T: Instance> Drop for UarteRx<'a, T> {
} }
} }
/// Receiver part of the UARTE driver, with `read_until_idle` support.
///
/// This can be obtained via [`Uarte::split_with_idle`].
pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> { pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> {
rx: UarteRx<'d, T>, rx: UarteRx<'d, T>,
timer: Timer<'d, U>, timer: Timer<'d, U>,
@ -661,16 +679,21 @@ pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> {
} }
impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> {
/// Read bytes until the buffer is filled.
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
self.ppi_ch1.disable(); self.ppi_ch1.disable();
self.rx.read(buffer).await self.rx.read(buffer).await
} }
/// Read bytes until the buffer is filled.
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
self.ppi_ch1.disable(); self.ppi_ch1.disable();
self.rx.blocking_read(buffer) self.rx.blocking_read(buffer)
} }
/// Read bytes until the buffer is filled, or the line becomes idle.
///
/// Returns the amount of bytes read.
pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result<usize, Error> { pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Ok(0); return Ok(0);
@ -727,6 +750,9 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> {
Ok(n) Ok(n)
} }
/// Read bytes until the buffer is filled, or the line becomes idle.
///
/// Returns the amount of bytes read.
pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result<usize, Error> { pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
if buffer.len() == 0 { if buffer.len() == 0 {
return Ok(0); return Ok(0);
@ -860,7 +886,9 @@ pub(crate) mod sealed {
} }
} }
/// UARTE peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }
@ -919,7 +947,7 @@ mod eh1 {
fn kind(&self) -> embedded_hal_1::serial::ErrorKind { fn kind(&self) -> embedded_hal_1::serial::ErrorKind {
match *self { match *self {
Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other,
Self::DMABufferNotInDataMemory => embedded_hal_1::serial::ErrorKind::Other, Self::BufferNotInRAM => embedded_hal_1::serial::ErrorKind::Other,
} }
} }
} }

View File

@ -1,3 +1,5 @@
//! Universal Serial Bus (USB) driver.
#![macro_use] #![macro_use]
use core::future::poll_fn; use core::future::poll_fn;
@ -24,38 +26,38 @@ static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8];
static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8];
static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0);
/// Trait for detecting USB VBUS power.
///
/// There are multiple ways to detect USB power. The behavior /// There are multiple ways to detect USB power. The behavior
/// here provides a hook into determining whether it is. /// here provides a hook into determining whether it is.
pub trait UsbSupply { pub trait VbusDetect {
/// Report whether power is detected.
///
/// This is indicated by the `USBREGSTATUS.VBUSDETECT` register, or the
/// `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral.
fn is_usb_detected(&self) -> bool; fn is_usb_detected(&self) -> bool;
/// Wait until USB power is ready.
///
/// USB power ready is indicated by the `USBREGSTATUS.OUTPUTRDY` register, or the
/// `USBPWRRDY` event from the `POWER` peripheral.
async fn wait_power_ready(&mut self) -> Result<(), ()>; async fn wait_power_ready(&mut self) -> Result<(), ()>;
} }
pub struct Driver<'d, T: Instance, P: UsbSupply> { /// [`VbusDetect`] implementation using the native hardware POWER peripheral.
_p: PeripheralRef<'d, T>, ///
alloc_in: Allocator, /// Unsuitable for usage with the nRF softdevice, since it reserves exclusive acces
alloc_out: Allocator, /// to POWER. In that case, use [`VbusDetectSignal`].
usb_supply: P,
}
/// Uses the POWER peripheral to detect when power is available
/// for USB. Unsuitable for usage with the nRF softdevice.
#[cfg(not(feature = "_nrf5340-app"))] #[cfg(not(feature = "_nrf5340-app"))]
pub struct PowerUsb { pub struct HardwareVbusDetect {
_private: (), _private: (),
} }
/// Can be used to signal that power is available. Particularly suited for
/// use with the nRF softdevice.
pub struct SignalledSupply {
usb_detected: AtomicBool,
power_ready: AtomicBool,
}
static POWER_WAKER: AtomicWaker = NEW_AW; static POWER_WAKER: AtomicWaker = NEW_AW;
#[cfg(not(feature = "_nrf5340-app"))] #[cfg(not(feature = "_nrf5340-app"))]
impl PowerUsb { impl HardwareVbusDetect {
/// Create a new `VbusDetectNative`.
pub fn new(power_irq: impl Interrupt) -> Self { pub fn new(power_irq: impl Interrupt) -> Self {
let regs = unsafe { &*pac::POWER::ptr() }; let regs = unsafe { &*pac::POWER::ptr() };
@ -92,7 +94,7 @@ impl PowerUsb {
} }
#[cfg(not(feature = "_nrf5340-app"))] #[cfg(not(feature = "_nrf5340-app"))]
impl UsbSupply for PowerUsb { impl VbusDetect for HardwareVbusDetect {
fn is_usb_detected(&self) -> bool { fn is_usb_detected(&self) -> bool {
let regs = unsafe { &*pac::POWER::ptr() }; let regs = unsafe { &*pac::POWER::ptr() };
regs.usbregstatus.read().vbusdetect().is_vbus_present() regs.usbregstatus.read().vbusdetect().is_vbus_present()
@ -115,7 +117,20 @@ impl UsbSupply for PowerUsb {
} }
} }
impl SignalledSupply { /// Software-backed [`VbusDetect`] implementation.
///
/// This implementation does not interact with the hardware, it allows user code
/// to notify the power events by calling functions instead.
///
/// This is suitable for use with the nRF softdevice, by calling the functions
/// when the softdevice reports power-related events.
pub struct SoftwareVbusDetect {
usb_detected: AtomicBool,
power_ready: AtomicBool,
}
impl SoftwareVbusDetect {
/// Create a new `SoftwareVbusDetect`.
pub fn new(usb_detected: bool, power_ready: bool) -> Self { pub fn new(usb_detected: bool, power_ready: bool) -> Self {
BUS_WAKER.wake(); BUS_WAKER.wake();
@ -125,6 +140,9 @@ impl SignalledSupply {
} }
} }
/// Report whether power was detected.
///
/// Equivalent to the `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral.
pub fn detected(&self, detected: bool) { pub fn detected(&self, detected: bool) {
self.usb_detected.store(detected, Ordering::Relaxed); self.usb_detected.store(detected, Ordering::Relaxed);
self.power_ready.store(false, Ordering::Relaxed); self.power_ready.store(false, Ordering::Relaxed);
@ -132,13 +150,16 @@ impl SignalledSupply {
POWER_WAKER.wake(); POWER_WAKER.wake();
} }
/// Report when USB power is ready.
///
/// Equivalent to the `USBPWRRDY` event from the `POWER` peripheral.
pub fn ready(&self) { pub fn ready(&self) {
self.power_ready.store(true, Ordering::Relaxed); self.power_ready.store(true, Ordering::Relaxed);
POWER_WAKER.wake(); POWER_WAKER.wake();
} }
} }
impl UsbSupply for &SignalledSupply { impl VbusDetect for &SoftwareVbusDetect {
fn is_usb_detected(&self) -> bool { fn is_usb_detected(&self) -> bool {
self.usb_detected.load(Ordering::Relaxed) self.usb_detected.load(Ordering::Relaxed)
} }
@ -159,7 +180,16 @@ impl UsbSupply for &SignalledSupply {
} }
} }
impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { /// USB driver.
pub struct Driver<'d, T: Instance, P: VbusDetect> {
_p: PeripheralRef<'d, T>,
alloc_in: Allocator,
alloc_out: Allocator,
usb_supply: P,
}
impl<'d, T: Instance, P: VbusDetect> Driver<'d, T, P> {
/// Create a new USB driver.
pub fn new(usb: impl Peripheral<P = T> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd, usb_supply: P) -> Self { pub fn new(usb: impl Peripheral<P = T> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd, usb_supply: P) -> Self {
into_ref!(usb, irq); into_ref!(usb, irq);
irq.set_handler(Self::on_interrupt); irq.set_handler(Self::on_interrupt);
@ -225,7 +255,7 @@ impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> {
} }
} }
impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> { impl<'d, T: Instance, P: VbusDetect + 'd> driver::Driver<'d> for Driver<'d, T, P> {
type EndpointOut = Endpoint<'d, T, Out>; type EndpointOut = Endpoint<'d, T, Out>;
type EndpointIn = Endpoint<'d, T, In>; type EndpointIn = Endpoint<'d, T, In>;
type ControlPipe = ControlPipe<'d, T>; type ControlPipe = ControlPipe<'d, T>;
@ -235,7 +265,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
packet_size: u16, packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointIn, driver::EndpointAllocError> { ) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
let index = self.alloc_in.allocate(ep_type)?; let index = self.alloc_in.allocate(ep_type)?;
let ep_addr = EndpointAddress::from_parts(index, Direction::In); let ep_addr = EndpointAddress::from_parts(index, Direction::In);
@ -243,7 +273,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
addr: ep_addr, addr: ep_addr,
ep_type, ep_type,
max_packet_size: packet_size, max_packet_size: packet_size,
interval, interval_ms,
})) }))
} }
@ -251,7 +281,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
packet_size: u16, packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointOut, driver::EndpointAllocError> { ) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
let index = self.alloc_out.allocate(ep_type)?; let index = self.alloc_out.allocate(ep_type)?;
let ep_addr = EndpointAddress::from_parts(index, Direction::Out); let ep_addr = EndpointAddress::from_parts(index, Direction::Out);
@ -259,7 +289,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
addr: ep_addr, addr: ep_addr,
ep_type, ep_type,
max_packet_size: packet_size, max_packet_size: packet_size,
interval, interval_ms,
})) }))
} }
@ -278,13 +308,14 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
} }
} }
pub struct Bus<'d, T: Instance, P: UsbSupply> { /// USB bus.
pub struct Bus<'d, T: Instance, P: VbusDetect> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
power_available: bool, power_available: bool,
usb_supply: P, usb_supply: P,
} }
impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { impl<'d, T: Instance, P: VbusDetect> driver::Bus for Bus<'d, T, P> {
async fn enable(&mut self) { async fn enable(&mut self) {
let regs = T::regs(); let regs = T::regs();
@ -513,7 +544,10 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> {
} }
} }
/// Type-level marker for OUT endpoints.
pub enum Out {} pub enum Out {}
/// Type-level marker for IN endpoints.
pub enum In {} pub enum In {}
trait EndpointDir { trait EndpointDir {
@ -556,6 +590,7 @@ impl EndpointDir for Out {
} }
} }
/// USB endpoint.
pub struct Endpoint<'d, T: Instance, Dir> { pub struct Endpoint<'d, T: Instance, Dir> {
_phantom: PhantomData<(&'d mut T, Dir)>, _phantom: PhantomData<(&'d mut T, Dir)>,
info: EndpointInfo, info: EndpointInfo,
@ -715,6 +750,7 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
} }
} }
/// USB control pipe.
pub struct ControlPipe<'d, T: Instance> { pub struct ControlPipe<'d, T: Instance> {
_p: PeripheralRef<'d, T>, _p: PeripheralRef<'d, T>,
max_packet_size: u16, max_packet_size: u16,
@ -905,7 +941,9 @@ pub(crate) mod sealed {
} }
} }
/// USB peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
/// Interrupt for this peripheral.
type Interrupt: Interrupt; type Interrupt: Interrupt;
} }

View File

@ -1,4 +1,4 @@
//! HAL interface to the WDT peripheral. //! Watchdog Timer (WDT) driver.
//! //!
//! This HAL implements a basic watchdog timer with 1..=8 handles. //! This HAL implements a basic watchdog timer with 1..=8 handles.
//! Once the watchdog has been started, it cannot be stopped. //! Once the watchdog has been started, it cannot be stopped.
@ -8,6 +8,7 @@ use crate::peripherals;
const MIN_TICKS: u32 = 15; const MIN_TICKS: u32 = 15;
/// WDT configuration.
#[non_exhaustive] #[non_exhaustive]
pub struct Config { pub struct Config {
/// Number of 32768 Hz ticks in each watchdog period. /// Number of 32768 Hz ticks in each watchdog period.
@ -57,13 +58,13 @@ impl Default for Config {
} }
} }
/// An interface to the Watchdog. /// Watchdog driver.
pub struct Watchdog { pub struct Watchdog {
_private: (), _private: (),
} }
impl Watchdog { impl Watchdog {
/// Try to create a new watchdog instance from the peripheral. /// Try to create a new watchdog driver.
/// ///
/// This function will return an error if the watchdog is already active /// This function will return an error if the watchdog is already active
/// with a `config` different to the requested one, or a different number of /// with a `config` different to the requested one, or a different number of
@ -155,6 +156,7 @@ impl Watchdog {
} }
} }
/// Watchdog handle.
pub struct WatchdogHandle { pub struct WatchdogHandle {
index: u8, index: u8,
} }

View File

@ -151,6 +151,7 @@ fn copy_inner<'a, C: Channel>(
Transfer::new(ch) Transfer::new(ch)
} }
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Transfer<'a, C: Channel> { pub struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>, channel: PeripheralRef<'a, C>,
} }

View File

@ -16,7 +16,7 @@ const NEW_AW: AtomicWaker = AtomicWaker::new();
static INTERRUPT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; static INTERRUPT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT];
/// Represents a digital input or output level. /// Represents a digital input or output level.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Level { pub enum Level {
Low, Low,
High, High,
@ -193,6 +193,7 @@ unsafe fn IO_IRQ_BANK0() {
} }
} }
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct InputFuture<'a, T: Pin> { struct InputFuture<'a, T: Pin> {
pin: PeripheralRef<'a, T>, pin: PeripheralRef<'a, T>,
level: InterruptTrigger, level: InterruptTrigger,

View File

@ -120,6 +120,7 @@ unsafe fn PIO1_IRQ_0() {
} }
/// Future that waits for TX-FIFO to become writable /// Future that waits for TX-FIFO to become writable
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct FifoOutFuture<'a, PIO: PioInstance, SM: PioStateMachine + Unpin> { pub struct FifoOutFuture<'a, PIO: PioInstance, SM: PioStateMachine + Unpin> {
sm: &'a mut SM, sm: &'a mut SM,
pio: PhantomData<PIO>, pio: PhantomData<PIO>,
@ -182,6 +183,7 @@ impl<'d, PIO: PioInstance, SM: PioStateMachine + Unpin> Drop for FifoOutFuture<'
} }
/// Future that waits for RX-FIFO to become readable /// Future that waits for RX-FIFO to become readable
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct FifoInFuture<'a, PIO: PioInstance, SM: PioStateMachine> { pub struct FifoInFuture<'a, PIO: PioInstance, SM: PioStateMachine> {
sm: &'a mut SM, sm: &'a mut SM,
pio: PhantomData<PIO>, pio: PhantomData<PIO>,
@ -241,6 +243,7 @@ impl<'d, PIO: PioInstance, SM: PioStateMachine> Drop for FifoInFuture<'d, PIO, S
} }
/// Future that waits for IRQ /// Future that waits for IRQ
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct IrqFuture<PIO: PioInstance> { pub struct IrqFuture<PIO: PioInstance> {
pio: PhantomData<PIO>, pio: PhantomData<PIO>,
irq_no: u8, irq_no: u8,
@ -1174,7 +1177,7 @@ impl<const SM_NO: u8> SmInstance for SmInstanceBase<SM_NO> {
const SM_NO: u8 = SM_NO; const SM_NO: u8 = SM_NO;
} }
pub trait PioPeripherial: Sized { pub trait PioPeripheral: Sized {
type Pio: PioInstance; type Pio: PioInstance;
fn pio(&self) -> u8 { fn pio(&self) -> u8 {
let _ = self; let _ = self;
@ -1249,7 +1252,7 @@ pub type Sm3 = SmInstanceBase<3>;
macro_rules! impl_pio_sm { macro_rules! impl_pio_sm {
($name:ident, $pio:expr) => { ($name:ident, $pio:expr) => {
impl PioPeripherial for peripherals::$name { impl PioPeripheral for peripherals::$name {
type Pio = PioInstanceBase<$pio>; type Pio = PioInstanceBase<$pio>;
} }
}; };

View File

@ -194,13 +194,13 @@ impl<'d, T: Instance> Driver<'d, T> {
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> { ) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
trace!( trace!(
"allocating type={:?} mps={:?} interval={}, dir={:?}", "allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
ep_type, ep_type,
max_packet_size, max_packet_size,
interval, interval_ms,
D::dir() D::dir()
); );
@ -219,14 +219,16 @@ impl<'d, T: Instance> Driver<'d, T> {
let (index, ep) = index.ok_or(EndpointAllocError)?; let (index, ep) = index.ok_or(EndpointAllocError)?;
assert!(!ep.used); assert!(!ep.used);
if max_packet_size > 64 { // as per datasheet, the maximum buffer size is 64, except for isochronous
// endpoints, which are allowed to be up to 1023 bytes.
if (ep_type != EndpointType::Isochronous && max_packet_size > 64) || max_packet_size > 1023 {
warn!("max_packet_size too high: {}", max_packet_size); warn!("max_packet_size too high: {}", max_packet_size);
return Err(EndpointAllocError); return Err(EndpointAllocError);
} }
// ep mem addrs must be 64-byte aligned, so there's no point in trying // ep mem addrs must be 64-byte aligned, so there's no point in trying
// to allocate smaller chunks to save memory. // to allocate smaller chunks to save memory.
let len = 64; let len = (max_packet_size + 63) / 64 * 64;
let addr = self.ep_mem_free; let addr = self.ep_mem_free;
if addr + len > EP_MEMORY_SIZE as _ { if addr + len > EP_MEMORY_SIZE as _ {
@ -279,7 +281,7 @@ impl<'d, T: Instance> Driver<'d, T> {
addr: EndpointAddress::from_parts(index, D::dir()), addr: EndpointAddress::from_parts(index, D::dir()),
ep_type, ep_type,
max_packet_size, max_packet_size,
interval, interval_ms,
}, },
buf, buf,
}) })
@ -296,18 +298,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointIn, driver::EndpointAllocError> { ) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval) self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
} }
fn alloc_endpoint_out( fn alloc_endpoint_out(
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointOut, driver::EndpointAllocError> { ) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval) self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
} }
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {

View File

@ -17,7 +17,7 @@ flavors = [
{ regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" },
{ regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" },
{ regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32f42.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" },
@ -58,7 +58,7 @@ cortex-m = "0.7.6"
futures = { version = "0.3.17", default-features = false, features = ["async-await"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
rand_core = "0.6.3" rand_core = "0.6.3"
sdio-host = "0.5.0" sdio-host = "0.5.0"
embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "46d1b1c2ff13e31e282ec1e352421721694f126a", optional = true }
critical-section = "1.1" critical-section = "1.1"
atomic-polyfill = "1.0.1" atomic-polyfill = "1.0.1"
stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] }
@ -77,7 +77,6 @@ stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", default-features
[features] [features]
defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt"] defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt"]
sdmmc-rs = ["embedded-sdmmc"]
memory-x = ["stm32-metapac/memory-x"] memory-x = ["stm32-metapac/memory-x"]
subghz = [] subghz = []
exti = [] exti = []

View File

@ -192,6 +192,7 @@ mod low_level_api {
options.flow_ctrl == crate::dma::FlowControl::Dma, options.flow_ctrl == crate::dma::FlowControl::Dma,
"Peripheral flow control not supported" "Peripheral flow control not supported"
); );
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
let ch = dma.ch(channel_number as _); let ch = dma.ch(channel_number as _);

View File

@ -4,7 +4,7 @@ use core::task::Waker;
use embassy_cortex_m::interrupt::Priority; use embassy_cortex_m::interrupt::Priority;
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use super::{Burst, FlowControl, Request, TransferOptions, Word, WordSize}; use super::{Burst, FifoThreshold, FlowControl, Request, TransferOptions, Word, WordSize};
use crate::_generated::DMA_CHANNEL_COUNT; use crate::_generated::DMA_CHANNEL_COUNT;
use crate::interrupt::{Interrupt, InterruptExt}; use crate::interrupt::{Interrupt, InterruptExt};
use crate::pac::dma::{regs, vals}; use crate::pac::dma::{regs, vals};
@ -40,6 +40,17 @@ impl From<FlowControl> for vals::Pfctrl {
} }
} }
impl From<FifoThreshold> for vals::Fth {
fn from(value: FifoThreshold) -> Self {
match value {
FifoThreshold::Quarter => vals::Fth::QUARTER,
FifoThreshold::Half => vals::Fth::HALF,
FifoThreshold::ThreeQuarters => vals::Fth::THREEQUARTERS,
FifoThreshold::Full => vals::Fth::FULL,
}
}
}
struct ChannelState { struct ChannelState {
waker: AtomicWaker, waker: AtomicWaker,
} }
@ -236,6 +247,16 @@ mod low_level_api {
ch.par().write_value(peri_addr as u32); ch.par().write_value(peri_addr as u32);
ch.m0ar().write_value(mem_addr as u32); ch.m0ar().write_value(mem_addr as u32);
ch.ndtr().write_value(regs::Ndtr(mem_len as _)); ch.ndtr().write_value(regs::Ndtr(mem_len as _));
ch.fcr().write(|w| {
if let Some(fth) = options.fifo_threshold {
// FIFO mode
w.set_dmdis(vals::Dmdis::DISABLED);
w.set_fth(fth.into());
} else {
// Direct mode
w.set_dmdis(vals::Dmdis::ENABLED);
}
});
ch.cr().write(|w| { ch.cr().write(|w| {
w.set_dir(dir); w.set_dir(dir);
w.set_msize(data_size); w.set_msize(data_size);
@ -411,12 +432,8 @@ mod low_level_api {
} }
if isr.tcif(channel_num % 4) && cr.read().tcie() { if isr.tcif(channel_num % 4) && cr.read().tcie() {
if cr.read().dbm() == vals::Dbm::DISABLED { /* acknowledge transfer complete interrupt */
cr.write(|_| ()); // Disable channel with the default value.
} else {
// for double buffered mode, clear TCIF flag but do not stop the transfer
dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true));
}
STATE.channels[state_index].waker.wake(); STATE.channels[state_index].waker.wake();
} }
} }

View File

@ -176,8 +176,16 @@ mod low_level_api {
mem_len: usize, mem_len: usize,
incr_mem: bool, incr_mem: bool,
data_size: WordSize, data_size: WordSize,
_options: TransferOptions, options: TransferOptions,
) { ) {
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(
options.flow_ctrl == crate::dma::FlowControl::Dma,
"Peripheral flow control not supported"
);
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
// "Preceding reads and writes cannot be moved past subsequent writes." // "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::SeqCst); fence(Ordering::SeqCst);

View File

@ -186,6 +186,19 @@ pub enum FlowControl {
Peripheral, Peripheral,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FifoThreshold {
/// 1/4 full FIFO
Quarter,
/// 1/2 full FIFO
Half,
/// 3/4 full FIFO
ThreeQuarters,
/// Full FIFO
Full,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TransferOptions { pub struct TransferOptions {
@ -195,6 +208,8 @@ pub struct TransferOptions {
pub mburst: Burst, pub mburst: Burst,
/// Flow control configuration /// Flow control configuration
pub flow_ctrl: FlowControl, pub flow_ctrl: FlowControl,
/// FIFO threshold for DMA FIFO mode. If none, direct mode is used.
pub fifo_threshold: Option<FifoThreshold>,
} }
impl Default for TransferOptions { impl Default for TransferOptions {
@ -203,6 +218,7 @@ impl Default for TransferOptions {
pburst: Burst::Single, pburst: Burst::Single,
mburst: Burst::Single, mburst: Burst::Single,
flow_ctrl: FlowControl::Dma, flow_ctrl: FlowControl::Dma,
fifo_threshold: None,
} }
} }
} }
@ -257,6 +273,7 @@ mod transfers {
Transfer::new(channel) Transfer::new(channel)
} }
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct Transfer<'a, C: Channel> { pub(crate) struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>, channel: PeripheralRef<'a, C>,
} }

View File

@ -198,6 +198,7 @@ mod eha {
} }
} }
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct ExtiInputFuture<'a> { struct ExtiInputFuture<'a> {
pin: u8, pin: u8,
phantom: PhantomData<&'a mut AnyPin>, phantom: PhantomData<&'a mut AnyPin>,

View File

@ -6,9 +6,6 @@ use crate::gpio::sealed::AFType;
use crate::gpio::{Pull, Speed}; use crate::gpio::{Pull, Speed};
use crate::Peripheral; use crate::Peripheral;
mod pins;
pub use pins::*;
pub struct Fmc<'d, T: Instance> { pub struct Fmc<'d, T: Instance> {
peri: PhantomData<&'d mut T>, peri: PhantomData<&'d mut T>,
} }
@ -19,7 +16,7 @@ unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T>
where where
T: Instance, T: Instance,
{ {
const REGISTERS: *const () = crate::pac::FMC.0 as *const _; const REGISTERS: *const () = T::REGS.0 as *const _;
fn enable(&mut self) { fn enable(&mut self) {
<T as crate::rcc::sealed::RccPeripheral>::enable(); <T as crate::rcc::sealed::RccPeripheral>::enable();
@ -27,9 +24,13 @@ where
} }
fn memory_controller_enable(&mut self) { fn memory_controller_enable(&mut self) {
// The FMCEN bit of the FMC_BCR2..4 registers is dont // fmc v1 and v2 does not have the fmcen bit
// care. It is only enabled through the FMC_BCR1 register. // fsmc v1, v2 and v3 does not have the fmcen bit
unsafe { T::regs().bcr1().modify(|r| r.set_fmcen(true)) }; // This is a "not" because it is expected that all future versions have this bit
#[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1)))]
unsafe {
T::REGS.bcr1().modify(|r| r.set_fmcen(true))
};
} }
fn source_clock_hz(&self) -> u32 { fn source_clock_hz(&self) -> u32 {
@ -107,6 +108,24 @@ impl<'d, T: Instance> Fmc<'d, T> {
] ]
)); ));
fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank2: (
bank: stm32_fmc::SdramTargetBank::Bank2,
addr: [
(a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin)
],
ba: [(ba0: BA0Pin), (ba1: BA1Pin)],
d: [
(d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin),
(d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin)
],
nbl: [
(nbl0: NBL0Pin), (nbl1: NBL1Pin)
],
ctrl: [
(sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin)
]
));
fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank2: ( fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank2: (
bank: stm32_fmc::SdramTargetBank::Bank2, bank: stm32_fmc::SdramTargetBank::Bank2,
addr: [ addr: [
@ -128,13 +147,130 @@ impl<'d, T: Instance> Fmc<'d, T> {
)); ));
} }
pub(crate) mod sealed {
pub trait Instance: crate::rcc::sealed::RccPeripheral {
const REGS: crate::pac::fmc::Fmc;
}
}
pub trait Instance: sealed::Instance + 'static {}
foreach_peripheral!( foreach_peripheral!(
(fmc, $inst:ident) => { (fmc, $inst:ident) => {
impl crate::fmc::sealed::Instance for crate::peripherals::$inst { impl crate::fmc::sealed::Instance for crate::peripherals::$inst {
fn regs() -> stm32_metapac::fmc::Fmc { const REGS: crate::pac::fmc::Fmc = crate::pac::$inst;
crate::pac::$inst
}
} }
impl crate::fmc::Instance for crate::peripherals::$inst {} impl crate::fmc::Instance for crate::peripherals::$inst {}
}; };
); );
pin_trait!(SDNWEPin, Instance);
pin_trait!(SDNCASPin, Instance);
pin_trait!(SDNRASPin, Instance);
pin_trait!(SDNE0Pin, Instance);
pin_trait!(SDNE1Pin, Instance);
pin_trait!(SDCKE0Pin, Instance);
pin_trait!(SDCKE1Pin, Instance);
pin_trait!(SDCLKPin, Instance);
pin_trait!(NBL0Pin, Instance);
pin_trait!(NBL1Pin, Instance);
pin_trait!(NBL2Pin, Instance);
pin_trait!(NBL3Pin, Instance);
pin_trait!(INTPin, Instance);
pin_trait!(NLPin, Instance);
pin_trait!(NWaitPin, Instance);
pin_trait!(NE1Pin, Instance);
pin_trait!(NE2Pin, Instance);
pin_trait!(NE3Pin, Instance);
pin_trait!(NE4Pin, Instance);
pin_trait!(NCEPin, Instance);
pin_trait!(NOEPin, Instance);
pin_trait!(NWEPin, Instance);
pin_trait!(ClkPin, Instance);
pin_trait!(BA0Pin, Instance);
pin_trait!(BA1Pin, Instance);
pin_trait!(D0Pin, Instance);
pin_trait!(D1Pin, Instance);
pin_trait!(D2Pin, Instance);
pin_trait!(D3Pin, Instance);
pin_trait!(D4Pin, Instance);
pin_trait!(D5Pin, Instance);
pin_trait!(D6Pin, Instance);
pin_trait!(D7Pin, Instance);
pin_trait!(D8Pin, Instance);
pin_trait!(D9Pin, Instance);
pin_trait!(D10Pin, Instance);
pin_trait!(D11Pin, Instance);
pin_trait!(D12Pin, Instance);
pin_trait!(D13Pin, Instance);
pin_trait!(D14Pin, Instance);
pin_trait!(D15Pin, Instance);
pin_trait!(D16Pin, Instance);
pin_trait!(D17Pin, Instance);
pin_trait!(D18Pin, Instance);
pin_trait!(D19Pin, Instance);
pin_trait!(D20Pin, Instance);
pin_trait!(D21Pin, Instance);
pin_trait!(D22Pin, Instance);
pin_trait!(D23Pin, Instance);
pin_trait!(D24Pin, Instance);
pin_trait!(D25Pin, Instance);
pin_trait!(D26Pin, Instance);
pin_trait!(D27Pin, Instance);
pin_trait!(D28Pin, Instance);
pin_trait!(D29Pin, Instance);
pin_trait!(D30Pin, Instance);
pin_trait!(D31Pin, Instance);
pin_trait!(DA0Pin, Instance);
pin_trait!(DA1Pin, Instance);
pin_trait!(DA2Pin, Instance);
pin_trait!(DA3Pin, Instance);
pin_trait!(DA4Pin, Instance);
pin_trait!(DA5Pin, Instance);
pin_trait!(DA6Pin, Instance);
pin_trait!(DA7Pin, Instance);
pin_trait!(DA8Pin, Instance);
pin_trait!(DA9Pin, Instance);
pin_trait!(DA10Pin, Instance);
pin_trait!(DA11Pin, Instance);
pin_trait!(DA12Pin, Instance);
pin_trait!(DA13Pin, Instance);
pin_trait!(DA14Pin, Instance);
pin_trait!(DA15Pin, Instance);
pin_trait!(A0Pin, Instance);
pin_trait!(A1Pin, Instance);
pin_trait!(A2Pin, Instance);
pin_trait!(A3Pin, Instance);
pin_trait!(A4Pin, Instance);
pin_trait!(A5Pin, Instance);
pin_trait!(A6Pin, Instance);
pin_trait!(A7Pin, Instance);
pin_trait!(A8Pin, Instance);
pin_trait!(A9Pin, Instance);
pin_trait!(A10Pin, Instance);
pin_trait!(A11Pin, Instance);
pin_trait!(A12Pin, Instance);
pin_trait!(A13Pin, Instance);
pin_trait!(A14Pin, Instance);
pin_trait!(A15Pin, Instance);
pin_trait!(A16Pin, Instance);
pin_trait!(A17Pin, Instance);
pin_trait!(A18Pin, Instance);
pin_trait!(A19Pin, Instance);
pin_trait!(A20Pin, Instance);
pin_trait!(A21Pin, Instance);
pin_trait!(A22Pin, Instance);
pin_trait!(A23Pin, Instance);
pin_trait!(A24Pin, Instance);
pin_trait!(A25Pin, Instance);

View File

@ -1,118 +0,0 @@
pub(crate) mod sealed {
pub trait Instance: crate::rcc::sealed::RccPeripheral {
fn regs() -> crate::pac::fmc::Fmc;
}
}
pub trait Instance: sealed::Instance + 'static {}
pin_trait!(SDNWEPin, Instance);
pin_trait!(SDNCASPin, Instance);
pin_trait!(SDNRASPin, Instance);
pin_trait!(SDNE0Pin, Instance);
pin_trait!(SDNE1Pin, Instance);
pin_trait!(SDCKE0Pin, Instance);
pin_trait!(SDCKE1Pin, Instance);
pin_trait!(SDCLKPin, Instance);
pin_trait!(NBL0Pin, Instance);
pin_trait!(NBL1Pin, Instance);
pin_trait!(NBL2Pin, Instance);
pin_trait!(NBL3Pin, Instance);
pin_trait!(INTPin, Instance);
pin_trait!(NLPin, Instance);
pin_trait!(NWaitPin, Instance);
pin_trait!(NE1Pin, Instance);
pin_trait!(NE2Pin, Instance);
pin_trait!(NE3Pin, Instance);
pin_trait!(NE4Pin, Instance);
pin_trait!(NCEPin, Instance);
pin_trait!(NOEPin, Instance);
pin_trait!(NWEPin, Instance);
pin_trait!(ClkPin, Instance);
pin_trait!(BA0Pin, Instance);
pin_trait!(BA1Pin, Instance);
pin_trait!(D0Pin, Instance);
pin_trait!(D1Pin, Instance);
pin_trait!(D2Pin, Instance);
pin_trait!(D3Pin, Instance);
pin_trait!(D4Pin, Instance);
pin_trait!(D5Pin, Instance);
pin_trait!(D6Pin, Instance);
pin_trait!(D7Pin, Instance);
pin_trait!(D8Pin, Instance);
pin_trait!(D9Pin, Instance);
pin_trait!(D10Pin, Instance);
pin_trait!(D11Pin, Instance);
pin_trait!(D12Pin, Instance);
pin_trait!(D13Pin, Instance);
pin_trait!(D14Pin, Instance);
pin_trait!(D15Pin, Instance);
pin_trait!(D16Pin, Instance);
pin_trait!(D17Pin, Instance);
pin_trait!(D18Pin, Instance);
pin_trait!(D19Pin, Instance);
pin_trait!(D20Pin, Instance);
pin_trait!(D21Pin, Instance);
pin_trait!(D22Pin, Instance);
pin_trait!(D23Pin, Instance);
pin_trait!(D24Pin, Instance);
pin_trait!(D25Pin, Instance);
pin_trait!(D26Pin, Instance);
pin_trait!(D27Pin, Instance);
pin_trait!(D28Pin, Instance);
pin_trait!(D29Pin, Instance);
pin_trait!(D30Pin, Instance);
pin_trait!(D31Pin, Instance);
pin_trait!(DA0Pin, Instance);
pin_trait!(DA1Pin, Instance);
pin_trait!(DA2Pin, Instance);
pin_trait!(DA3Pin, Instance);
pin_trait!(DA4Pin, Instance);
pin_trait!(DA5Pin, Instance);
pin_trait!(DA6Pin, Instance);
pin_trait!(DA7Pin, Instance);
pin_trait!(DA8Pin, Instance);
pin_trait!(DA9Pin, Instance);
pin_trait!(DA10Pin, Instance);
pin_trait!(DA11Pin, Instance);
pin_trait!(DA12Pin, Instance);
pin_trait!(DA13Pin, Instance);
pin_trait!(DA14Pin, Instance);
pin_trait!(DA15Pin, Instance);
pin_trait!(A0Pin, Instance);
pin_trait!(A1Pin, Instance);
pin_trait!(A2Pin, Instance);
pin_trait!(A3Pin, Instance);
pin_trait!(A4Pin, Instance);
pin_trait!(A5Pin, Instance);
pin_trait!(A6Pin, Instance);
pin_trait!(A7Pin, Instance);
pin_trait!(A8Pin, Instance);
pin_trait!(A9Pin, Instance);
pin_trait!(A10Pin, Instance);
pin_trait!(A11Pin, Instance);
pin_trait!(A12Pin, Instance);
pin_trait!(A13Pin, Instance);
pin_trait!(A14Pin, Instance);
pin_trait!(A15Pin, Instance);
pin_trait!(A16Pin, Instance);
pin_trait!(A17Pin, Instance);
pin_trait!(A18Pin, Instance);
pin_trait!(A19Pin, Instance);
pin_trait!(A20Pin, Instance);
pin_trait!(A21Pin, Instance);
pin_trait!(A22Pin, Instance);
pin_trait!(A23Pin, Instance);
pin_trait!(A24Pin, Instance);
pin_trait!(A25Pin, Instance);

View File

@ -28,6 +28,21 @@ impl<'d, T: Pin> Flex<'d, T> {
Self { pin } Self { pin }
} }
#[inline]
pub fn degrade(mut self) -> Flex<'d, AnyPin> {
// Safety: We are about to drop the other copy of this pin, so
// this clone is safe.
let pin = unsafe { self.pin.clone_unchecked() };
// We don't want to run the destructor here, because that would
// deconfigure the pin.
core::mem::forget(self);
Flex {
pin: pin.map_into::<AnyPin>(),
}
}
/// Put the pin into input mode. /// Put the pin into input mode.
#[inline] #[inline]
pub fn set_as_input(&mut self, pull: Pull) { pub fn set_as_input(&mut self, pull: Pull) {
@ -286,6 +301,13 @@ impl<'d, T: Pin> Input<'d, T> {
Self { pin } Self { pin }
} }
#[inline]
pub fn degrade(self) -> Input<'d, AnyPin> {
Input {
pin: self.pin.degrade(),
}
}
#[inline] #[inline]
pub fn is_high(&self) -> bool { pub fn is_high(&self) -> bool {
self.pin.is_high() self.pin.is_high()
@ -345,6 +367,13 @@ impl<'d, T: Pin> Output<'d, T> {
Self { pin } Self { pin }
} }
#[inline]
pub fn degrade(self) -> Output<'d, AnyPin> {
Output {
pin: self.pin.degrade(),
}
}
/// Set the output as high. /// Set the output as high.
#[inline] #[inline]
pub fn set_high(&mut self) { pub fn set_high(&mut self) {
@ -407,6 +436,13 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> {
Self { pin } Self { pin }
} }
#[inline]
pub fn degrade(self) -> Output<'d, AnyPin> {
Output {
pin: self.pin.degrade(),
}
}
#[inline] #[inline]
pub fn is_high(&self) -> bool { pub fn is_high(&self) -> bool {
!self.pin.is_low() !self.pin.is_low()

View File

@ -64,7 +64,7 @@ impl Into<u8> for APBPrescaler {
impl Into<u8> for AHBPrescaler { impl Into<u8> for AHBPrescaler {
fn into(self) -> u8 { fn into(self) -> u8 {
match self { match self {
AHBPrescaler::NotDivided => 1, AHBPrescaler::NotDivided => 0x0,
AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div2 => 0x08,
AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div3 => 0x01,
AHBPrescaler::Div4 => 0x09, AHBPrescaler::Div4 => 0x09,

View File

@ -158,7 +158,7 @@ impl Into<u8> for APBPrescaler {
impl Into<u8> for AHBPrescaler { impl Into<u8> for AHBPrescaler {
fn into(self) -> u8 { fn into(self) -> u8 {
match self { match self {
AHBPrescaler::NotDivided => 1, AHBPrescaler::NotDivided => 0x0,
AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div2 => 0x08,
AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div3 => 0x01,
AHBPrescaler::Div4 => 0x09, AHBPrescaler::Div4 => 0x09,

View File

@ -32,6 +32,11 @@ impl<'d, T: Instance> Rng<'d, T> {
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
// rng_v2 locks up on seed error, needs reset
#[cfg(rng_v2)]
if unsafe { T::regs().sr().read().seis() } {
T::reset();
}
unsafe { unsafe {
T::regs().cr().modify(|reg| { T::regs().cr().modify(|reg| {
reg.set_rngen(true); reg.set_rngen(true);
@ -90,8 +95,10 @@ impl<'d, T: Instance> Rng<'d, T> {
impl<'d, T: Instance> RngCore for Rng<'d, T> { impl<'d, T: Instance> RngCore for Rng<'d, T> {
fn next_u32(&mut self) -> u32 { fn next_u32(&mut self) -> u32 {
loop { loop {
let bits = unsafe { T::regs().sr().read() }; let sr = unsafe { T::regs().sr().read() };
if bits.drdy() { if sr.seis() | sr.ceis() {
self.reset();
} else if sr.drdy() {
return unsafe { T::regs().dr().read() }; return unsafe { T::regs().dr().read() };
} }
} }

View File

@ -2,6 +2,7 @@
use core::default::Default; use core::default::Default;
use core::future::poll_fn; use core::future::poll_fn;
use core::ops::{Deref, DerefMut};
use core::task::Poll; use core::task::Poll;
use embassy_hal_common::drop::OnDrop; use embassy_hal_common::drop::OnDrop;
@ -40,7 +41,23 @@ impl Default for Signalling {
} }
#[repr(align(4))] #[repr(align(4))]
pub struct DataBlock([u8; 512]); #[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DataBlock(pub [u8; 512]);
impl Deref for DataBlock {
type Target = [u8; 512];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DataBlock {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// Errors /// Errors
#[non_exhaustive] #[non_exhaustive]
@ -123,10 +140,21 @@ cfg_if::cfg_if! {
/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to
/// `sdmmc_ck` in Hertz. /// `sdmmc_ck` in Hertz.
/// ///
/// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1),
/// value and `clk_f` is the resulting new clock frequency. /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency.
fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u8, Hertz), Error> { fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> {
let clk_div = match ker_ck.0 / sdmmc_ck { // sdmmc_v1 maximum clock is 50 MHz
if sdmmc_ck > 50_000_000 {
return Err(Error::BadClock);
}
// bypass divisor
if ker_ck.0 <= sdmmc_ck {
return Ok((true, 0, ker_ck));
}
// `ker_ck / sdmmc_ck` rounded up
let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck {
0 | 1 => Ok(0), 0 | 1 => Ok(0),
x @ 2..=258 => { x @ 2..=258 => {
Ok((x - 2) as u8) Ok((x - 2) as u8)
@ -136,22 +164,24 @@ cfg_if::cfg_if! {
// SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2]
let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2));
Ok((clk_div, clk_f)) Ok((false, clk_div, clk_f))
} }
} else if #[cfg(sdmmc_v2)] { } else if #[cfg(sdmmc_v2)] {
/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to
/// `sdmmc_ck` in Hertz. /// `sdmmc_ck` in Hertz.
/// ///
/// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1),
/// value and `clk_f` is the resulting new clock frequency. /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency.
fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u16, Hertz), Error> { fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> {
// `ker_ck / sdmmc_ck` rounded up
match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck {
0 | 1 => Ok((0, ker_ck)), 0 | 1 => Ok((false, 0, ker_ck)),
x @ 2..=2046 => { x @ 2..=2046 => {
// SDMMC_CK frequency = SDMMCCLK / [CLKDIV + 2]
let clk_div = ((x + 1) / 2) as u16; let clk_div = ((x + 1) / 2) as u16;
let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2));
Ok((clk_div, clk)) Ok((false, clk_div, clk))
} }
_ => Err(Error::BadClock), _ => Err(Error::BadClock),
} }
@ -461,7 +491,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
bus_width, bus_width,
&mut self.card, &mut self.card,
&mut self.signalling, &mut self.signalling,
T::frequency(), T::kernel_clk(),
&mut self.clock, &mut self.clock,
T::state(), T::state(),
self.config.data_transfer_timeout, self.config.data_transfer_timeout,
@ -570,6 +600,12 @@ impl SdmmcInner {
regs.clkcr().write(|w| { regs.clkcr().write(|w| {
w.set_pwrsav(false); w.set_pwrsav(false);
w.set_negedge(false); w.set_negedge(false);
// Hardware flow control is broken on SDIOv1 and causes clock glitches, which result in CRC errors.
// See chip erratas for more details.
#[cfg(sdmmc_v1)]
w.set_hwfc_en(false);
#[cfg(sdmmc_v2)]
w.set_hwfc_en(true); w.set_hwfc_en(true);
#[cfg(sdmmc_v1)] #[cfg(sdmmc_v1)]
@ -602,7 +638,7 @@ impl SdmmcInner {
unsafe { unsafe {
// While the SD/SDIO card or eMMC is in identification mode, // While the SD/SDIO card or eMMC is in identification mode,
// the SDMMC_CK frequency must be no more than 400 kHz. // the SDMMC_CK frequency must be no more than 400 kHz.
let (clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0));
*clock = init_clock; *clock = init_clock;
// CPSMACT and DPSMACT must be 0 to set WIDBUS // CPSMACT and DPSMACT must be 0 to set WIDBUS
@ -611,6 +647,8 @@ impl SdmmcInner {
regs.clkcr().modify(|w| { regs.clkcr().modify(|w| {
w.set_widbus(0); w.set_widbus(0);
w.set_clkdiv(clkdiv); w.set_clkdiv(clkdiv);
#[cfg(sdmmc_v1)]
w.set_bypass(_bypass);
}); });
regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8));
@ -807,10 +845,16 @@ impl SdmmcInner {
let regs = self.0; let regs = self.0;
let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); let on_drop = OnDrop::new(|| unsafe { self.on_drop() });
// sdmmc_v1 uses different cmd/dma order than v2, but only for writes
#[cfg(sdmmc_v1)]
self.cmd(Cmd::write_single_block(address), true)?;
unsafe { unsafe {
self.prepare_datapath_write(buffer as *const [u32; 128], 512, 9, data_transfer_timeout, dma); self.prepare_datapath_write(buffer as *const [u32; 128], 512, 9, data_transfer_timeout, dma);
self.data_interrupts(true); self.data_interrupts(true);
} }
#[cfg(sdmmc_v2)]
self.cmd(Cmd::write_single_block(address), true)?; self.cmd(Cmd::write_single_block(address), true)?;
let res = poll_fn(|cx| { let res = poll_fn(|cx| {
@ -922,7 +966,9 @@ impl SdmmcInner {
let request = dma.request(); let request = dma.request();
dma.start_read(request, regs.fifor().ptr() as *const u32, buffer, crate::dma::TransferOptions { dma.start_read(request, regs.fifor().ptr() as *const u32, buffer, crate::dma::TransferOptions {
pburst: crate::dma::Burst::Incr4, pburst: crate::dma::Burst::Incr4,
mburst: crate::dma::Burst::Incr4,
flow_ctrl: crate::dma::FlowControl::Peripheral, flow_ctrl: crate::dma::FlowControl::Peripheral,
fifo_threshold: Some(crate::dma::FifoThreshold::Full),
..Default::default() ..Default::default()
}); });
} else if #[cfg(sdmmc_v2)] { } else if #[cfg(sdmmc_v2)] {
@ -970,7 +1016,9 @@ impl SdmmcInner {
let request = dma.request(); let request = dma.request();
dma.start_write(request, buffer, regs.fifor().ptr() as *mut u32, crate::dma::TransferOptions { dma.start_write(request, buffer, regs.fifor().ptr() as *mut u32, crate::dma::TransferOptions {
pburst: crate::dma::Burst::Incr4, pburst: crate::dma::Burst::Incr4,
mburst: crate::dma::Burst::Incr4,
flow_ctrl: crate::dma::FlowControl::Peripheral, flow_ctrl: crate::dma::FlowControl::Peripheral,
fifo_threshold: Some(crate::dma::FifoThreshold::Full),
..Default::default() ..Default::default()
}); });
} else if #[cfg(sdmmc_v2)] { } else if #[cfg(sdmmc_v2)] {
@ -982,6 +1030,11 @@ impl SdmmcInner {
regs.dctrl().modify(|w| { regs.dctrl().modify(|w| {
w.set_dblocksize(block_size); w.set_dblocksize(block_size);
w.set_dtdir(false); w.set_dtdir(false);
#[cfg(sdmmc_v1)]
{
w.set_dmaen(true);
w.set_dten(true);
}
}); });
} }
@ -1014,7 +1067,8 @@ impl SdmmcInner {
_ => panic!("Invalid Bus Width"), _ => panic!("Invalid Bus Width"),
}; };
let (clkdiv, new_clock) = clk_div(ker_ck, freq)?; let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?;
// Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7
// Section 55.5.8 // Section 55.5.8
let sdmmc_bus_bandwidth = new_clock.0 * width_u32; let sdmmc_bus_bandwidth = new_clock.0 * width_u32;
@ -1025,7 +1079,11 @@ impl SdmmcInner {
unsafe { unsafe {
// CPSMACT and DPSMACT must be 0 to set CLKDIV // CPSMACT and DPSMACT must be 0 to set CLKDIV
self.wait_idle(); self.wait_idle();
regs.clkcr().modify(|w| w.set_clkdiv(clkdiv)); regs.clkcr().modify(|w| {
w.set_clkdiv(clkdiv);
#[cfg(sdmmc_v1)]
w.set_bypass(_bypass);
});
} }
Ok(()) Ok(())
@ -1114,7 +1172,6 @@ impl SdmmcInner {
} }
/// Query the card status (CMD13, returns R1) /// Query the card status (CMD13, returns R1)
///
fn read_status(&self, card: &Card) -> Result<CardStatus, Error> { fn read_status(&self, card: &Card) -> Result<CardStatus, Error> {
let regs = self.0; let regs = self.0;
let rca = card.rca; let rca = card.rca;
@ -1485,6 +1542,7 @@ pub(crate) mod sealed {
fn inner() -> SdmmcInner; fn inner() -> SdmmcInner;
fn state() -> &'static AtomicWaker; fn state() -> &'static AtomicWaker;
fn kernel_clk() -> Hertz;
} }
pub trait Pins<T: Instance> {} pub trait Pins<T: Instance> {}
@ -1512,6 +1570,61 @@ cfg_if::cfg_if! {
} }
} }
cfg_if::cfg_if! {
// TODO, these could not be implemented, because required clocks are not exposed in RCC:
// - H7 uses pll1_q_ck or pll2_r_ck depending on SDMMCSEL
// - L1 uses pll48
// - L4 uses clk48(pll48)
// - L4+, L5, U5 uses clk48(pll48) or PLLSAI3CLK(PLLP) depending on SDMMCSEL
if #[cfg(stm32f1)] {
// F1 uses AHB1(HCLK), which is correct in PAC
macro_rules! kernel_clk {
($inst:ident) => {
<peripherals::$inst as crate::rcc::sealed::RccPeripheral>::frequency()
}
}
} else if #[cfg(any(stm32f2, stm32f4))] {
// F2, F4 always use pll48
macro_rules! kernel_clk {
($inst:ident) => {
critical_section::with(|_| unsafe {
crate::rcc::get_freqs().pll48
}).expect("PLL48 is required for SDIO")
}
}
} else if #[cfg(stm32f7)] {
macro_rules! kernel_clk {
(SDMMC1) => {
critical_section::with(|_| unsafe {
let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc1sel();
if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK {
crate::rcc::get_freqs().sys
} else {
crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC")
}
})
};
(SDMMC2) => {
critical_section::with(|_| unsafe {
let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc2sel();
if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK {
crate::rcc::get_freqs().sys
} else {
crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC")
}
})
};
}
} else {
// Use default peripheral clock and hope it works
macro_rules! kernel_clk {
($inst:ident) => {
<peripherals::$inst as crate::rcc::sealed::RccPeripheral>::frequency()
}
}
}
}
foreach_peripheral!( foreach_peripheral!(
(sdmmc, $inst:ident) => { (sdmmc, $inst:ident) => {
impl sealed::Instance for peripherals::$inst { impl sealed::Instance for peripherals::$inst {
@ -1526,13 +1639,17 @@ foreach_peripheral!(
static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new(); static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new();
&WAKER &WAKER
} }
fn kernel_clk() -> Hertz {
kernel_clk!($inst)
}
} }
impl Instance for peripherals::$inst {} impl Instance for peripherals::$inst {}
}; };
); );
#[cfg(feature = "sdmmc-rs")] #[cfg(feature = "embedded-sdmmc")]
mod sdmmc_rs { mod sdmmc_rs {
use core::future::Future; use core::future::Future;
@ -1540,7 +1657,7 @@ mod sdmmc_rs {
use super::*; use super::*;
impl<'d, T: Instance, P: Pins<T>> BlockDevice for Sdmmc<'d, T, P> { impl<'d, T: Instance, Dma: SdmmcDma<T>> BlockDevice for Sdmmc<'d, T, Dma> {
type Error = Error; type Error = Error;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a
@ -1558,19 +1675,14 @@ mod sdmmc_rs {
_reason: &str, _reason: &str,
) -> Self::ReadFuture<'a> { ) -> Self::ReadFuture<'a> {
async move { async move {
let card_capacity = self.card()?.card_type;
let inner = T::inner();
let state = T::state();
let mut address = start_block_idx.0; let mut address = start_block_idx.0;
for block in blocks.iter_mut() { for block in blocks.iter_mut() {
let block: &mut [u8; 512] = &mut block.contents; let block: &mut [u8; 512] = &mut block.contents;
// NOTE(unsafe) Block uses align(4) // NOTE(unsafe) Block uses align(4)
let buf = unsafe { &mut *(block as *mut [u8; 512] as *mut [u32; 128]) }; let block = unsafe { &mut *(block as *mut _ as *mut DataBlock) };
inner self.read_block(address, block).await?;
.read_block(address, buf, card_capacity, state, self.config.data_transfer_timeout)
.await?;
address += 1; address += 1;
} }
Ok(()) Ok(())
@ -1579,19 +1691,14 @@ mod sdmmc_rs {
fn write<'a>(&'a mut self, blocks: &'a [Block], start_block_idx: BlockIdx) -> Self::WriteFuture<'a> { fn write<'a>(&'a mut self, blocks: &'a [Block], start_block_idx: BlockIdx) -> Self::WriteFuture<'a> {
async move { async move {
let card = self.card.as_mut().ok_or(Error::NoCard)?;
let inner = T::inner();
let state = T::state();
let mut address = start_block_idx.0; let mut address = start_block_idx.0;
for block in blocks.iter() { for block in blocks.iter() {
let block: &[u8; 512] = &block.contents; let block: &[u8; 512] = &block.contents;
// NOTE(unsafe) DataBlock uses align 4 // NOTE(unsafe) DataBlock uses align 4
let buf = unsafe { &*(block as *const [u8; 512] as *const [u32; 128]) }; let block = unsafe { &*(block as *const _ as *const DataBlock) };
inner self.write_block(address, block).await?;
.write_block(address, buf, card, state, self.config.data_transfer_timeout)
.await?;
address += 1; address += 1;
} }
Ok(()) Ok(())

View File

@ -10,7 +10,7 @@ pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MO
use self::sealed::WordSize; use self::sealed::WordSize;
use crate::dma::{slice_ptr_parts, Transfer}; use crate::dma::{slice_ptr_parts, Transfer};
use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::sealed::{AFType, Pin as _};
use crate::gpio::AnyPin; use crate::gpio::{AnyPin, Pull};
use crate::pac::spi::{regs, vals, Spi as Regs}; use crate::pac::spi::{regs, vals, Spi as Regs};
use crate::rcc::RccPeripheral; use crate::rcc::RccPeripheral;
use crate::time::Hertz; use crate::time::Hertz;
@ -93,8 +93,14 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
config: Config, config: Config,
) -> Self { ) -> Self {
into_ref!(peri, sck, mosi, miso); into_ref!(peri, sck, mosi, miso);
let sck_pull_mode = match config.mode.polarity {
Polarity::IdleLow => Pull::Down,
Polarity::IdleHigh => Pull::Up,
};
unsafe { unsafe {
sck.set_as_af(sck.af_num(), AFType::OutputPushPull); sck.set_as_af_pull(sck.af_num(), AFType::OutputPushPull, sck_pull_mode);
sck.set_speed(crate::gpio::Speed::VeryHigh); sck.set_speed(crate::gpio::Speed::VeryHigh);
mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull);
mosi.set_speed(crate::gpio::Speed::VeryHigh); mosi.set_speed(crate::gpio::Speed::VeryHigh);

View File

@ -770,7 +770,14 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, multiplier: u32, enable
unsafe { unsafe {
r.brr().write_value(regs::Brr(div)); r.brr().write_value(regs::Brr(div));
r.cr2().write(|_w| {}); r.cr2().write(|w| {
w.set_stop(match config.stop_bits {
StopBits::STOP0P5 => vals::Stop::STOP0P5,
StopBits::STOP1 => vals::Stop::STOP1,
StopBits::STOP1P5 => vals::Stop::STOP1P5,
StopBits::STOP2 => vals::Stop::STOP2,
});
});
r.cr1().write(|w| { r.cr1().write(|w| {
// enable uart // enable uart
w.set_ue(true); w.set_ue(true);
@ -1148,7 +1155,7 @@ macro_rules! impl_lpuart {
foreach_interrupt!( foreach_interrupt!(
($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => { ($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => {
impl_lpuart!($inst, $irq, 255); impl_lpuart!($inst, $irq, 256);
}; };
($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => { ($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => {

View File

@ -268,13 +268,13 @@ impl<'d, T: Instance> Driver<'d, T> {
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> { ) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
trace!( trace!(
"allocating type={:?} mps={:?} interval={}, dir={:?}", "allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
ep_type, ep_type,
max_packet_size, max_packet_size,
interval, interval_ms,
D::dir() D::dir()
); );
@ -345,7 +345,7 @@ impl<'d, T: Instance> Driver<'d, T> {
addr: EndpointAddress::from_parts(index, D::dir()), addr: EndpointAddress::from_parts(index, D::dir()),
ep_type, ep_type,
max_packet_size, max_packet_size,
interval, interval_ms,
}, },
buf, buf,
}) })
@ -362,18 +362,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointIn, driver::EndpointAllocError> { ) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval) self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
} }
fn alloc_endpoint_out( fn alloc_endpoint_out(
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointOut, driver::EndpointAllocError> { ) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval) self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
} }
fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {

View File

@ -217,13 +217,13 @@ impl<'d, T: Instance> Driver<'d, T> {
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Endpoint<'d, T, D>, EndpointAllocError> { ) -> Result<Endpoint<'d, T, D>, EndpointAllocError> {
trace!( trace!(
"allocating type={:?} mps={:?} interval={}, dir={:?}", "allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
ep_type, ep_type,
max_packet_size, max_packet_size,
interval, interval_ms,
D::dir() D::dir()
); );
@ -292,7 +292,7 @@ impl<'d, T: Instance> Driver<'d, T> {
addr: EndpointAddress::from_parts(index, D::dir()), addr: EndpointAddress::from_parts(index, D::dir()),
ep_type, ep_type,
max_packet_size, max_packet_size,
interval, interval_ms,
}, },
}) })
} }
@ -308,18 +308,18 @@ impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointIn, EndpointAllocError> { ) -> Result<Self::EndpointIn, EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval) self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
} }
fn alloc_endpoint_out( fn alloc_endpoint_out(
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointOut, EndpointAllocError> { ) -> Result<Self::EndpointOut, EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval) self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
} }
fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {

View File

@ -181,6 +181,7 @@ where
} }
/// Future returned by [`Channel::recv`] and [`Receiver::recv`]. /// Future returned by [`Channel::recv`] and [`Receiver::recv`].
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct RecvFuture<'ch, M, T, const N: usize> pub struct RecvFuture<'ch, M, T, const N: usize>
where where
M: RawMutex, M: RawMutex,
@ -203,6 +204,7 @@ where
} }
/// Future returned by [`DynamicReceiver::recv`]. /// Future returned by [`DynamicReceiver::recv`].
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct DynamicRecvFuture<'ch, T> { pub struct DynamicRecvFuture<'ch, T> {
channel: &'ch dyn DynamicChannel<T>, channel: &'ch dyn DynamicChannel<T>,
} }
@ -219,6 +221,7 @@ impl<'ch, T> Future for DynamicRecvFuture<'ch, T> {
} }
/// Future returned by [`Channel::send`] and [`Sender::send`]. /// Future returned by [`Channel::send`] and [`Sender::send`].
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct SendFuture<'ch, M, T, const N: usize> pub struct SendFuture<'ch, M, T, const N: usize>
where where
M: RawMutex, M: RawMutex,
@ -250,6 +253,7 @@ where
impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {} impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {}
/// Future returned by [`DynamicSender::send`]. /// Future returned by [`DynamicSender::send`].
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct DynamicSendFuture<'ch, T> { pub struct DynamicSendFuture<'ch, T> {
channel: &'ch dyn DynamicChannel<T>, channel: &'ch dyn DynamicChannel<T>,
message: Option<T>, message: Option<T>,

View File

@ -48,6 +48,7 @@ where
} }
/// Future returned by [`Pipe::write`] and [`Writer::write`]. /// Future returned by [`Pipe::write`] and [`Writer::write`].
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct WriteFuture<'p, M, const N: usize> pub struct WriteFuture<'p, M, const N: usize>
where where
M: RawMutex, M: RawMutex,
@ -110,6 +111,7 @@ where
} }
/// Future returned by [`Pipe::read`] and [`Reader::read`]. /// Future returned by [`Pipe::read`] and [`Reader::read`].
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct ReadFuture<'p, M, const N: usize> pub struct ReadFuture<'p, M, const N: usize>
where where
M: RawMutex, M: RawMutex,

View File

@ -1,10 +1,12 @@
use std::cell::UnsafeCell; use std::cell::{RefCell, UnsafeCell};
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::sync::{Condvar, Mutex, Once}; use std::sync::{Condvar, Mutex, Once};
use std::time::{Duration as StdDuration, Instant as StdInstant}; use std::time::{Duration as StdDuration, Instant as StdInstant};
use std::{mem, ptr, thread}; use std::{mem, ptr, thread};
use atomic_polyfill::{AtomicU8, Ordering}; use atomic_polyfill::{AtomicU8, Ordering};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex as EmbassyMutex;
use crate::driver::{AlarmHandle, Driver}; use crate::driver::{AlarmHandle, Driver};
@ -35,7 +37,10 @@ struct TimeDriver {
alarm_count: AtomicU8, alarm_count: AtomicU8,
once: Once, once: Once,
alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>, // The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't
// Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections
// themselves are reentrant
alarms: UninitCell<EmbassyMutex<CriticalSectionRawMutex, RefCell<[AlarmState; ALARM_COUNT]>>>,
zero_instant: UninitCell<StdInstant>, zero_instant: UninitCell<StdInstant>,
signaler: UninitCell<Signaler>, signaler: UninitCell<Signaler>,
} }
@ -53,7 +58,8 @@ crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
impl TimeDriver { impl TimeDriver {
fn init(&self) { fn init(&self) {
self.once.call_once(|| unsafe { self.once.call_once(|| unsafe {
self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); self.alarms
.write(EmbassyMutex::new(RefCell::new([ALARM_NEW; ALARM_COUNT])));
self.zero_instant.write(StdInstant::now()); self.zero_instant.write(StdInstant::now());
self.signaler.write(Signaler::new()); self.signaler.write(Signaler::new());
@ -66,26 +72,38 @@ impl TimeDriver {
loop { loop {
let now = DRIVER.now(); let now = DRIVER.now();
let mut next_alarm = u64::MAX; let next_alarm = unsafe { DRIVER.alarms.as_ref() }.lock(|alarms| {
{ loop {
let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); let pending = alarms
for alarm in alarms { .borrow_mut()
if alarm.timestamp <= now { .iter_mut()
.find(|alarm| alarm.timestamp <= now)
.map(|alarm| {
alarm.timestamp = u64::MAX; alarm.timestamp = u64::MAX;
// Call after clearing alarm, so the callback can set another alarm. (alarm.callback, alarm.ctx)
});
if let Some((callback, ctx)) = pending {
// safety: // safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback // - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; let f: fn(*mut ()) = unsafe { mem::transmute(callback) };
f(alarm.ctx); f(ctx);
} else { } else {
next_alarm = next_alarm.min(alarm.timestamp); // No alarm due
} break;
} }
} }
alarms
.borrow()
.iter()
.map(|alarm| alarm.timestamp)
.min()
.unwrap_or(u64::MAX)
});
// Ensure we don't overflow // Ensure we don't overflow
let until = zero let until = zero
.checked_add(StdDuration::from_micros(next_alarm)) .checked_add(StdDuration::from_micros(next_alarm))
@ -121,18 +139,23 @@ impl Driver for TimeDriver {
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
self.init(); self.init();
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); unsafe { self.alarms.as_ref() }.lock(|alarms| {
let mut alarms = alarms.borrow_mut();
let alarm = &mut alarms[alarm.id() as usize]; let alarm = &mut alarms[alarm.id() as usize];
alarm.callback = callback as *const (); alarm.callback = callback as *const ();
alarm.ctx = ctx; alarm.ctx = ctx;
});
} }
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
self.init(); self.init();
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); unsafe { self.alarms.as_ref() }.lock(|alarms| {
let mut alarms = alarms.borrow_mut();
let alarm = &mut alarms[alarm.id() as usize]; let alarm = &mut alarms[alarm.id() as usize];
alarm.timestamp = timestamp; alarm.timestamp = timestamp;
unsafe { self.signaler.as_ref() }.signal(); unsafe { self.signaler.as_ref() }.signal();
});
true true
} }

View File

@ -81,6 +81,20 @@ impl Duration {
} }
} }
/// Creates a duration corresponding to the specified Hz.
/// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1
/// tick. Doing so will not deadlock, but will certainly not produce the desired output.
pub const fn from_hz(hz: u64) -> Duration {
let ticks = {
if hz >= TICK_HZ {
1
} else {
(TICK_HZ + hz / 2) / hz
}
};
Duration { ticks }
}
/// Adds one Duration to another, returning a new Duration or None in the event of an overflow. /// Adds one Duration to another, returning a new Duration or None in the event of an overflow.
pub fn checked_add(self, rhs: Duration) -> Option<Duration> { pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks })
@ -178,3 +192,19 @@ impl<'a> fmt::Display for Duration {
const fn div_ceil(num: u64, den: u64) -> u64 { const fn div_ceil(num: u64, den: u64) -> u64 {
(num + den - 1) / den (num + den - 1) / den
} }
impl TryFrom<core::time::Duration> for Duration {
type Error = <u64 as TryFrom<u128>>::Error;
/// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64.
fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
Ok(Self::from_micros(value.as_micros().try_into()?))
}
}
impl From<Duration> for core::time::Duration {
/// Converts using [`Duration::as_micros`].
fn from(value: Duration) -> Self {
core::time::Duration::from_micros(value.as_micros())
}
}

View File

@ -26,6 +26,7 @@ pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Out
} }
/// A future that completes at a specified [Instant](struct.Instant.html). /// A future that completes at a specified [Instant](struct.Instant.html).
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Timer { pub struct Timer {
expires_at: Instant, expires_at: Instant,
yielded_once: bool, yielded_once: bool,

View File

@ -0,0 +1,32 @@
# embassy-usb-driver
This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these
traits to add support for using `embassy-usb` for a given chip/platform.
The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`]
APIs don't cause a semver-major bump of thsi crate. This allows existing HALs/BSPs to be used
with the newer `embassy-usb` without needing updates.
If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate
instead of this one.
[`embassy-usb`]: https://crates.io/crates/embassy-usb
## Interoperability
This crate can run on any executor.
## Minimum supported Rust version (MSRV)
This crate requires nightly Rust, due to using "async fn in trait" support.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -1,6 +1,8 @@
#![no_std] #![no_std]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from /// Direction of USB traffic. Note that in the USB standard the direction is always indicated from
/// the perspective of the host, which is backward for devices, but the standard directions are used /// the perspective of the host, which is backward for devices, but the standard directions are used
@ -95,46 +97,65 @@ impl EndpointAddress {
} }
} }
/// Infomation for an endpoint.
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndpointInfo { pub struct EndpointInfo {
/// Endpoint's address.
pub addr: EndpointAddress, pub addr: EndpointAddress,
/// Endpoint's type.
pub ep_type: EndpointType, pub ep_type: EndpointType,
/// Max packet size, in bytes.
pub max_packet_size: u16, pub max_packet_size: u16,
pub interval: u8, /// Polling interval, in milliseconds.
pub interval_ms: u8,
} }
/// Driver for a specific USB peripheral. Implement this to add support for a new hardware /// Main USB driver trait.
/// platform. ///
/// Implement this to add support for a new hardware platform.
pub trait Driver<'a> { pub trait Driver<'a> {
/// Type of the OUT endpoints for this driver.
type EndpointOut: EndpointOut + 'a; type EndpointOut: EndpointOut + 'a;
/// Type of the IN endpoints for this driver.
type EndpointIn: EndpointIn + 'a; type EndpointIn: EndpointIn + 'a;
/// Type of the control pipe for this driver.
type ControlPipe: ControlPipe + 'a; type ControlPipe: ControlPipe + 'a;
/// Type for bus control for this driver.
type Bus: Bus + 'a; type Bus: Bus + 'a;
/// Allocates an endpoint and specified endpoint parameters. This method is called by the device /// Allocates an OUT endpoint.
/// and class implementations to allocate endpoints, and can only be called before ///
/// [`start`](Self::start) is called. /// This method is called by the USB stack to allocate endpoints.
/// It can only be called before [`start`](Self::start) is called.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should /// * `ep_type` - the endpoint's type.
/// attempt to return an endpoint with the specified address. If None, the implementation
/// should return the next available one.
/// * `max_packet_size` - Maximum packet size in bytes. /// * `max_packet_size` - Maximum packet size in bytes.
/// * `interval` - Polling interval parameter for interrupt endpoints. /// * `interval_ms` - Polling interval parameter for interrupt endpoints.
fn alloc_endpoint_out( fn alloc_endpoint_out(
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointOut, EndpointAllocError>; ) -> Result<Self::EndpointOut, EndpointAllocError>;
/// Allocates an IN endpoint.
///
/// This method is called by the USB stack to allocate endpoints.
/// It can only be called before [`start`](Self::start) is called.
///
/// # Arguments
///
/// * `ep_type` - the endpoint's type.
/// * `max_packet_size` - Maximum packet size in bytes.
/// * `interval_ms` - Polling interval parameter for interrupt endpoints.
fn alloc_endpoint_in( fn alloc_endpoint_in(
&mut self, &mut self,
ep_type: EndpointType, ep_type: EndpointType,
max_packet_size: u16, max_packet_size: u16,
interval: u8, interval_ms: u8,
) -> Result<Self::EndpointIn, EndpointAllocError>; ) -> Result<Self::EndpointIn, EndpointAllocError>;
/// Start operation of the USB device. /// Start operation of the USB device.
@ -146,35 +167,37 @@ pub trait Driver<'a> {
/// This consumes the `Driver` instance, so it's no longer possible to allocate more /// This consumes the `Driver` instance, so it's no longer possible to allocate more
/// endpoints. /// endpoints.
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe);
/// Indicates that `set_device_address` must be called before accepting the corresponding
/// control transfer, not after.
///
/// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6
const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false;
} }
/// USB bus trait.
///
/// This trait provides methods that act on the whole bus. It is kept owned by
/// the main USB task, and used to manage the bus.
pub trait Bus { pub trait Bus {
/// Enables the USB peripheral. Soon after enabling the device will be reset, so /// Enable the USB peripheral.
/// there is no need to perform a USB reset in this method.
async fn enable(&mut self); async fn enable(&mut self);
/// Disables and powers down the USB peripheral. /// Disable and powers down the USB peripheral.
async fn disable(&mut self); async fn disable(&mut self);
/// Wait for a bus-related event.
///
/// This method should asynchronously wait for an event to happen, then
/// return it. See [`Event`] for the list of events this method should return.
async fn poll(&mut self) -> Event; async fn poll(&mut self) -> Event;
/// Enables or disables an endpoint. /// Enable or disable an endpoint.
fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool);
/// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it /// Set or clear the STALL condition for an endpoint.
/// should be prepared to receive data again. Only used during control transfers. ///
/// If the endpoint is an OUT endpoint, it should be prepared to receive data again.
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool);
/// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. /// Get whether the STALL condition is set for an endpoint.
fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool;
/// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the /// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the
/// device. /// device.
/// ///
/// The default implementation just returns `Unsupported`. /// The default implementation just returns `Unsupported`.
@ -187,7 +210,7 @@ pub trait Bus {
Err(Unsupported) Err(Unsupported)
} }
/// Initiates a remote wakeup of the host by the device. /// Initiate a remote wakeup of the host by the device.
/// ///
/// # Errors /// # Errors
/// ///
@ -196,25 +219,27 @@ pub trait Bus {
async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; async fn remote_wakeup(&mut self) -> Result<(), Unsupported>;
} }
/// Endpoint trait, common for OUT and IN.
pub trait Endpoint { pub trait Endpoint {
/// Get the endpoint address /// Get the endpoint address
fn info(&self) -> &EndpointInfo; fn info(&self) -> &EndpointInfo;
/// Waits for the endpoint to be enabled. /// Wait for the endpoint to be enabled.
async fn wait_enabled(&mut self); async fn wait_enabled(&mut self);
} }
/// OUT Endpoint trait.
pub trait EndpointOut: Endpoint { pub trait EndpointOut: Endpoint {
/// Reads a single packet of data from the endpoint, and returns the actual length of /// Read a single packet of data from the endpoint, and return the actual length of
/// the packet. /// the packet.
/// ///
/// This should also clear any NAK flags and prepare the endpoint to receive the next packet. /// This should also clear any NAK flags and prepare the endpoint to receive the next packet.
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError>; async fn read(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError>;
} }
/// Trait for USB control pipe. /// USB control pipe trait.
/// ///
/// The USB control pipe owns both OUT ep 0 and IN ep 0 in a single /// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single
/// unit, and manages them together to implement the control pipe state machine. /// unit, and manages them together to implement the control pipe state machine.
/// ///
/// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that /// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that
@ -232,6 +257,14 @@ pub trait EndpointOut: Endpoint {
/// accept() or reject() /// accept() or reject()
/// ``` /// ```
/// ///
/// - control out for setting the device address:
///
/// ```not_rust
/// setup()
/// (...processing...)
/// accept_set_address(addr) or reject()
/// ```
///
/// - control out with len != 0: /// - control out with len != 0:
/// ///
/// ```not_rust /// ```not_rust
@ -280,26 +313,26 @@ pub trait ControlPipe {
/// Maximum packet size for the control pipe /// Maximum packet size for the control pipe
fn max_packet_size(&self) -> usize; fn max_packet_size(&self) -> usize;
/// Reads a single setup packet from the endpoint. /// Read a single setup packet from the endpoint.
async fn setup(&mut self) -> [u8; 8]; async fn setup(&mut self) -> [u8; 8];
/// Reads a DATA OUT packet into `buf` in response to a control write request. /// Read a DATA OUT packet into `buf` in response to a control write request.
/// ///
/// Must be called after `setup()` for requests with `direction` of `Out` /// Must be called after `setup()` for requests with `direction` of `Out`
/// and `length` greater than zero. /// and `length` greater than zero.
async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result<usize, EndpointError>; async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result<usize, EndpointError>;
/// Sends a DATA IN packet with `data` in response to a control read request. /// Send a DATA IN packet with `data` in response to a control read request.
/// ///
/// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`.
async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>;
/// Accepts a control request. /// Accept a control request.
/// ///
/// Causes the STATUS packet for the current request to be ACKed. /// Causes the STATUS packet for the current request to be ACKed.
async fn accept(&mut self); async fn accept(&mut self);
/// Rejects a control request. /// Reject a control request.
/// ///
/// Sets a STALL condition on the pipe to indicate an error. /// Sets a STALL condition on the pipe to indicate an error.
async fn reject(&mut self); async fn reject(&mut self);
@ -311,8 +344,9 @@ pub trait ControlPipe {
async fn accept_set_address(&mut self, addr: u8); async fn accept_set_address(&mut self, addr: u8);
} }
/// IN Endpoint trait.
pub trait EndpointIn: Endpoint { pub trait EndpointIn: Endpoint {
/// Writes a single packet of data to the endpoint. /// Write a single packet of data to the endpoint.
async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>;
} }
@ -338,18 +372,22 @@ pub enum Event {
PowerRemoved, PowerRemoved,
} }
/// Allocating an endpoint failed.
///
/// This can be due to running out of endpoints, or out of endpoint memory,
/// or because the hardware doesn't support the requested combination of features.
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndpointAllocError; pub struct EndpointAllocError;
/// Operation is unsupported by the driver.
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Operation is unsupported by the driver.
pub struct Unsupported; pub struct Unsupported;
/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`]
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`]
pub enum EndpointError { pub enum EndpointError {
/// Either the packet to be written is too long to fit in the transmission /// Either the packet to be written is too long to fit in the transmission
/// buffer or the received packet is too long to fit in `buf`. /// buffer or the received packet is too long to fit in `buf`.

View File

@ -3,6 +3,11 @@ name = "embassy-usb-logger"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-logger-v$VERSION/embassy-usb/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-logger/src/"
target = "thumbv7em-none-eabi"
[dependencies] [dependencies]
embassy-usb = { version = "0.1.0", path = "../embassy-usb" } embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" }

View File

@ -74,7 +74,6 @@ impl<const N: usize> UsbLogger<N> {
&mut state.config_descriptor, &mut state.config_descriptor,
&mut state.bos_descriptor, &mut state.bos_descriptor,
&mut state.control_buf, &mut state.control_buf,
None,
); );
// Create classes on the builder. // Create classes on the builder.

View File

@ -13,8 +13,31 @@ target = "thumbv7em-none-eabi"
[features] [features]
defmt = ["dep:defmt", "embassy-usb-driver/defmt"] defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
msos-descriptor = []
default = ["usbd-hid"] default = ["usbd-hid"]
# BEGIN AUTOGENERATED CONFIG FEATURES
# Generated by gen_config.py. DO NOT EDIT.
max-interface-count-1 = []
max-interface-count-2 = []
max-interface-count-3 = []
max-interface-count-4 = [] # Default
max-interface-count-5 = []
max-interface-count-6 = []
max-interface-count-7 = []
max-interface-count-8 = []
max-handler-count-1 = []
max-handler-count-2 = []
max-handler-count-3 = []
max-handler-count-4 = [] # Default
max-handler-count-5 = []
max-handler-count-6 = []
max-handler-count-7 = []
max-handler-count-8 = []
# END AUTOGENERATED CONFIG FEATURES
[dependencies] [dependencies]
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" }

44
embassy-usb/README.md Normal file
View File

@ -0,0 +1,44 @@
# embassy-usb
TODO crate description
## Configuration
`embassy-usb` has some configuration settings that are set at compile time, affecting sizes
and counts of buffers.
They can be set in two ways:
- Via Cargo features: enable a feature like `<name>-<value>`. `name` must be in lowercase and
use dashes instead of underscores. For example. `max-interface-count-3`. Only a selection of values
is available, check `Cargo.toml` for the list.
- Via environment variables at build time: set the variable named `EMBASSY_USB_<value>`. For example
`EMBASSY_USB_MAX_INTERFACE_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
Any value can be set, unlike with Cargo features.
Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
with different values, compilation fails.
### `MAX_INTERFACE_COUNT`
Max amount of interfaces that can be created in one device. Default: 4.
## Interoperability
This crate can run on any executor.
## Minimum supported Rust version (MSRV)
This crate requires nightly Rust, due to using "async fn in trait" support.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

94
embassy-usb/build.rs Normal file
View File

@ -0,0 +1,94 @@
use std::collections::HashMap;
use std::fmt::Write;
use std::path::PathBuf;
use std::{env, fs};
static CONFIGS: &[(&str, usize)] = &[
// BEGIN AUTOGENERATED CONFIG FEATURES
// Generated by gen_config.py. DO NOT EDIT.
("MAX_INTERFACE_COUNT", 4),
("MAX_HANDLER_COUNT", 4),
// END AUTOGENERATED CONFIG FEATURES
];
struct ConfigState {
value: usize,
seen_feature: bool,
seen_env: bool,
}
fn main() {
let crate_name = env::var("CARGO_PKG_NAME")
.unwrap()
.to_ascii_uppercase()
.replace('-', "_");
// only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
// other file changed.
println!("cargo:rerun-if-changed=build.rs");
// Rebuild if config envvar changed.
for (name, _) in CONFIGS {
println!("cargo:rerun-if-env-changed={crate_name}_{name}");
}
let mut configs = HashMap::new();
for (name, default) in CONFIGS {
configs.insert(
*name,
ConfigState {
value: *default,
seen_env: false,
seen_feature: false,
},
);
}
let prefix = format!("{crate_name}_");
for (var, value) in env::vars() {
if let Some(name) = var.strip_prefix(&prefix) {
let Some(cfg) = configs.get_mut(name) else {
panic!("Unknown env var {name}")
};
let Ok(value) = value.parse::<usize>() else {
panic!("Invalid value for env var {name}: {value}")
};
cfg.value = value;
cfg.seen_env = true;
}
if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
if let Some(i) = feature.rfind('_') {
let name = &feature[..i];
let value = &feature[i + 1..];
if let Some(cfg) = configs.get_mut(name) {
let Ok(value) = value.parse::<usize>() else {
panic!("Invalid value for feature {name}: {value}")
};
// envvars take priority.
if !cfg.seen_env {
if cfg.seen_feature {
panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
}
cfg.value = value;
cfg.seen_feature = true;
}
}
}
}
}
let mut data = String::new();
for (name, cfg) in &configs {
writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
}
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
fs::write(out_file, data).unwrap();
}

74
embassy-usb/gen_config.py Normal file
View File

@ -0,0 +1,74 @@
import os
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
features = []
def feature(name, default, min, max, pow2=None):
vals = set()
val = min
while val <= max:
vals.add(val)
if pow2 == True or (isinstance(pow2, int) and val >= pow2):
val *= 2
else:
val += 1
vals.add(default)
features.append(
{
"name": name,
"default": default,
"vals": sorted(list(vals)),
}
)
feature("max_interface_count", default=4, min=1, max=8)
feature("max_handler_count", default=4, min=1, max=8)
# ========= Update Cargo.toml
things = ""
for f in features:
name = f["name"].replace("_", "-")
for val in f["vals"]:
things += f"{name}-{val} = []"
if val == f["default"]:
things += " # Default"
things += "\n"
things += "\n"
SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
with open("Cargo.toml", "r") as f:
data = f.read()
before, data = data.split(SEPARATOR_START, maxsplit=1)
_, after = data.split(SEPARATOR_END, maxsplit=1)
data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
with open("Cargo.toml", "w") as f:
f.write(data)
# ========= Update build.rs
things = ""
for f in features:
name = f["name"].upper()
things += f' ("{name}", {f["default"]}),\n'
SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
with open("build.rs", "r") as f:
data = f.read()
before, data = data.split(SEPARATOR_START, maxsplit=1)
_, after = data.split(SEPARATOR_END, maxsplit=1)
data = before + SEPARATOR_START + HELP + \
things + " " + SEPARATOR_END + after
with open("build.rs", "w") as f:
f.write(data)

View File

@ -1,10 +1,12 @@
use heapless::Vec; use heapless::Vec;
use crate::control::ControlHandler; use crate::config::*;
use crate::descriptor::{BosWriter, DescriptorWriter}; use crate::descriptor::{BosWriter, DescriptorWriter};
use crate::driver::{Driver, Endpoint, EndpointType}; use crate::driver::{Driver, Endpoint, EndpointType};
#[cfg(feature = "msos-descriptor")]
use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter};
use crate::types::*; use crate::types::*;
use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -120,8 +122,8 @@ impl<'a> Config<'a> {
/// [`UsbDevice`] builder. /// [`UsbDevice`] builder.
pub struct Builder<'d, D: Driver<'d>> { pub struct Builder<'d, D: Driver<'d>> {
config: Config<'d>, config: Config<'d>,
handler: Option<&'d dyn DeviceStateHandler>, handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>,
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, interfaces: Vec<Interface, MAX_INTERFACE_COUNT>,
control_buf: &'d mut [u8], control_buf: &'d mut [u8],
driver: D, driver: D,
@ -130,6 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> {
device_descriptor: DescriptorWriter<'d>, device_descriptor: DescriptorWriter<'d>,
config_descriptor: DescriptorWriter<'d>, config_descriptor: DescriptorWriter<'d>,
bos_descriptor: BosWriter<'d>, bos_descriptor: BosWriter<'d>,
#[cfg(feature = "msos-descriptor")]
msos_descriptor: MsOsDescriptorWriter<'d>,
} }
impl<'d, D: Driver<'d>> Builder<'d, D> { impl<'d, D: Driver<'d>> Builder<'d, D> {
@ -144,8 +149,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
device_descriptor_buf: &'d mut [u8], device_descriptor_buf: &'d mut [u8],
config_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8],
bos_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8],
#[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8],
control_buf: &'d mut [u8], control_buf: &'d mut [u8],
handler: Option<&'d dyn DeviceStateHandler>,
) -> Self { ) -> Self {
// Magic values specified in USB-IF ECN on IADs. // Magic values specified in USB-IF ECN on IADs.
if config.composite_with_iads if config.composite_with_iads
@ -173,32 +178,40 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
Builder { Builder {
driver, driver,
handler,
config, config,
interfaces: Vec::new(), interfaces: Vec::new(),
handlers: Vec::new(),
control_buf, control_buf,
next_string_index: STRING_INDEX_CUSTOM_START, next_string_index: STRING_INDEX_CUSTOM_START,
device_descriptor, device_descriptor,
config_descriptor, config_descriptor,
bos_descriptor, bos_descriptor,
#[cfg(feature = "msos-descriptor")]
msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf),
} }
} }
/// Creates the [`UsbDevice`] instance with the configuration in this builder. /// Creates the [`UsbDevice`] instance with the configuration in this builder.
pub fn build(mut self) -> UsbDevice<'d, D> { pub fn build(mut self) -> UsbDevice<'d, D> {
#[cfg(feature = "msos-descriptor")]
let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor);
self.config_descriptor.end_configuration(); self.config_descriptor.end_configuration();
self.bos_descriptor.end_bos(); self.bos_descriptor.end_bos();
UsbDevice::build( UsbDevice::build(
self.driver, self.driver,
self.config, self.config,
self.handler, self.handlers,
self.device_descriptor.into_buf(), self.device_descriptor.into_buf(),
self.config_descriptor.into_buf(), self.config_descriptor.into_buf(),
self.bos_descriptor.writer.into_buf(), self.bos_descriptor.writer.into_buf(),
self.interfaces, self.interfaces,
self.control_buf, self.control_buf,
#[cfg(feature = "msos-descriptor")]
msos_descriptor,
) )
} }
@ -215,14 +228,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
/// ///
/// If it's not set, no IAD descriptor is added. /// If it's not set, no IAD descriptor is added.
pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> {
let first_interface = InterfaceNumber::new(self.interfaces.len() as u8);
let iface_count_index = if self.config.composite_with_iads { let iface_count_index = if self.config.composite_with_iads {
self.config_descriptor.iad( self.config_descriptor
InterfaceNumber::new(self.interfaces.len() as _), .iad(first_interface, 0, class, subclass, protocol);
0,
class,
subclass,
protocol,
);
Some(self.config_descriptor.position() - 5) Some(self.config_descriptor.position() - 5)
} else { } else {
@ -232,8 +241,52 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
FunctionBuilder { FunctionBuilder {
builder: self, builder: self,
iface_count_index, iface_count_index,
#[cfg(feature = "msos-descriptor")]
first_interface,
} }
} }
/// Add a Handler.
///
/// The Handler is called on some USB bus events, and to handle all control requests not already
/// handled by the USB stack.
pub fn handler(&mut self, handler: &'d mut dyn Handler) {
if self.handlers.push(handler).is_err() {
panic!(
"embassy-usb: handler list full. Increase the `max_handler_count` compile-time setting. Current value: {}",
MAX_HANDLER_COUNT
)
}
}
/// Allocates a new string index.
pub fn string(&mut self) -> StringIndex {
let index = self.next_string_index;
self.next_string_index += 1;
StringIndex::new(index)
}
#[cfg(feature = "msos-descriptor")]
/// Add an MS OS 2.0 Descriptor Set.
///
/// Panics if called more than once.
pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) {
self.msos_descriptor.header(windows_version, vendor_code);
}
#[cfg(feature = "msos-descriptor")]
/// Add an MS OS 2.0 Device Level Feature Descriptor.
pub fn msos_feature<T: DeviceLevelDescriptor>(&mut self, desc: T) {
self.msos_descriptor.device_feature(desc);
}
#[cfg(feature = "msos-descriptor")]
/// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that
/// do not add their own.
pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> {
&mut self.msos_descriptor
}
} }
/// Function builder. /// Function builder.
@ -244,6 +297,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> {
builder: &'a mut Builder<'d, D>, builder: &'a mut Builder<'d, D>,
iface_count_index: Option<usize>, iface_count_index: Option<usize>,
#[cfg(feature = "msos-descriptor")]
first_interface: InterfaceNumber,
}
impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> {
fn drop(&mut self) {
#[cfg(feature = "msos-descriptor")]
self.builder.msos_descriptor.end_function();
}
} }
impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
@ -257,14 +320,15 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
let number = self.builder.interfaces.len() as _; let number = self.builder.interfaces.len() as _;
let iface = Interface { let iface = Interface {
handler: None,
current_alt_setting: 0, current_alt_setting: 0,
num_alt_settings: 0, num_alt_settings: 0,
num_strings: 0,
}; };
if self.builder.interfaces.push(iface).is_err() { if self.builder.interfaces.push(iface).is_err() {
panic!("max interface count reached") panic!(
"embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}",
MAX_INTERFACE_COUNT
)
} }
InterfaceBuilder { InterfaceBuilder {
@ -273,6 +337,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
next_alt_setting_number: 0, next_alt_setting_number: 0,
} }
} }
#[cfg(feature = "msos-descriptor")]
/// Add an MS OS 2.0 Function Level Feature Descriptor.
pub fn msos_feature<T: FunctionLevelDescriptor>(&mut self, desc: T) {
if !self.builder.msos_descriptor.is_in_config_subset() {
self.builder.msos_descriptor.configuration(0);
}
if !self.builder.msos_descriptor.is_in_function_subset() {
self.builder.msos_descriptor.function(self.first_interface);
}
#[cfg(feature = "msos-descriptor")]
self.builder.msos_descriptor.function_feature(desc);
}
} }
/// Interface builder. /// Interface builder.
@ -288,17 +367,9 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> {
self.interface_number self.interface_number
} }
pub fn handler(&mut self, handler: &'d mut dyn ControlHandler) {
self.builder.interfaces[self.interface_number.0 as usize].handler = Some(handler);
}
/// Allocates a new string index. /// Allocates a new string index.
pub fn string(&mut self) -> StringIndex { pub fn string(&mut self) -> StringIndex {
let index = self.builder.next_string_index; self.builder.string()
self.builder.next_string_index += 1;
self.builder.interfaces[self.interface_number.0 as usize].num_strings += 1;
StringIndex::new(index)
} }
/// Add an alternate setting to the interface and write its descriptor. /// Add an alternate setting to the interface and write its descriptor.
@ -306,14 +377,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> {
/// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0. /// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0.
/// ///
/// The first alternate setting, with number 0, is the default one. /// The first alternate setting, with number 0, is the default one.
pub fn alt_setting(&mut self, class: u8, subclass: u8, protocol: u8) -> InterfaceAltBuilder<'_, 'd, D> { pub fn alt_setting(
&mut self,
class: u8,
subclass: u8,
protocol: u8,
interface_string: Option<StringIndex>,
) -> InterfaceAltBuilder<'_, 'd, D> {
let number = self.next_alt_setting_number; let number = self.next_alt_setting_number;
self.next_alt_setting_number += 1; self.next_alt_setting_number += 1;
self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1; self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1;
self.builder self.builder.config_descriptor.interface_alt(
.config_descriptor self.interface_number,
.interface_alt(self.interface_number, number, class, subclass, protocol, None); number,
class,
subclass,
protocol,
interface_string,
);
InterfaceAltBuilder { InterfaceAltBuilder {
builder: self.builder, builder: self.builder,
@ -349,11 +431,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
self.builder.config_descriptor.write(descriptor_type, descriptor) self.builder.config_descriptor.write(descriptor_type, descriptor)
} }
fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn { fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
let ep = self let ep = self
.builder .builder
.driver .driver
.alloc_endpoint_in(ep_type, max_packet_size, interval) .alloc_endpoint_in(ep_type, max_packet_size, interval_ms)
.expect("alloc_endpoint_in failed"); .expect("alloc_endpoint_in failed");
self.builder.config_descriptor.endpoint(ep.info()); self.builder.config_descriptor.endpoint(ep.info());
@ -361,11 +443,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
ep ep
} }
fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut { fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
let ep = self let ep = self
.builder .builder
.driver .driver
.alloc_endpoint_out(ep_type, max_packet_size, interval) .alloc_endpoint_out(ep_type, max_packet_size, interval_ms)
.expect("alloc_endpoint_out failed"); .expect("alloc_endpoint_out failed");
self.builder.config_descriptor.endpoint(ep.info()); self.builder.config_descriptor.endpoint(ep.info());
@ -393,12 +475,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
/// ///
/// Descriptors are written in the order builder functions are called. Note that some /// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order. /// classes care about the order.
pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn { pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval) self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms)
} }
/// Allocate a INTERRUPT OUT endpoint and write its descriptor. /// Allocate a INTERRUPT OUT endpoint and write its descriptor.
pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval) self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms)
}
/// Allocate a ISOCHRONOUS IN endpoint and write its descriptor.
///
/// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order.
pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms)
}
/// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor.
pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms)
} }
} }

View File

@ -1,13 +1,15 @@
//! CDC-ACM class implementation, aka Serial over USB.
use core::cell::Cell; use core::cell::Cell;
use core::mem::{self, MaybeUninit}; use core::mem::{self, MaybeUninit};
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use embassy_sync::blocking_mutex::CriticalSectionMutex; use embassy_sync::blocking_mutex::CriticalSectionMutex;
use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType};
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
use crate::types::*; use crate::types::*;
use crate::Builder; use crate::{Builder, Handler};
/// This should be used as `device_class` when building the `UsbDevice`. /// This should be used as `device_class` when building the `UsbDevice`.
pub const USB_CLASS_CDC: u8 = 0x02; pub const USB_CLASS_CDC: u8 = 0x02;
@ -28,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20;
const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_GET_LINE_CODING: u8 = 0x21;
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
/// Internal state for CDC-ACM
pub struct State<'a> { pub struct State<'a> {
control: MaybeUninit<Control<'a>>, control: MaybeUninit<Control<'a>>,
shared: ControlShared, shared: ControlShared,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new `State`.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
control: MaybeUninit::uninit(), control: MaybeUninit::uninit(),
@ -63,6 +67,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> {
} }
struct Control<'a> { struct Control<'a> {
comm_if: InterfaceNumber,
shared: &'a ControlShared, shared: &'a ControlShared,
} }
@ -94,7 +99,7 @@ impl<'a> Control<'a> {
} }
} }
impl<'d> ControlHandler for Control<'d> { impl<'d> Handler for Control<'d> {
fn reset(&mut self) { fn reset(&mut self) {
let shared = self.shared(); let shared = self.shared();
shared.line_coding.lock(|x| x.set(LineCoding::default())); shared.line_coding.lock(|x| x.set(LineCoding::default()));
@ -102,12 +107,18 @@ impl<'d> ControlHandler for Control<'d> {
shared.rts.store(false, Ordering::Relaxed); shared.rts.store(false, Ordering::Relaxed);
} }
fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option<OutResponse> {
if (req.request_type, req.recipient, req.index)
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
{
return None;
}
match req.request { match req.request {
REQ_SEND_ENCAPSULATED_COMMAND => { REQ_SEND_ENCAPSULATED_COMMAND => {
// We don't actually support encapsulated commands but pretend we do for standards // We don't actually support encapsulated commands but pretend we do for standards
// compatibility. // compatibility.
OutResponse::Accepted Some(OutResponse::Accepted)
} }
REQ_SET_LINE_CODING if data.len() >= 7 => { REQ_SET_LINE_CODING if data.len() >= 7 => {
let coding = LineCoding { let coding = LineCoding {
@ -119,7 +130,7 @@ impl<'d> ControlHandler for Control<'d> {
self.shared().line_coding.lock(|x| x.set(coding)); self.shared().line_coding.lock(|x| x.set(coding));
debug!("Set line coding to: {:?}", coding); debug!("Set line coding to: {:?}", coding);
OutResponse::Accepted Some(OutResponse::Accepted)
} }
REQ_SET_CONTROL_LINE_STATE => { REQ_SET_CONTROL_LINE_STATE => {
let dtr = (req.value & 0x0001) != 0; let dtr = (req.value & 0x0001) != 0;
@ -130,13 +141,19 @@ impl<'d> ControlHandler for Control<'d> {
shared.rts.store(rts, Ordering::Relaxed); shared.rts.store(rts, Ordering::Relaxed);
debug!("Set dtr {}, rts {}", dtr, rts); debug!("Set dtr {}, rts {}", dtr, rts);
OutResponse::Accepted Some(OutResponse::Accepted)
} }
_ => OutResponse::Rejected, _ => Some(OutResponse::Rejected),
} }
} }
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
if (req.request_type, req.recipient, req.index)
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
{
return None;
}
match req.request { match req.request {
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
REQ_GET_LINE_CODING if req.length == 7 => { REQ_GET_LINE_CODING if req.length == 7 => {
@ -147,9 +164,9 @@ impl<'d> ControlHandler for Control<'d> {
buf[4] = coding.stop_bits as u8; buf[4] = coding.stop_bits as u8;
buf[5] = coding.parity_type as u8; buf[5] = coding.parity_type as u8;
buf[6] = coding.data_bits; buf[6] = coding.data_bits;
InResponse::Accepted(&buf[0..7]) Some(InResponse::Accepted(&buf[0..7]))
} }
_ => InResponse::Rejected, _ => Some(InResponse::Rejected),
} }
} }
} }
@ -158,20 +175,15 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
/// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For
/// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self {
let control = state.control.write(Control { shared: &state.shared });
let control_shared = &state.shared;
assert!(builder.control_buf_len() >= 7); assert!(builder.control_buf_len() >= 7);
let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE);
// Control interface // Control interface
let mut iface = func.interface(); let mut iface = func.interface();
iface.handler(control);
let comm_if = iface.interface_number(); let comm_if = iface.interface_number();
let data_if = u8::from(comm_if) + 1; let data_if = u8::from(comm_if) + 1;
let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None);
alt.descriptor( alt.descriptor(
CS_INTERFACE, CS_INTERFACE,
@ -205,10 +217,20 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
// Data interface // Data interface
let mut iface = func.interface(); let mut iface = func.interface();
let data_if = iface.interface_number(); let data_if = iface.interface_number();
let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None);
let read_ep = alt.endpoint_bulk_out(max_packet_size); let read_ep = alt.endpoint_bulk_out(max_packet_size);
let write_ep = alt.endpoint_bulk_in(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size);
drop(func);
let control = state.control.write(Control {
shared: &state.shared,
comm_if,
});
builder.handler(control);
let control_shared = &state.shared;
CdcAcmClass { CdcAcmClass {
_comm_ep: comm_ep, _comm_ep: comm_ep,
_data_if: data_if, _data_if: data_if,
@ -284,10 +306,15 @@ impl From<u8> for StopBits {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ParityType { pub enum ParityType {
/// No parity bit.
None = 0, None = 0,
/// Parity bit is 1 if the amount of `1` bits in the data byte is odd.
Odd = 1, Odd = 1,
/// Parity bit is 1 if the amount of `1` bits in the data byte is even.
Even = 2, Even = 2,
/// Parity bit is always 1
Mark = 3, Mark = 3,
/// Parity bit is always 0
Space = 4, Space = 4,
} }

View File

@ -1,3 +1,4 @@
//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class.
use embassy_futures::select::{select, Either}; use embassy_futures::select::{select, Either};
use embassy_net_driver_channel as ch; use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::LinkState; use embassy_net_driver_channel::driver::LinkState;
@ -5,11 +6,13 @@ use embassy_usb_driver::Driver;
use super::{CdcNcmClass, Receiver, Sender}; use super::{CdcNcmClass, Receiver, Sender};
/// Internal state for the embassy-net integration.
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> { pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
ch_state: ch::State<MTU, N_RX, N_TX>, ch_state: ch::State<MTU, N_RX, N_TX>,
} }
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> { impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
/// Create a new `State`.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
ch_state: ch::State::new(), ch_state: ch::State::new(),
@ -17,6 +20,9 @@ impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_
} }
} }
/// Background runner for the CDC-NCM class.
///
/// You must call `.run()` in a background task for the class to operate.
pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
tx_usb: Sender<'d, D>, tx_usb: Sender<'d, D>,
rx_usb: Receiver<'d, D>, rx_usb: Receiver<'d, D>,
@ -24,6 +30,9 @@ pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
} }
impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
/// Run the CDC-NCM class.
///
/// You must call this in a background task for the class to operate.
pub async fn run(mut self) -> ! { pub async fn run(mut self) -> ! {
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
let rx_fut = async move { let rx_fut = async move {
@ -66,9 +75,11 @@ impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug? // would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug?
//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd; //pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd;
/// Type alias for the embassy-net driver for CDC-NCM.
pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>; pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>;
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
/// Obtain a driver for using the CDC-NCM class with [`embassy-net`](crates.io/crates/embassy-net).
pub fn into_embassy_net_device<const MTU: usize, const N_RX: usize, const N_TX: usize>( pub fn into_embassy_net_device<const MTU: usize, const N_RX: usize, const N_TX: usize>(
self, self,
state: &'d mut State<MTU, N_RX, N_TX>, state: &'d mut State<MTU, N_RX, N_TX>,

View File

@ -1,25 +1,26 @@
/// CDC-NCM, aka Ethernet over USB. //! CDC-NCM class implementation, aka Ethernet over USB.
/// //!
/// # Compatibility //! # Compatibility
/// //!
/// Windows: NOT supported in Windows 10. Supported in Windows 11. //! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11.
/// //!
/// Linux: Well-supported since forever. //! Linux: Well-supported since forever.
/// //!
/// Android: Support for CDC-NCM is spotty and varies across manufacturers. //! Android: Support for CDC-NCM is spotty and varies across manufacturers.
/// //!
/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12. //! - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), //! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. //! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 //! This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417
/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 //! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
use core::intrinsics::copy_nonoverlapping; use core::intrinsics::copy_nonoverlapping;
use core::mem::{size_of, MaybeUninit}; use core::mem::{size_of, MaybeUninit};
use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType};
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
use crate::types::*; use crate::types::*;
use crate::Builder; use crate::{Builder, Handler};
pub mod embassy_net; pub mod embassy_net;
@ -114,17 +115,17 @@ fn byteify<T>(buf: &mut [u8], data: T) -> &[u8] {
&buf[..len] &buf[..len]
} }
/// Internal state for the CDC-NCM class.
pub struct State<'a> { pub struct State<'a> {
comm_control: MaybeUninit<CommControl<'a>>, control: MaybeUninit<Control<'a>>,
data_control: MaybeUninit<DataControl>,
shared: ControlShared, shared: ControlShared,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new `State`.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
comm_control: MaybeUninit::uninit(), control: MaybeUninit::uninit(),
data_control: MaybeUninit::uninit(),
shared: Default::default(), shared: Default::default(),
} }
} }
@ -141,29 +142,55 @@ impl Default for ControlShared {
} }
} }
struct CommControl<'a> { struct Control<'a> {
mac_addr_string: StringIndex, mac_addr_string: StringIndex,
shared: &'a ControlShared, shared: &'a ControlShared,
mac_addr_str: [u8; 12], mac_addr_str: [u8; 12],
comm_if: InterfaceNumber,
data_if: InterfaceNumber,
} }
impl<'d> ControlHandler for CommControl<'d> { impl<'d> Handler for Control<'d> {
fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) {
if iface != self.data_if {
return;
}
match alternate_setting {
ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"),
ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"),
_ => unreachable!(),
}
}
fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option<OutResponse> {
if (req.request_type, req.recipient, req.index)
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
{
return None;
}
match req.request { match req.request {
REQ_SEND_ENCAPSULATED_COMMAND => { REQ_SEND_ENCAPSULATED_COMMAND => {
// We don't actually support encapsulated commands but pretend we do for standards // We don't actually support encapsulated commands but pretend we do for standards
// compatibility. // compatibility.
OutResponse::Accepted Some(OutResponse::Accepted)
} }
REQ_SET_NTB_INPUT_SIZE => { REQ_SET_NTB_INPUT_SIZE => {
// TODO // TODO
OutResponse::Accepted Some(OutResponse::Accepted)
} }
_ => OutResponse::Rejected, _ => Some(OutResponse::Rejected),
} }
} }
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
if (req.request_type, req.recipient, req.index)
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
{
return None;
}
match req.request { match req.request {
REQ_GET_NTB_PARAMETERS => { REQ_GET_NTB_PARAMETERS => {
let res = NtbParameters { let res = NtbParameters {
@ -184,9 +211,9 @@ impl<'d> ControlHandler for CommControl<'d> {
max_datagram_count: 1, // We only decode 1 packet per NTB max_datagram_count: 1, // We only decode 1 packet per NTB
}, },
}; };
InResponse::Accepted(byteify(buf, res)) Some(InResponse::Accepted(byteify(buf, res)))
} }
_ => InResponse::Rejected, _ => Some(InResponse::Rejected),
} }
} }
@ -211,18 +238,7 @@ impl<'d> ControlHandler for CommControl<'d> {
} }
} }
struct DataControl {} /// CDC-NCM class
impl ControlHandler for DataControl {
fn set_alternate_setting(&mut self, alternate_setting: u8) {
match alternate_setting {
ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"),
ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"),
_ => unreachable!(),
}
}
}
pub struct CdcNcmClass<'d, D: Driver<'d>> { pub struct CdcNcmClass<'d, D: Driver<'d>> {
_comm_if: InterfaceNumber, _comm_if: InterfaceNumber,
comm_ep: D::EndpointIn, comm_ep: D::EndpointIn,
@ -235,6 +251,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> {
} }
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
/// Create a new CDC NCM class.
pub fn new( pub fn new(
builder: &mut Builder<'d, D>, builder: &mut Builder<'d, D>,
state: &'d mut State<'d>, state: &'d mut State<'d>,
@ -248,13 +265,8 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
// Control interface // Control interface
let mut iface = func.interface(); let mut iface = func.interface();
let mac_addr_string = iface.string(); let mac_addr_string = iface.string();
iface.handler(state.comm_control.write(CommControl {
mac_addr_string,
shared: &state.shared,
mac_addr_str: [0; 12],
}));
let comm_if = iface.interface_number(); let comm_if = iface.interface_number();
let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None);
alt.descriptor( alt.descriptor(
CS_INTERFACE, CS_INTERFACE,
@ -302,13 +314,23 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
// Data interface // Data interface
let mut iface = func.interface(); let mut iface = func.interface();
iface.handler(state.data_control.write(DataControl {}));
let data_if = iface.interface_number(); let data_if = iface.interface_number();
let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None);
let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None);
let read_ep = alt.endpoint_bulk_out(max_packet_size); let read_ep = alt.endpoint_bulk_out(max_packet_size);
let write_ep = alt.endpoint_bulk_in(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size);
drop(func);
let control = state.control.write(Control {
mac_addr_string,
shared: &state.shared,
mac_addr_str: [0; 12],
comm_if,
data_if,
});
builder.handler(control);
CdcNcmClass { CdcNcmClass {
_comm_if: comm_if, _comm_if: comm_if,
comm_ep, comm_ep,
@ -319,6 +341,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
} }
} }
/// Split the class into a sender and receiver.
///
/// This allows concurrently sending and receiving packets from separate tasks.
pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
( (
Sender { Sender {
@ -334,12 +359,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
} }
} }
/// CDC NCM class packet sender.
///
/// You can obtain a `Sender` with [`CdcNcmClass::split`]
pub struct Sender<'d, D: Driver<'d>> { pub struct Sender<'d, D: Driver<'d>> {
write_ep: D::EndpointIn, write_ep: D::EndpointIn,
seq: u16, seq: u16,
} }
impl<'d, D: Driver<'d>> Sender<'d, D> { impl<'d, D: Driver<'d>> Sender<'d, D> {
/// Write a packet.
///
/// This waits until the packet is succesfully stored in the CDC-NCM endpoint buffers.
pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
let seq = self.seq; let seq = self.seq;
self.seq = self.seq.wrapping_add(1); self.seq = self.seq.wrapping_add(1);
@ -393,6 +424,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> {
} }
} }
/// CDC NCM class packet receiver.
///
/// You can obtain a `Receiver` with [`CdcNcmClass::split`]
pub struct Receiver<'d, D: Driver<'d>> { pub struct Receiver<'d, D: Driver<'d>> {
data_if: InterfaceNumber, data_if: InterfaceNumber,
comm_ep: D::EndpointIn, comm_ep: D::EndpointIn,
@ -400,7 +434,9 @@ pub struct Receiver<'d, D: Driver<'d>> {
} }
impl<'d, D: Driver<'d>> Receiver<'d, D> { impl<'d, D: Driver<'d>> Receiver<'d, D> {
/// Reads a single packet from the OUT endpoint. /// Write a network packet.
///
/// This waits until a packet is succesfully received from the endpoint buffers.
pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> { pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
// Retry loop // Retry loop
loop { loop {

View File

@ -1,3 +1,5 @@
//! USB HID (Human Interface Device) class implementation.
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use core::ops::Range; use core::ops::Range;
use core::sync::atomic::{AtomicUsize, Ordering}; use core::sync::atomic::{AtomicUsize, Ordering};
@ -7,9 +9,10 @@ use ssmarshal::serialize;
#[cfg(feature = "usbd-hid")] #[cfg(feature = "usbd-hid")]
use usbd_hid::descriptor::AsInputReport; use usbd_hid::descriptor::AsInputReport;
use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType};
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
use crate::Builder; use crate::types::InterfaceNumber;
use crate::{Builder, Handler};
const USB_CLASS_HID: u8 = 0x03; const USB_CLASS_HID: u8 = 0x03;
const USB_SUBCLASS_NONE: u8 = 0x00; const USB_SUBCLASS_NONE: u8 = 0x00;
@ -28,6 +31,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09;
const HID_REQ_GET_PROTOCOL: u8 = 0x03; const HID_REQ_GET_PROTOCOL: u8 = 0x03;
const HID_REQ_SET_PROTOCOL: u8 = 0x0b; const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
/// Configuration for the HID class.
pub struct Config<'d> { pub struct Config<'d> {
/// HID report descriptor. /// HID report descriptor.
pub report_descriptor: &'d [u8], pub report_descriptor: &'d [u8],
@ -46,11 +50,15 @@ pub struct Config<'d> {
pub max_packet_size: u16, pub max_packet_size: u16,
} }
/// Report ID
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ReportId { pub enum ReportId {
/// IN report
In(u8), In(u8),
/// OUT report
Out(u8), Out(u8),
/// Feature report
Feature(u8), Feature(u8),
} }
@ -65,12 +73,14 @@ impl ReportId {
} }
} }
/// Internal state for USB HID.
pub struct State<'d> { pub struct State<'d> {
control: MaybeUninit<Control<'d>>, control: MaybeUninit<Control<'d>>,
out_report_offset: AtomicUsize, out_report_offset: AtomicUsize,
} }
impl<'d> State<'d> { impl<'d> State<'d> {
/// Create a new `State`.
pub fn new() -> Self { pub fn new() -> Self {
State { State {
control: MaybeUninit::uninit(), control: MaybeUninit::uninit(),
@ -79,6 +89,7 @@ impl<'d> State<'d> {
} }
} }
/// USB HID reader/writer.
pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
reader: HidReader<'d, D, READ_N>, reader: HidReader<'d, D, READ_N>,
writer: HidWriter<'d, D, WRITE_N>, writer: HidWriter<'d, D, WRITE_N>,
@ -90,18 +101,12 @@ fn build<'d, D: Driver<'d>>(
config: Config<'d>, config: Config<'d>,
with_out_endpoint: bool, with_out_endpoint: bool,
) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) { ) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) {
let control = state.control.write(Control::new(
config.report_descriptor,
config.request_handler,
&state.out_report_offset,
));
let len = config.report_descriptor.len(); let len = config.report_descriptor.len();
let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE);
let mut iface = func.interface(); let mut iface = func.interface();
iface.handler(control); let if_num = iface.interface_number();
let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None);
// HID descriptor // HID descriptor
alt.descriptor( alt.descriptor(
@ -129,6 +134,16 @@ fn build<'d, D: Driver<'d>>(
None None
}; };
drop(func);
let control = state.control.write(Control::new(
if_num,
config.report_descriptor,
config.request_handler,
&state.out_report_offset,
));
builder.handler(control);
(ep_out, ep_in, &state.out_report_offset) (ep_out, ep_in, &state.out_report_offset)
} }
@ -180,20 +195,30 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit
} }
} }
/// USB HID writer.
///
/// You can obtain a `HidWriter` using [`HidReaderWriter::split`].
pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { pub struct HidWriter<'d, D: Driver<'d>, const N: usize> {
ep_in: D::EndpointIn, ep_in: D::EndpointIn,
} }
/// USB HID reader.
///
/// You can obtain a `HidReader` using [`HidReaderWriter::split`].
pub struct HidReader<'d, D: Driver<'d>, const N: usize> { pub struct HidReader<'d, D: Driver<'d>, const N: usize> {
ep_out: D::EndpointOut, ep_out: D::EndpointOut,
offset: &'d AtomicUsize, offset: &'d AtomicUsize,
} }
/// Error when reading a HID report.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ReadError { pub enum ReadError {
/// The given buffer was too small to read the received report.
BufferOverflow, BufferOverflow,
/// The endpoint is disabled.
Disabled, Disabled,
/// The report was only partially read. See [`HidReader::read`] for details.
Sync(Range<usize>), Sync(Range<usize>),
} }
@ -344,6 +369,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> {
} }
} }
/// Handler for HID-related control requests.
pub trait RequestHandler { pub trait RequestHandler {
/// Reads the value of report `id` into `buf` returning the size. /// Reads the value of report `id` into `buf` returning the size.
/// ///
@ -379,6 +405,7 @@ pub trait RequestHandler {
} }
struct Control<'d> { struct Control<'d> {
if_num: InterfaceNumber,
report_descriptor: &'d [u8], report_descriptor: &'d [u8],
request_handler: Option<&'d dyn RequestHandler>, request_handler: Option<&'d dyn RequestHandler>,
out_report_offset: &'d AtomicUsize, out_report_offset: &'d AtomicUsize,
@ -387,11 +414,13 @@ struct Control<'d> {
impl<'d> Control<'d> { impl<'d> Control<'d> {
fn new( fn new(
if_num: InterfaceNumber,
report_descriptor: &'d [u8], report_descriptor: &'d [u8],
request_handler: Option<&'d dyn RequestHandler>, request_handler: Option<&'d dyn RequestHandler>,
out_report_offset: &'d AtomicUsize, out_report_offset: &'d AtomicUsize,
) -> Self { ) -> Self {
Control { Control {
if_num,
report_descriptor, report_descriptor,
request_handler, request_handler,
out_report_offset, out_report_offset,
@ -417,22 +446,19 @@ impl<'d> Control<'d> {
} }
} }
impl<'d> ControlHandler for Control<'d> { impl<'d> Handler for Control<'d> {
fn reset(&mut self) { fn reset(&mut self) {
self.out_report_offset.store(0, Ordering::Release); self.out_report_offset.store(0, Ordering::Release);
} }
fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { fn control_out(&mut self, req: Request, data: &[u8]) -> Option<OutResponse> {
match (req.value >> 8) as u8 { if (req.request_type, req.recipient, req.index)
HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), != (RequestType::Class, Recipient::Interface, self.if_num.0 as u16)
HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), {
_ => InResponse::Rejected, return None;
}
} }
fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse {
trace!("HID control_out {:?} {=[u8]:x}", req, data); trace!("HID control_out {:?} {=[u8]:x}", req, data);
if let RequestType::Class = req.request_type {
match req.request { match req.request {
HID_REQ_SET_IDLE => { HID_REQ_SET_IDLE => {
if let Some(handler) = self.request_handler { if let Some(handler) = self.request_handler {
@ -442,28 +468,40 @@ impl<'d> ControlHandler for Control<'d> {
let dur = if dur == 0 { u32::MAX } else { 4 * dur }; let dur = if dur == 0 { u32::MAX } else { 4 * dur };
handler.set_idle_ms(id, dur); handler.set_idle_ms(id, dur);
} }
OutResponse::Accepted Some(OutResponse::Accepted)
} }
HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) {
(Ok(id), Some(handler)) => handler.set_report(id, data), (Ok(id), Some(handler)) => Some(handler.set_report(id, data)),
_ => OutResponse::Rejected, _ => Some(OutResponse::Rejected),
}, },
HID_REQ_SET_PROTOCOL => { HID_REQ_SET_PROTOCOL => {
if req.value == 1 { if req.value == 1 {
OutResponse::Accepted Some(OutResponse::Accepted)
} else { } else {
warn!("HID Boot Protocol is unsupported."); warn!("HID Boot Protocol is unsupported.");
OutResponse::Rejected // UNSUPPORTED: Boot Protocol Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol
} }
} }
_ => OutResponse::Rejected, _ => Some(OutResponse::Rejected),
}
} else {
OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR
} }
} }
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
if req.index != self.if_num.0 as u16 {
return None;
}
match (req.request_type, req.recipient) {
(RequestType::Standard, Recipient::Interface) => match req.request {
Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 {
HID_DESC_DESCTYPE_HID_REPORT => Some(InResponse::Accepted(self.report_descriptor)),
HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)),
_ => Some(InResponse::Rejected),
},
_ => Some(InResponse::Rejected),
},
(RequestType::Class, Recipient::Interface) => {
trace!("HID control_in {:?}", req); trace!("HID control_in {:?}", req);
match req.request { match req.request {
HID_REQ_GET_REPORT => { HID_REQ_GET_REPORT => {
@ -473,9 +511,9 @@ impl<'d> ControlHandler for Control<'d> {
}; };
if let Some(size) = size { if let Some(size) = size {
InResponse::Accepted(&buf[0..size]) Some(InResponse::Accepted(&buf[0..size]))
} else { } else {
InResponse::Rejected Some(InResponse::Rejected)
} }
} }
HID_REQ_GET_IDLE => { HID_REQ_GET_IDLE => {
@ -485,20 +523,23 @@ impl<'d> ControlHandler for Control<'d> {
if let Some(dur) = handler.get_idle_ms(id) { if let Some(dur) = handler.get_idle_ms(id) {
let dur = u8::try_from(dur / 4).unwrap_or(0); let dur = u8::try_from(dur / 4).unwrap_or(0);
buf[0] = dur; buf[0] = dur;
InResponse::Accepted(&buf[0..1]) Some(InResponse::Accepted(&buf[0..1]))
} else { } else {
InResponse::Rejected Some(InResponse::Rejected)
} }
} else { } else {
InResponse::Rejected Some(InResponse::Rejected)
} }
} }
HID_REQ_GET_PROTOCOL => { HID_REQ_GET_PROTOCOL => {
// UNSUPPORTED: Boot Protocol // UNSUPPORTED: Boot Protocol
buf[0] = 1; buf[0] = 1;
InResponse::Accepted(&buf[0..1]) Some(InResponse::Accepted(&buf[0..1]))
} }
_ => InResponse::Rejected, _ => Some(InResponse::Rejected),
}
}
_ => None,
} }
} }
} }

View File

@ -1,3 +1,4 @@
//! Implementations of well-known USB classes.
pub mod cdc_acm; pub mod cdc_acm;
pub mod cdc_ncm; pub mod cdc_ncm;
pub mod hid; pub mod hid;

View File

@ -2,7 +2,6 @@
use core::mem; use core::mem;
use crate::driver::Direction; use crate::driver::Direction;
use crate::types::StringIndex;
/// Control request type. /// Control request type.
#[repr(u8)] #[repr(u8)]
@ -126,72 +125,22 @@ impl Request {
} }
} }
/// Response for a CONTROL OUT request.
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum OutResponse { pub enum OutResponse {
/// The request was accepted.
Accepted, Accepted,
/// The request was rejected.
Rejected, Rejected,
} }
/// Response for a CONTROL IN request.
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InResponse<'a> { pub enum InResponse<'a> {
/// The request was accepted. The buffer contains the response data.
Accepted(&'a [u8]), Accepted(&'a [u8]),
/// The request was rejected.
Rejected, Rejected,
} }
/// Handler for control requests.
///
/// All methods are optional callbacks that will be called by
/// [`UsbDevice::run()`](crate::UsbDevice::run)
pub trait ControlHandler {
/// Called after a USB reset after the bus reset sequence is complete.
fn reset(&mut self) {}
fn set_alternate_setting(&mut self, alternate_setting: u8) {
let _ = alternate_setting;
}
/// Called when a control request is received with direction HostToDevice.
///
/// # Arguments
///
/// * `req` - The request from the SETUP packet.
/// * `data` - The data from the request.
fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse {
let _ = (req, data);
OutResponse::Rejected
}
/// Called when a control request is received with direction DeviceToHost.
///
/// You should write the response somewhere (usually to `buf`, but you may use another buffer
/// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`.
///
/// # Arguments
///
/// * `req` - The request from the SETUP packet.
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
let _ = (req, buf);
InResponse::Rejected
}
/// Called when a GET DESCRIPTOR control request is received on the interface.
///
/// You should write the response somewhere (usually to `buf`, but you may use another buffer
/// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`.
///
/// # Arguments
///
/// * `req` - The request from the SETUP packet.
fn get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
let _ = (req, buf);
InResponse::Rejected
}
/// Called when a GET_DESCRIPTOR STRING control request is received.
fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> {
let _ = (index, lang_id);
None
}
}

View File

@ -1,3 +1,5 @@
//! Utilities for writing USB descriptors.
use crate::builder::Config; use crate::builder::Config;
use crate::driver::EndpointInfo; use crate::driver::EndpointInfo;
use crate::types::*; use crate::types::*;
@ -236,7 +238,7 @@ impl<'a> DescriptorWriter<'a> {
endpoint.ep_type as u8, // bmAttributes endpoint.ep_type as u8, // bmAttributes
endpoint.max_packet_size as u8, endpoint.max_packet_size as u8,
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
endpoint.interval, // bInterval endpoint.interval_ms, // bInterval
], ],
); );
} }

Some files were not shown because too many files have changed in this diff Show More