Merge pull request #1 from embassy-rs/main

Pull changes from embassy main
This commit is contained in:
Tyler 2023-07-01 15:41:11 -05:00 committed by GitHub
commit 3aedbce5c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
149 changed files with 5036 additions and 1114 deletions

View file

@ -6,16 +6,16 @@
"rust-analyzer.check.allTargets": false,
"rust-analyzer.check.noDefaultFeatures": true,
"rust-analyzer.cargo.noDefaultFeatures": true,
"rust-analyzer.cargo.target": "thumbv7em-none-eabi",
"rust-analyzer.cargo.target": "thumbv7m-none-eabi",
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
"rust-analyzer.cargo.features": [
"nightly",
///"nightly",
],
"rust-analyzer.linkedProjects": [
// Declare for the target you wish to develop
// "embassy-executor/Cargo.toml",
// "embassy-sync/Cargo.toml",
"examples/nrf52840/Cargo.toml",
"examples/stm32wl/Cargo.toml",
// "examples/nrf5340/Cargo.toml",
// "examples/nrf-rtos-trace/Cargo.toml",
// "examples/rp/Cargo.toml",

View file

@ -99,10 +99,10 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer
### Running examples
- Install `probe-rs-cli` with defmt support.
- Install `probe-rs`.
```bash
cargo install probe-rs-cli
cargo install probe-rs --features cli
```
- Change directory to the sample's base directory. For example:

2
ci.sh
View file

@ -3,7 +3,7 @@
set -euo pipefail
export RUSTFLAGS=-Dwarnings
export DEFMT_LOG=trace,cyw43=info,cyw43_pio=info,smoltcp=info
export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
# needed by wifi examples
export WIFI_NETWORK=x

View file

@ -1,6 +1,6 @@
# cyw43
WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
## Current status
@ -19,18 +19,18 @@ Working:
TODO:
- Setting a custom MAC address.
- Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?)
- Bus sleep (for power consumption optimization)
## Running the examples
- `cargo install probe-rs-cli`
- `cd examples/rpi-pico-w`
- `cargo install probe-rs --features cli`
- `cd examples/rp`
### Example 1: Scan the wifi stations
- `cargo run --release --bin wifi_scan`
### Example 2: Create an access point (IP and credentials in the code)
- `cargo run --release --bin tcp_server_ap`
- `cargo run --release --bin wifi_ap_tcp_server`
### Example 3: Connect to an existing network and create a server
- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release`
- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release --bin wifi_tcp_server`
After a few seconds, you should see that DHCP picks up an IP address like this
```

View file

@ -6,7 +6,7 @@ An adaptation of `embassy-boot` for nRF.
## Features
* Load applications with our without the softdevice.
* Load applications with or without the softdevice.
* Configure bootloader partitions based on linker script.
* Using watchdog timer to detect application failure.

View file

@ -12,9 +12,9 @@ categories = [
]
[dependencies]
syn = { version = "1.0.76", features = ["full", "extra-traits"] }
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
quote = "1.0.9"
darling = "0.13.0"
darling = "0.20.1"
proc-macro2 = "1.0.29"
[lib]

View file

@ -1,11 +1,28 @@
#![doc = include_str!("../README.md")]
extern crate proc_macro;
use darling::ast::NestedMeta;
use proc_macro::TokenStream;
mod macros;
mod util;
use macros::*;
use syn::parse::{Parse, ParseBuffer};
use syn::punctuated::Punctuated;
use syn::Token;
struct Args {
meta: Vec<NestedMeta>,
}
impl Parse for Args {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
Ok(Args {
meta: meta.into_iter().collect(),
})
}
}
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
/// many concurrent tasks can be spawned (default is 1) for the function.
@ -39,10 +56,10 @@ use macros::*;
/// ```
#[proc_macro_attribute]
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
task::run(args, f).unwrap_or_else(|x| x).into()
task::run(&args.meta, f).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
@ -65,9 +82,9 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::cortex_m()).unwrap_or_else(|x| x).into()
main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
@ -100,9 +117,9 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args.clone(), f, main::riscv(args))
main::run(&args.meta, f, main::riscv(&args.meta))
.unwrap_or_else(|x| x)
.into()
}
@ -127,9 +144,9 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::std()).unwrap_or_else(|x| x).into()
main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
@ -152,7 +169,7 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::wasm()).unwrap_or_else(|x| x).into()
main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
}

View file

@ -1,3 +1,4 @@
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::quote;
@ -11,8 +12,8 @@ struct Args {
entry: Option<String>,
}
pub fn riscv(args: syn::AttributeArgs) -> TokenStream {
let maybe_entry = match Args::from_list(&args) {
pub fn riscv(args: &[NestedMeta]) -> TokenStream {
let maybe_entry = match Args::from_list(args) {
Ok(args) => args.entry,
Err(e) => return e.write_errors(),
};
@ -77,9 +78,9 @@ pub fn std() -> TokenStream {
}
}
pub fn run(args: syn::AttributeArgs, f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
#[allow(unused_variables)]
let args = Args::from_list(&args).map_err(|e| e.write_errors())?;
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
let fargs = f.sig.inputs.clone();

View file

@ -1,20 +1,24 @@
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_quote, ItemFn, ReturnType, Type};
use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type};
use crate::util::ctxt::Ctxt;
#[derive(Debug, FromMeta)]
struct Args {
#[darling(default)]
pool_size: Option<usize>,
pool_size: Option<syn::Expr>,
}
pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
let args = Args::from_list(&args).map_err(|e| e.write_errors())?;
pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
let pool_size: usize = args.pool_size.unwrap_or(1);
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
attrs: vec![],
lit: Lit::Int(LitInt::new("1", Span::call_site())),
}));
let ctxt = Ctxt::new();
@ -45,10 +49,6 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
},
}
if pool_size < 1 {
ctxt.error_spanned_by(&f.sig, "pool_size must be 1 or greater");
}
let mut arg_names = Vec::new();
let mut fargs = f.sig.inputs.clone();
@ -82,7 +82,8 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
type Fut = impl ::core::future::Future + 'static;
static POOL: ::embassy_executor::raw::TaskPool<Fut, #pool_size> = ::embassy_executor::raw::TaskPool::new();
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::raw::TaskPool<Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
}
};

View file

@ -2,6 +2,14 @@
name = "embassy-net-driver-channel"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack."
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/"
@ -9,6 +17,9 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-d
features = ["defmt"]
target = "thumbv7em-none-eabi"
[package.metadata.docs.rs]
features = ["defmt"]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }

View file

@ -0,0 +1,96 @@
# embassy-net-driver-channel
This crate provides a toolkit for implementing [`embassy-net`](https://crates.io/crates/embassy-net) drivers in a
higher level way than implementing the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) trait directly.
The `embassy-net-driver` trait is polling-based. To implement it, you must write the packet receive/transmit state machines by
hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net`
knows when to poll your driver again to make more progress.
With `embassy-net-driver-channel`
## A note about deadlocks
When implementing a driver using this crate, it might be tempting to write it in the most straightforward way:
```rust,ignore
loop {
// Wait for either..
match select(
// ... the chip signaling an interrupt, indicating a packet is available to receive, or
irq_pin.wait_for_low(),
// ... a TX buffer becoming available, i.e. embassy-net wants to send a packet
tx_chan.tx_buf(),
).await {
Either::First(_) => {
// a packet is ready to be received!
let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue
let n = receive_packet_over_spi(buf).await;
rx_chan.rx_done(n);
}
Either::Second(buf) => {
// a packet is ready to be sent!
send_packet_over_spi(buf).await;
tx_chan.tx_done();
}
}
}
```
However, this code has a latent deadlock bug. The symptom is it can hang at `rx_chan.rx_buf().await` under load.
The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue.
The fix is to make sure to always service the TX queue while you're waiting for space to become available in the TX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available":
```rust,ignore
loop {
// Wait for either..
match select(
async {
// ... the chip signaling an interrupt, indicating a packet is available to receive
irq_pin.wait_for_low().await;
// *AND* the buffer is ready...
rx_chan.rx_buf().await
},
// ... or a TX buffer becoming available, i.e. embassy-net wants to send a packet
tx_chan.tx_buf(),
).await {
Either::First(buf) => {
// a packet is ready to be received!
let n = receive_packet_over_spi(buf).await;
rx_chan.rx_done(n);
}
Either::Second(buf) => {
// a packet is ready to be sent!
send_packet_over_spi(buf).await;
tx_chan.tx_done();
}
}
}
```
## Examples
These `embassy-net` drivers are implemented using this crate. You can look at them for inspiration.
- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W
- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support.
- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip.
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
## Interoperability
This crate can run on any executor.
## 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,5 @@
#![no_std]
#![doc = include_str!("../README.md")]
// must go first!
mod fmt;

View file

@ -3,7 +3,13 @@ name = "embassy-net-driver"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Driver trait for the `embassy-net` async TCP/IP network stack."
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/"
@ -11,5 +17,8 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-d
features = ["defmt"]
target = "thumbv7em-none-eabi"
[package.metadata.docs.rs]
features = ["defmt"]
[dependencies]
defmt = { version = "0.3", optional = true }

View file

@ -1,5 +1,21 @@
# embassy-net-driver
This crate contains the driver trait necessary for adding [`embassy-net`](https://crates.io/crates/embassy-net) support
for a new hardware platform.
If you want to *use* `embassy-net` with already made drivers, you should depend on the main `embassy-net` crate, not on this crate.
If you are writing a driver, you should depend only on this crate, not on the main `embassy-net` crate.
This will allow your driver to continue working for newer `embassy-net` major versions, without needing an update,
if the driver trait has not had breaking changes.
See also [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel), which provides a higer-level API
to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via
packet queues for RX and TX.
## Interoperability
This crate can run on any executor.
## License

View file

@ -0,0 +1,20 @@
[package]
name = "embassy-net-esp-hosted"
version = "0.1.0"
edition = "2021"
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.1.0", path = "../embassy-time" }
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
embedded-hal = { version = "1.0.0-alpha.10" }
embedded-hal-async = { version = "=0.2.0-alpha.1" }
noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] }
#noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] }
heapless = "0.7.16"

View file

@ -0,0 +1,139 @@
use ch::driver::LinkState;
use defmt::Debug2Format;
use embassy_net_driver_channel as ch;
use heapless::String;
use crate::ioctl::Shared;
use crate::proto::{self, CtrlMsg};
#[derive(Debug)]
pub struct Error {
pub status: u32,
}
pub struct Control<'a> {
state_ch: ch::StateRunner<'a>,
shared: &'a Shared,
}
#[allow(unused)]
enum WifiMode {
None = 0,
Sta = 1,
Ap = 2,
ApSta = 3,
}
impl<'a> Control<'a> {
pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self {
Self { state_ch, shared }
}
pub async fn init(&mut self) {
debug!("wait for init event...");
self.shared.init_wait().await;
debug!("set wifi mode");
self.set_wifi_mode(WifiMode::Sta as _).await;
let mac_addr = self.get_mac_addr().await;
debug!("mac addr: {:02x}", mac_addr);
self.state_ch.set_ethernet_address(mac_addr);
}
pub async fn join(&mut self, ssid: &str, password: &str) {
let req = proto::CtrlMsg {
msg_id: proto::CtrlMsgId::ReqConnectAp as _,
msg_type: proto::CtrlMsgType::Req as _,
payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp {
ssid: String::from(ssid),
pwd: String::from(password),
bssid: String::new(),
listen_interval: 3,
is_wpa3_supported: false,
})),
};
let resp = self.ioctl(req).await;
let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
debug!("======= {:?}", Debug2Format(&resp));
assert_eq!(resp.resp, 0);
self.state_ch.set_link_state(LinkState::Up);
}
async fn get_mac_addr(&mut self) -> [u8; 6] {
let req = proto::CtrlMsg {
msg_id: proto::CtrlMsgId::ReqGetMacAddress as _,
msg_type: proto::CtrlMsgType::Req as _,
payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress(
proto::CtrlMsgReqGetMacAddress {
mode: WifiMode::Sta as _,
},
)),
};
let resp = self.ioctl(req).await;
let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
assert_eq!(resp.resp, 0);
// WHY IS THIS A STRING? WHYYYY
fn nibble_from_hex(b: u8) -> u8 {
match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b + 0xa - b'a',
b'A'..=b'F' => b + 0xa - b'A',
_ => panic!("invalid hex digit {}", b),
}
}
let mac = resp.mac.as_bytes();
let mut res = [0; 6];
assert_eq!(mac.len(), 17);
for (i, b) in res.iter_mut().enumerate() {
*b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1])
}
res
}
async fn set_wifi_mode(&mut self, mode: u32) {
let req = proto::CtrlMsg {
msg_id: proto::CtrlMsgId::ReqSetWifiMode as _,
msg_type: proto::CtrlMsgType::Req as _,
payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })),
};
let resp = self.ioctl(req).await;
let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
assert_eq!(resp.resp, 0);
}
async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg {
debug!("ioctl req: {:?}", &req);
let mut buf = [0u8; 128];
let req_len = noproto::write(&req, &mut buf).unwrap();
struct CancelOnDrop<'a>(&'a Shared);
impl CancelOnDrop<'_> {
fn defuse(self) {
core::mem::forget(self);
}
}
impl Drop for CancelOnDrop<'_> {
fn drop(&mut self) {
self.0.ioctl_cancel();
}
}
let ioctl = CancelOnDrop(self.shared);
let resp_len = ioctl.0.ioctl(&mut buf, req_len).await;
ioctl.defuse();
let res = noproto::read(&buf[..resp_len]).unwrap();
debug!("ioctl resp: {:?}", &res);
res
}
}

View file

@ -0,0 +1,432 @@
syntax = "proto3";
/* Enums similar to ESP IDF */
enum Ctrl_VendorIEType {
Beacon = 0;
Probe_req = 1;
Probe_resp = 2;
Assoc_req = 3;
Assoc_resp = 4;
}
enum Ctrl_VendorIEID {
ID_0 = 0;
ID_1 = 1;
}
enum Ctrl_WifiMode {
NONE = 0;
STA = 1;
AP = 2;
APSTA = 3;
}
enum Ctrl_WifiBw {
BW_Invalid = 0;
HT20 = 1;
HT40 = 2;
}
enum Ctrl_WifiPowerSave {
PS_Invalid = 0;
MIN_MODEM = 1;
MAX_MODEM = 2;
}
enum Ctrl_WifiSecProt {
Open = 0;
WEP = 1;
WPA_PSK = 2;
WPA2_PSK = 3;
WPA_WPA2_PSK = 4;
WPA2_ENTERPRISE = 5;
WPA3_PSK = 6;
WPA2_WPA3_PSK = 7;
}
/* enums for Control path */
enum Ctrl_Status {
Connected = 0;
Not_Connected = 1;
No_AP_Found = 2;
Connection_Fail = 3;
Invalid_Argument = 4;
Out_Of_Range = 5;
}
enum CtrlMsgType {
MsgType_Invalid = 0;
Req = 1;
Resp = 2;
Event = 3;
MsgType_Max = 4;
}
enum CtrlMsgId {
MsgId_Invalid = 0;
/** Request Msgs **/
Req_Base = 100;
Req_GetMACAddress = 101;
Req_SetMacAddress = 102;
Req_GetWifiMode = 103;
Req_SetWifiMode = 104;
Req_GetAPScanList = 105;
Req_GetAPConfig = 106;
Req_ConnectAP = 107;
Req_DisconnectAP = 108;
Req_GetSoftAPConfig = 109;
Req_SetSoftAPVendorSpecificIE = 110;
Req_StartSoftAP = 111;
Req_GetSoftAPConnectedSTAList = 112;
Req_StopSoftAP = 113;
Req_SetPowerSaveMode = 114;
Req_GetPowerSaveMode = 115;
Req_OTABegin = 116;
Req_OTAWrite = 117;
Req_OTAEnd = 118;
Req_SetWifiMaxTxPower = 119;
Req_GetWifiCurrTxPower = 120;
Req_ConfigHeartbeat = 121;
/* Add new control path command response before Req_Max
* and update Req_Max */
Req_Max = 122;
/** Response Msgs **/
Resp_Base = 200;
Resp_GetMACAddress = 201;
Resp_SetMacAddress = 202;
Resp_GetWifiMode = 203;
Resp_SetWifiMode = 204;
Resp_GetAPScanList = 205;
Resp_GetAPConfig = 206;
Resp_ConnectAP = 207;
Resp_DisconnectAP = 208;
Resp_GetSoftAPConfig = 209;
Resp_SetSoftAPVendorSpecificIE = 210;
Resp_StartSoftAP = 211;
Resp_GetSoftAPConnectedSTAList = 212;
Resp_StopSoftAP = 213;
Resp_SetPowerSaveMode = 214;
Resp_GetPowerSaveMode = 215;
Resp_OTABegin = 216;
Resp_OTAWrite = 217;
Resp_OTAEnd = 218;
Resp_SetWifiMaxTxPower = 219;
Resp_GetWifiCurrTxPower = 220;
Resp_ConfigHeartbeat = 221;
/* Add new control path command response before Resp_Max
* and update Resp_Max */
Resp_Max = 222;
/** Event Msgs **/
Event_Base = 300;
Event_ESPInit = 301;
Event_Heartbeat = 302;
Event_StationDisconnectFromAP = 303;
Event_StationDisconnectFromESPSoftAP = 304;
/* Add new control path command notification before Event_Max
* and update Event_Max */
Event_Max = 305;
}
/* internal supporting structures for CtrlMsg */
message ScanResult {
bytes ssid = 1;
uint32 chnl = 2;
int32 rssi = 3;
bytes bssid = 4;
Ctrl_WifiSecProt sec_prot = 5;
}
message ConnectedSTAList {
bytes mac = 1;
int32 rssi = 2;
}
/* Control path structures */
/** Req/Resp structure **/
message CtrlMsg_Req_GetMacAddress {
int32 mode = 1;
}
message CtrlMsg_Resp_GetMacAddress {
bytes mac = 1;
int32 resp = 2;
}
message CtrlMsg_Req_GetMode {
}
message CtrlMsg_Resp_GetMode {
int32 mode = 1;
int32 resp = 2;
}
message CtrlMsg_Req_SetMode {
int32 mode = 1;
}
message CtrlMsg_Resp_SetMode {
int32 resp = 1;
}
message CtrlMsg_Req_GetStatus {
}
message CtrlMsg_Resp_GetStatus {
int32 resp = 1;
}
message CtrlMsg_Req_SetMacAddress {
bytes mac = 1;
int32 mode = 2;
}
message CtrlMsg_Resp_SetMacAddress {
int32 resp = 1;
}
message CtrlMsg_Req_GetAPConfig {
}
message CtrlMsg_Resp_GetAPConfig {
bytes ssid = 1;
bytes bssid = 2;
int32 rssi = 3;
int32 chnl = 4;
Ctrl_WifiSecProt sec_prot = 5;
int32 resp = 6;
}
message CtrlMsg_Req_ConnectAP {
string ssid = 1;
string pwd = 2;
string bssid = 3;
bool is_wpa3_supported = 4;
int32 listen_interval = 5;
}
message CtrlMsg_Resp_ConnectAP {
int32 resp = 1;
bytes mac = 2;
}
message CtrlMsg_Req_GetSoftAPConfig {
}
message CtrlMsg_Resp_GetSoftAPConfig {
bytes ssid = 1;
bytes pwd = 2;
int32 chnl = 3;
Ctrl_WifiSecProt sec_prot = 4;
int32 max_conn = 5;
bool ssid_hidden = 6;
int32 bw = 7;
int32 resp = 8;
}
message CtrlMsg_Req_StartSoftAP {
string ssid = 1;
string pwd = 2;
int32 chnl = 3;
Ctrl_WifiSecProt sec_prot = 4;
int32 max_conn = 5;
bool ssid_hidden = 6;
int32 bw = 7;
}
message CtrlMsg_Resp_StartSoftAP {
int32 resp = 1;
bytes mac = 2;
}
message CtrlMsg_Req_ScanResult {
}
message CtrlMsg_Resp_ScanResult {
uint32 count = 1;
repeated ScanResult entries = 2;
int32 resp = 3;
}
message CtrlMsg_Req_SoftAPConnectedSTA {
}
message CtrlMsg_Resp_SoftAPConnectedSTA {
uint32 num = 1;
repeated ConnectedSTAList stations = 2;
int32 resp = 3;
}
message CtrlMsg_Req_OTABegin {
}
message CtrlMsg_Resp_OTABegin {
int32 resp = 1;
}
message CtrlMsg_Req_OTAWrite {
bytes ota_data = 1;
}
message CtrlMsg_Resp_OTAWrite {
int32 resp = 1;
}
message CtrlMsg_Req_OTAEnd {
}
message CtrlMsg_Resp_OTAEnd {
int32 resp = 1;
}
message CtrlMsg_Req_VendorIEData {
int32 element_id = 1;
int32 length = 2;
bytes vendor_oui = 3;
int32 vendor_oui_type = 4;
bytes payload = 5;
}
message CtrlMsg_Req_SetSoftAPVendorSpecificIE {
bool enable = 1;
Ctrl_VendorIEType type = 2;
Ctrl_VendorIEID idx = 3;
CtrlMsg_Req_VendorIEData vendor_ie_data = 4;
}
message CtrlMsg_Resp_SetSoftAPVendorSpecificIE {
int32 resp = 1;
}
message CtrlMsg_Req_SetWifiMaxTxPower {
int32 wifi_max_tx_power = 1;
}
message CtrlMsg_Resp_SetWifiMaxTxPower {
int32 resp = 1;
}
message CtrlMsg_Req_GetWifiCurrTxPower {
}
message CtrlMsg_Resp_GetWifiCurrTxPower {
int32 wifi_curr_tx_power = 1;
int32 resp = 2;
}
message CtrlMsg_Req_ConfigHeartbeat {
bool enable = 1;
int32 duration = 2;
}
message CtrlMsg_Resp_ConfigHeartbeat {
int32 resp = 1;
}
/** Event structure **/
message CtrlMsg_Event_ESPInit {
bytes init_data = 1;
}
message CtrlMsg_Event_Heartbeat {
int32 hb_num = 1;
}
message CtrlMsg_Event_StationDisconnectFromAP {
int32 resp = 1;
}
message CtrlMsg_Event_StationDisconnectFromESPSoftAP {
int32 resp = 1;
bytes mac = 2;
}
message CtrlMsg {
/* msg_type could be req, resp or Event */
CtrlMsgType msg_type = 1;
/* msg id */
CtrlMsgId msg_id = 2;
/* union of all msg ids */
oneof payload {
/** Requests **/
CtrlMsg_Req_GetMacAddress req_get_mac_address = 101;
CtrlMsg_Req_SetMacAddress req_set_mac_address = 102;
CtrlMsg_Req_GetMode req_get_wifi_mode = 103;
CtrlMsg_Req_SetMode req_set_wifi_mode = 104;
CtrlMsg_Req_ScanResult req_scan_ap_list = 105;
CtrlMsg_Req_GetAPConfig req_get_ap_config = 106;
CtrlMsg_Req_ConnectAP req_connect_ap = 107;
CtrlMsg_Req_GetStatus req_disconnect_ap = 108;
CtrlMsg_Req_GetSoftAPConfig req_get_softap_config = 109;
CtrlMsg_Req_SetSoftAPVendorSpecificIE req_set_softap_vendor_specific_ie = 110;
CtrlMsg_Req_StartSoftAP req_start_softap = 111;
CtrlMsg_Req_SoftAPConnectedSTA req_softap_connected_stas_list = 112;
CtrlMsg_Req_GetStatus req_stop_softap = 113;
CtrlMsg_Req_SetMode req_set_power_save_mode = 114;
CtrlMsg_Req_GetMode req_get_power_save_mode = 115;
CtrlMsg_Req_OTABegin req_ota_begin = 116;
CtrlMsg_Req_OTAWrite req_ota_write = 117;
CtrlMsg_Req_OTAEnd req_ota_end = 118;
CtrlMsg_Req_SetWifiMaxTxPower req_set_wifi_max_tx_power = 119;
CtrlMsg_Req_GetWifiCurrTxPower req_get_wifi_curr_tx_power = 120;
CtrlMsg_Req_ConfigHeartbeat req_config_heartbeat = 121;
/** Responses **/
CtrlMsg_Resp_GetMacAddress resp_get_mac_address = 201;
CtrlMsg_Resp_SetMacAddress resp_set_mac_address = 202;
CtrlMsg_Resp_GetMode resp_get_wifi_mode = 203;
CtrlMsg_Resp_SetMode resp_set_wifi_mode = 204;
CtrlMsg_Resp_ScanResult resp_scan_ap_list = 205;
CtrlMsg_Resp_GetAPConfig resp_get_ap_config = 206;
CtrlMsg_Resp_ConnectAP resp_connect_ap = 207;
CtrlMsg_Resp_GetStatus resp_disconnect_ap = 208;
CtrlMsg_Resp_GetSoftAPConfig resp_get_softap_config = 209;
CtrlMsg_Resp_SetSoftAPVendorSpecificIE resp_set_softap_vendor_specific_ie = 210;
CtrlMsg_Resp_StartSoftAP resp_start_softap = 211;
CtrlMsg_Resp_SoftAPConnectedSTA resp_softap_connected_stas_list = 212;
CtrlMsg_Resp_GetStatus resp_stop_softap = 213;
CtrlMsg_Resp_SetMode resp_set_power_save_mode = 214;
CtrlMsg_Resp_GetMode resp_get_power_save_mode = 215;
CtrlMsg_Resp_OTABegin resp_ota_begin = 216;
CtrlMsg_Resp_OTAWrite resp_ota_write = 217;
CtrlMsg_Resp_OTAEnd resp_ota_end = 218;
CtrlMsg_Resp_SetWifiMaxTxPower resp_set_wifi_max_tx_power = 219;
CtrlMsg_Resp_GetWifiCurrTxPower resp_get_wifi_curr_tx_power = 220;
CtrlMsg_Resp_ConfigHeartbeat resp_config_heartbeat = 221;
/** Notifications **/
CtrlMsg_Event_ESPInit event_esp_init = 301;
CtrlMsg_Event_Heartbeat event_heartbeat = 302;
CtrlMsg_Event_StationDisconnectFromAP event_station_disconnect_from_AP = 303;
CtrlMsg_Event_StationDisconnectFromESPSoftAP event_station_disconnect_from_ESP_SoftAP = 304;
}
}

View file

@ -0,0 +1,257 @@
#![macro_use]
#![allow(unused_macros)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*);
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> Display for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> LowerHex for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Bytes<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "{:02x}", self.0)
}
}

View file

@ -0,0 +1,123 @@
use core::cell::RefCell;
use core::future::poll_fn;
use core::task::Poll;
use embassy_sync::waitqueue::WakerRegistration;
use crate::fmt::Bytes;
#[derive(Clone, Copy)]
pub struct PendingIoctl {
pub buf: *mut [u8],
pub req_len: usize,
}
#[derive(Clone, Copy)]
enum IoctlState {
Pending(PendingIoctl),
Sent { buf: *mut [u8] },
Done { resp_len: usize },
}
pub struct Shared(RefCell<SharedInner>);
struct SharedInner {
ioctl: IoctlState,
is_init: bool,
control_waker: WakerRegistration,
runner_waker: WakerRegistration,
}
impl Shared {
pub fn new() -> Self {
Self(RefCell::new(SharedInner {
ioctl: IoctlState::Done { resp_len: 0 },
is_init: false,
control_waker: WakerRegistration::new(),
runner_waker: WakerRegistration::new(),
}))
}
pub async fn ioctl_wait_complete(&self) -> usize {
poll_fn(|cx| {
let mut this = self.0.borrow_mut();
if let IoctlState::Done { resp_len } = this.ioctl {
Poll::Ready(resp_len)
} else {
this.control_waker.register(cx.waker());
Poll::Pending
}
})
.await
}
pub async fn ioctl_wait_pending(&self) -> PendingIoctl {
let pending = poll_fn(|cx| {
let mut this = self.0.borrow_mut();
if let IoctlState::Pending(pending) = this.ioctl {
Poll::Ready(pending)
} else {
this.runner_waker.register(cx.waker());
Poll::Pending
}
})
.await;
self.0.borrow_mut().ioctl = IoctlState::Sent { buf: pending.buf };
pending
}
pub fn ioctl_cancel(&self) {
self.0.borrow_mut().ioctl = IoctlState::Done { resp_len: 0 };
}
pub async fn ioctl(&self, buf: &mut [u8], req_len: usize) -> usize {
trace!("ioctl req bytes: {:02x}", Bytes(&buf[..req_len]));
{
let mut this = self.0.borrow_mut();
this.ioctl = IoctlState::Pending(PendingIoctl { buf, req_len });
this.runner_waker.wake();
}
self.ioctl_wait_complete().await
}
pub fn ioctl_done(&self, response: &[u8]) {
let mut this = self.0.borrow_mut();
if let IoctlState::Sent { buf } = this.ioctl {
trace!("ioctl resp bytes: {:02x}", Bytes(response));
// TODO fix this
(unsafe { &mut *buf }[..response.len()]).copy_from_slice(response);
this.ioctl = IoctlState::Done {
resp_len: response.len(),
};
this.control_waker.wake();
} else {
warn!("IOCTL Response but no pending Ioctl");
}
}
// // // // // // // // // // // // // // // // // // // //
pub fn init_done(&self) {
let mut this = self.0.borrow_mut();
this.is_init = true;
this.control_waker.wake();
}
pub async fn init_wait(&self) {
poll_fn(|cx| {
let mut this = self.0.borrow_mut();
if this.is_init {
Poll::Ready(())
} else {
this.control_waker.register(cx.waker());
Poll::Pending
}
})
.await
}
}

View file

@ -0,0 +1,337 @@
#![no_std]
use control::Control;
use embassy_futures::select::{select3, Either3};
use embassy_net_driver_channel as ch;
use embassy_time::{Duration, Instant, Timer};
use embedded_hal::digital::{InputPin, OutputPin};
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiDevice;
use ioctl::Shared;
use proto::CtrlMsg;
use crate::ioctl::PendingIoctl;
use crate::proto::CtrlMsgPayload;
mod proto;
// must be first
mod fmt;
mod control;
mod ioctl;
const MTU: usize = 1514;
macro_rules! impl_bytes {
($t:ident) => {
impl $t {
pub const SIZE: usize = core::mem::size_of::<Self>();
#[allow(unused)]
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
unsafe { core::mem::transmute(*self) }
}
#[allow(unused)]
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self {
let alignment = core::mem::align_of::<Self>();
assert_eq!(
bytes.as_ptr().align_offset(alignment),
0,
"{} is not aligned",
core::any::type_name::<Self>()
);
unsafe { core::mem::transmute(bytes) }
}
#[allow(unused)]
pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self {
let alignment = core::mem::align_of::<Self>();
assert_eq!(
bytes.as_ptr().align_offset(alignment),
0,
"{} is not aligned",
core::any::type_name::<Self>()
);
unsafe { core::mem::transmute(bytes) }
}
}
};
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
struct PayloadHeader {
/// InterfaceType on lower 4 bits, number on higher 4 bits.
if_type_and_num: u8,
/// Flags.
///
/// bit 0: more fragments.
flags: u8,
len: u16,
offset: u16,
checksum: u16,
seq_num: u16,
reserved2: u8,
/// Packet type for HCI or PRIV interface, reserved otherwise
hci_priv_packet_type: u8,
}
impl_bytes!(PayloadHeader);
#[allow(unused)]
#[repr(u8)]
enum InterfaceType {
Sta = 0,
Ap = 1,
Serial = 2,
Hci = 3,
Priv = 4,
Test = 5,
}
const MAX_SPI_BUFFER_SIZE: usize = 1600;
pub struct State {
shared: Shared,
ch: ch::State<MTU, 4, 4>,
}
impl State {
pub fn new() -> Self {
Self {
shared: Shared::new(),
ch: ch::State::new(),
}
}
}
pub type NetDriver<'a> = ch::Device<'a, MTU>;
pub async fn new<'a, SPI, IN, OUT>(
state: &'a mut State,
spi: SPI,
handshake: IN,
ready: IN,
reset: OUT,
) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>)
where
SPI: SpiDevice,
IN: InputPin + Wait,
OUT: OutputPin,
{
let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
let state_ch = ch_runner.state_runner();
let mut runner = Runner {
ch: ch_runner,
shared: &state.shared,
next_seq: 1,
handshake,
ready,
reset,
spi,
};
runner.init().await;
(device, Control::new(state_ch, &state.shared), runner)
}
pub struct Runner<'a, SPI, IN, OUT> {
ch: ch::Runner<'a, MTU>,
shared: &'a Shared,
next_seq: u16,
spi: SPI,
handshake: IN,
ready: IN,
reset: OUT,
}
impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT>
where
SPI: SpiDevice,
IN: InputPin + Wait,
OUT: OutputPin,
{
async fn init(&mut self) {}
pub async fn run(mut self) -> ! {
debug!("resetting...");
self.reset.set_low().unwrap();
Timer::after(Duration::from_millis(100)).await;
self.reset.set_high().unwrap();
Timer::after(Duration::from_millis(1000)).await;
let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE];
let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE];
loop {
self.handshake.wait_for_high().await.unwrap();
let ioctl = self.shared.ioctl_wait_pending();
let tx = self.ch.tx_buf();
let ev = async { self.ready.wait_for_high().await.unwrap() };
match select3(ioctl, tx, ev).await {
Either3::First(PendingIoctl { buf, req_len }) => {
tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02");
tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes());
tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]);
let mut header = PayloadHeader {
if_type_and_num: InterfaceType::Serial as _,
len: (req_len + 14) as _,
offset: PayloadHeader::SIZE as _,
seq_num: self.next_seq,
..Default::default()
};
self.next_seq = self.next_seq.wrapping_add(1);
// Calculate checksum
tx_buf[0..12].copy_from_slice(&header.to_bytes());
header.checksum = checksum(&tx_buf[..26 + req_len]);
tx_buf[0..12].copy_from_slice(&header.to_bytes());
}
Either3::Second(packet) => {
tx_buf[12..][..packet.len()].copy_from_slice(packet);
let mut header = PayloadHeader {
if_type_and_num: InterfaceType::Sta as _,
len: packet.len() as _,
offset: PayloadHeader::SIZE as _,
seq_num: self.next_seq,
..Default::default()
};
self.next_seq = self.next_seq.wrapping_add(1);
// Calculate checksum
tx_buf[0..12].copy_from_slice(&header.to_bytes());
header.checksum = checksum(&tx_buf[..12 + packet.len()]);
tx_buf[0..12].copy_from_slice(&header.to_bytes());
self.ch.tx_done();
}
Either3::Third(()) => {
tx_buf[..PayloadHeader::SIZE].fill(0);
}
}
if tx_buf[0] != 0 {
trace!("tx: {:02x}", &tx_buf[..40]);
}
self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap();
// The esp-hosted firmware deasserts the HANSHAKE pin a few us AFTER ending the SPI transfer
// If we check it again too fast, we'll see it's high from the previous transfer, and if we send it
// data it will get lost.
// Make sure we check it after 100us at minimum.
let delay_until = Instant::now() + Duration::from_micros(100);
self.handle_rx(&mut rx_buf);
Timer::at(delay_until).await;
}
}
fn handle_rx(&mut self, buf: &mut [u8]) {
trace!("rx: {:02x}", &buf[..40]);
let buf_len = buf.len();
let h = PayloadHeader::from_bytes_mut((&mut buf[..PayloadHeader::SIZE]).try_into().unwrap());
if h.len == 0 || h.offset as usize != PayloadHeader::SIZE {
return;
}
let payload_len = h.len as usize;
if buf_len < PayloadHeader::SIZE + payload_len {
warn!("rx: len too big");
return;
}
let if_type_and_num = h.if_type_and_num;
let want_checksum = h.checksum;
h.checksum = 0;
let got_checksum = checksum(&buf[..PayloadHeader::SIZE + payload_len]);
if want_checksum != got_checksum {
warn!("rx: bad checksum. Got {:04x}, want {:04x}", got_checksum, want_checksum);
return;
}
let payload = &mut buf[PayloadHeader::SIZE..][..payload_len];
match if_type_and_num & 0x0f {
// STA
0 => match self.ch.try_rx_buf() {
Some(buf) => {
buf[..payload.len()].copy_from_slice(payload);
self.ch.rx_done(payload.len())
}
None => warn!("failed to push rxd packet to the channel."),
},
// serial
2 => {
trace!("serial rx: {:02x}", payload);
if payload.len() < 14 {
warn!("serial rx: too short");
return;
}
let is_event = match &payload[..12] {
b"\x01\x08\x00ctrlResp\x02" => false,
b"\x01\x08\x00ctrlEvnt\x02" => true,
_ => {
warn!("serial rx: bad tlv");
return;
}
};
let len = u16::from_le_bytes(payload[12..14].try_into().unwrap()) as usize;
if payload.len() < 14 + len {
warn!("serial rx: too short 2");
return;
}
let data = &payload[14..][..len];
if is_event {
self.handle_event(data);
} else {
self.shared.ioctl_done(data);
}
}
_ => warn!("unknown iftype {}", if_type_and_num),
}
}
fn handle_event(&self, data: &[u8]) {
let Ok(event) = noproto::read::<CtrlMsg>(data) else {
warn!("failed to parse event");
return
};
debug!("event: {:?}", &event);
let Some(payload) = &event.payload else {
warn!("event without payload?");
return
};
match payload {
CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(),
_ => {}
}
}
}
fn checksum(buf: &[u8]) -> u16 {
let mut res = 0u16;
for &b in buf {
res = res.wrapping_add(b as _);
}
res
}

View file

@ -0,0 +1,652 @@
use heapless::{String, Vec};
/// internal supporting structures for CtrlMsg
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ScanResult {
#[noproto(tag = "1")]
pub ssid: String<32>,
#[noproto(tag = "2")]
pub chnl: u32,
#[noproto(tag = "3")]
pub rssi: u32,
#[noproto(tag = "4")]
pub bssid: String<32>,
#[noproto(tag = "5")]
pub sec_prot: CtrlWifiSecProt,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConnectedStaList {
#[noproto(tag = "1")]
pub mac: String<32>,
#[noproto(tag = "2")]
pub rssi: u32,
}
/// * Req/Resp structure *
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqGetMacAddress {
#[noproto(tag = "1")]
pub mode: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespGetMacAddress {
#[noproto(tag = "1")]
pub mac: String<32>,
#[noproto(tag = "2")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqGetMode {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespGetMode {
#[noproto(tag = "1")]
pub mode: u32,
#[noproto(tag = "2")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqSetMode {
#[noproto(tag = "1")]
pub mode: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespSetMode {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqGetStatus {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespGetStatus {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqSetMacAddress {
#[noproto(tag = "1")]
pub mac: String<32>,
#[noproto(tag = "2")]
pub mode: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespSetMacAddress {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqGetApConfig {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespGetApConfig {
#[noproto(tag = "1")]
pub ssid: String<32>,
#[noproto(tag = "2")]
pub bssid: String<32>,
#[noproto(tag = "3")]
pub rssi: u32,
#[noproto(tag = "4")]
pub chnl: u32,
#[noproto(tag = "5")]
pub sec_prot: CtrlWifiSecProt,
#[noproto(tag = "6")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqConnectAp {
#[noproto(tag = "1")]
pub ssid: String<32>,
#[noproto(tag = "2")]
pub pwd: String<32>,
#[noproto(tag = "3")]
pub bssid: String<32>,
#[noproto(tag = "4")]
pub is_wpa3_supported: bool,
#[noproto(tag = "5")]
pub listen_interval: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespConnectAp {
#[noproto(tag = "1")]
pub resp: u32,
#[noproto(tag = "2")]
pub mac: String<32>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqGetSoftApConfig {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespGetSoftApConfig {
#[noproto(tag = "1")]
pub ssid: String<32>,
#[noproto(tag = "2")]
pub pwd: String<32>,
#[noproto(tag = "3")]
pub chnl: u32,
#[noproto(tag = "4")]
pub sec_prot: CtrlWifiSecProt,
#[noproto(tag = "5")]
pub max_conn: u32,
#[noproto(tag = "6")]
pub ssid_hidden: bool,
#[noproto(tag = "7")]
pub bw: u32,
#[noproto(tag = "8")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqStartSoftAp {
#[noproto(tag = "1")]
pub ssid: String<32>,
#[noproto(tag = "2")]
pub pwd: String<32>,
#[noproto(tag = "3")]
pub chnl: u32,
#[noproto(tag = "4")]
pub sec_prot: CtrlWifiSecProt,
#[noproto(tag = "5")]
pub max_conn: u32,
#[noproto(tag = "6")]
pub ssid_hidden: bool,
#[noproto(tag = "7")]
pub bw: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespStartSoftAp {
#[noproto(tag = "1")]
pub resp: u32,
#[noproto(tag = "2")]
pub mac: String<32>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqScanResult {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespScanResult {
#[noproto(tag = "1")]
pub count: u32,
#[noproto(repeated, tag = "2")]
pub entries: Vec<ScanResult, 16>,
#[noproto(tag = "3")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqSoftApConnectedSta {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespSoftApConnectedSta {
#[noproto(tag = "1")]
pub num: u32,
#[noproto(repeated, tag = "2")]
pub stations: Vec<ConnectedStaList, 16>,
#[noproto(tag = "3")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqOtaBegin {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespOtaBegin {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqOtaWrite {
#[noproto(tag = "1")]
pub ota_data: Vec<u8, 1024>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespOtaWrite {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqOtaEnd {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespOtaEnd {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqVendorIeData {
#[noproto(tag = "1")]
pub element_id: u32,
#[noproto(tag = "2")]
pub length: u32,
#[noproto(tag = "3")]
pub vendor_oui: Vec<u8, 8>,
#[noproto(tag = "4")]
pub vendor_oui_type: u32,
#[noproto(tag = "5")]
pub payload: Vec<u8, 64>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqSetSoftApVendorSpecificIe {
#[noproto(tag = "1")]
pub enable: bool,
#[noproto(tag = "2")]
pub r#type: CtrlVendorIeType,
#[noproto(tag = "3")]
pub idx: CtrlVendorIeid,
#[noproto(optional, tag = "4")]
pub vendor_ie_data: Option<CtrlMsgReqVendorIeData>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespSetSoftApVendorSpecificIe {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqSetWifiMaxTxPower {
#[noproto(tag = "1")]
pub wifi_max_tx_power: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespSetWifiMaxTxPower {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqGetWifiCurrTxPower {}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespGetWifiCurrTxPower {
#[noproto(tag = "1")]
pub wifi_curr_tx_power: u32,
#[noproto(tag = "2")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgReqConfigHeartbeat {
#[noproto(tag = "1")]
pub enable: bool,
#[noproto(tag = "2")]
pub duration: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgRespConfigHeartbeat {
#[noproto(tag = "1")]
pub resp: u32,
}
/// * Event structure *
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgEventEspInit {
#[noproto(tag = "1")]
pub init_data: Vec<u8, 64>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgEventHeartbeat {
#[noproto(tag = "1")]
pub hb_num: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgEventStationDisconnectFromAp {
#[noproto(tag = "1")]
pub resp: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsgEventStationDisconnectFromEspSoftAp {
#[noproto(tag = "1")]
pub resp: u32,
#[noproto(tag = "2")]
pub mac: String<32>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtrlMsg {
/// msg_type could be req, resp or Event
#[noproto(tag = "1")]
pub msg_type: CtrlMsgType,
/// msg id
#[noproto(tag = "2")]
pub msg_id: CtrlMsgId,
/// union of all msg ids
#[noproto(
oneof,
tags = "101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 301, 302, 303, 304"
)]
pub payload: Option<CtrlMsgPayload>,
}
/// union of all msg ids
#[derive(Debug, Clone, Eq, PartialEq, noproto::Oneof)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlMsgPayload {
/// * Requests *
#[noproto(tag = "101")]
ReqGetMacAddress(CtrlMsgReqGetMacAddress),
#[noproto(tag = "102")]
ReqSetMacAddress(CtrlMsgReqSetMacAddress),
#[noproto(tag = "103")]
ReqGetWifiMode(CtrlMsgReqGetMode),
#[noproto(tag = "104")]
ReqSetWifiMode(CtrlMsgReqSetMode),
#[noproto(tag = "105")]
ReqScanApList(CtrlMsgReqScanResult),
#[noproto(tag = "106")]
ReqGetApConfig(CtrlMsgReqGetApConfig),
#[noproto(tag = "107")]
ReqConnectAp(CtrlMsgReqConnectAp),
#[noproto(tag = "108")]
ReqDisconnectAp(CtrlMsgReqGetStatus),
#[noproto(tag = "109")]
ReqGetSoftapConfig(CtrlMsgReqGetSoftApConfig),
#[noproto(tag = "110")]
ReqSetSoftapVendorSpecificIe(CtrlMsgReqSetSoftApVendorSpecificIe),
#[noproto(tag = "111")]
ReqStartSoftap(CtrlMsgReqStartSoftAp),
#[noproto(tag = "112")]
ReqSoftapConnectedStasList(CtrlMsgReqSoftApConnectedSta),
#[noproto(tag = "113")]
ReqStopSoftap(CtrlMsgReqGetStatus),
#[noproto(tag = "114")]
ReqSetPowerSaveMode(CtrlMsgReqSetMode),
#[noproto(tag = "115")]
ReqGetPowerSaveMode(CtrlMsgReqGetMode),
#[noproto(tag = "116")]
ReqOtaBegin(CtrlMsgReqOtaBegin),
#[noproto(tag = "117")]
ReqOtaWrite(CtrlMsgReqOtaWrite),
#[noproto(tag = "118")]
ReqOtaEnd(CtrlMsgReqOtaEnd),
#[noproto(tag = "119")]
ReqSetWifiMaxTxPower(CtrlMsgReqSetWifiMaxTxPower),
#[noproto(tag = "120")]
ReqGetWifiCurrTxPower(CtrlMsgReqGetWifiCurrTxPower),
#[noproto(tag = "121")]
ReqConfigHeartbeat(CtrlMsgReqConfigHeartbeat),
/// * Responses *
#[noproto(tag = "201")]
RespGetMacAddress(CtrlMsgRespGetMacAddress),
#[noproto(tag = "202")]
RespSetMacAddress(CtrlMsgRespSetMacAddress),
#[noproto(tag = "203")]
RespGetWifiMode(CtrlMsgRespGetMode),
#[noproto(tag = "204")]
RespSetWifiMode(CtrlMsgRespSetMode),
#[noproto(tag = "205")]
RespScanApList(CtrlMsgRespScanResult),
#[noproto(tag = "206")]
RespGetApConfig(CtrlMsgRespGetApConfig),
#[noproto(tag = "207")]
RespConnectAp(CtrlMsgRespConnectAp),
#[noproto(tag = "208")]
RespDisconnectAp(CtrlMsgRespGetStatus),
#[noproto(tag = "209")]
RespGetSoftapConfig(CtrlMsgRespGetSoftApConfig),
#[noproto(tag = "210")]
RespSetSoftapVendorSpecificIe(CtrlMsgRespSetSoftApVendorSpecificIe),
#[noproto(tag = "211")]
RespStartSoftap(CtrlMsgRespStartSoftAp),
#[noproto(tag = "212")]
RespSoftapConnectedStasList(CtrlMsgRespSoftApConnectedSta),
#[noproto(tag = "213")]
RespStopSoftap(CtrlMsgRespGetStatus),
#[noproto(tag = "214")]
RespSetPowerSaveMode(CtrlMsgRespSetMode),
#[noproto(tag = "215")]
RespGetPowerSaveMode(CtrlMsgRespGetMode),
#[noproto(tag = "216")]
RespOtaBegin(CtrlMsgRespOtaBegin),
#[noproto(tag = "217")]
RespOtaWrite(CtrlMsgRespOtaWrite),
#[noproto(tag = "218")]
RespOtaEnd(CtrlMsgRespOtaEnd),
#[noproto(tag = "219")]
RespSetWifiMaxTxPower(CtrlMsgRespSetWifiMaxTxPower),
#[noproto(tag = "220")]
RespGetWifiCurrTxPower(CtrlMsgRespGetWifiCurrTxPower),
#[noproto(tag = "221")]
RespConfigHeartbeat(CtrlMsgRespConfigHeartbeat),
/// * Notifications *
#[noproto(tag = "301")]
EventEspInit(CtrlMsgEventEspInit),
#[noproto(tag = "302")]
EventHeartbeat(CtrlMsgEventHeartbeat),
#[noproto(tag = "303")]
EventStationDisconnectFromAp(CtrlMsgEventStationDisconnectFromAp),
#[noproto(tag = "304")]
EventStationDisconnectFromEspSoftAp(CtrlMsgEventStationDisconnectFromEspSoftAp),
}
/// Enums similar to ESP IDF
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlVendorIeType {
#[default]
Beacon = 0,
ProbeReq = 1,
ProbeResp = 2,
AssocReq = 3,
AssocResp = 4,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlVendorIeid {
#[default]
Id0 = 0,
Id1 = 1,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlWifiMode {
#[default]
None = 0,
Sta = 1,
Ap = 2,
Apsta = 3,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlWifiBw {
#[default]
BwInvalid = 0,
Ht20 = 1,
Ht40 = 2,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlWifiPowerSave {
#[default]
PsInvalid = 0,
MinModem = 1,
MaxModem = 2,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlWifiSecProt {
#[default]
Open = 0,
Wep = 1,
WpaPsk = 2,
Wpa2Psk = 3,
WpaWpa2Psk = 4,
Wpa2Enterprise = 5,
Wpa3Psk = 6,
Wpa2Wpa3Psk = 7,
}
/// enums for Control path
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlStatus {
#[default]
Connected = 0,
NotConnected = 1,
NoApFound = 2,
ConnectionFail = 3,
InvalidArgument = 4,
OutOfRange = 5,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlMsgType {
#[default]
MsgTypeInvalid = 0,
Req = 1,
Resp = 2,
Event = 3,
MsgTypeMax = 4,
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtrlMsgId {
#[default]
MsgIdInvalid = 0,
/// * Request Msgs *
ReqBase = 100,
ReqGetMacAddress = 101,
ReqSetMacAddress = 102,
ReqGetWifiMode = 103,
ReqSetWifiMode = 104,
ReqGetApScanList = 105,
ReqGetApConfig = 106,
ReqConnectAp = 107,
ReqDisconnectAp = 108,
ReqGetSoftApConfig = 109,
ReqSetSoftApVendorSpecificIe = 110,
ReqStartSoftAp = 111,
ReqGetSoftApConnectedStaList = 112,
ReqStopSoftAp = 113,
ReqSetPowerSaveMode = 114,
ReqGetPowerSaveMode = 115,
ReqOtaBegin = 116,
ReqOtaWrite = 117,
ReqOtaEnd = 118,
ReqSetWifiMaxTxPower = 119,
ReqGetWifiCurrTxPower = 120,
ReqConfigHeartbeat = 121,
/// Add new control path command response before Req_Max
/// and update Req_Max
ReqMax = 122,
/// * Response Msgs *
RespBase = 200,
RespGetMacAddress = 201,
RespSetMacAddress = 202,
RespGetWifiMode = 203,
RespSetWifiMode = 204,
RespGetApScanList = 205,
RespGetApConfig = 206,
RespConnectAp = 207,
RespDisconnectAp = 208,
RespGetSoftApConfig = 209,
RespSetSoftApVendorSpecificIe = 210,
RespStartSoftAp = 211,
RespGetSoftApConnectedStaList = 212,
RespStopSoftAp = 213,
RespSetPowerSaveMode = 214,
RespGetPowerSaveMode = 215,
RespOtaBegin = 216,
RespOtaWrite = 217,
RespOtaEnd = 218,
RespSetWifiMaxTxPower = 219,
RespGetWifiCurrTxPower = 220,
RespConfigHeartbeat = 221,
/// Add new control path command response before Resp_Max
/// and update Resp_Max
RespMax = 222,
/// * Event Msgs *
EventBase = 300,
EventEspInit = 301,
EventHeartbeat = 302,
EventStationDisconnectFromAp = 303,
EventStationDisconnectFromEspSoftAp = 304,
/// Add new control path command notification before Event_Max
/// and update Event_Max
EventMax = 305,
}

View file

@ -1,5 +1,6 @@
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip.
#![no_std]
/// [`embassy-net`](crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip.
mod device;
mod socket;
mod spi;
@ -77,7 +78,7 @@ impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> {
}
}
/// Obtain a driver for using the W5500 with [`embassy-net`](crates.io/crates/embassy-net).
/// Obtain a driver for using the W5500 with [`embassy-net`](https://crates.io/crates/embassy-net).
pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>(
mac_addr: [u8; 6],
state: &'a mut State<N_RX, N_TX>,

View file

@ -3,7 +3,13 @@ name = "embassy-net"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Async TCP/IP network stack for embedded systems"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/"
@ -38,13 +44,12 @@ igmp = ["smoltcp/proto-igmp"]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
smoltcp = { version = "0.9.0", default-features = false, features = [
smoltcp = { version = "0.10.0", default-features = false, features = [
"socket",
"async",
]}
] }
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-sync = { version = "0.2.0", path = "../embassy-sync" }
embedded-io = { version = "0.4.0", optional = true }

View file

@ -1,30 +1,56 @@
# embassy-net
embassy-net contains an async network API based on smoltcp and embassy, designed
for embedded systems.
`embassy-net` is a no-std no-alloc async network stack, designed for embedded systems.
## Running the example
It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated
API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and
memory management designed to work well for embedded systems, aiiming for a more "Just Works" experience.
First, create the tap0 interface. You only need to do this once.
## Features
```sh
sudo ip tuntap add name tap0 mode tap user $USER
sudo ip link set tap0 up
sudo ip addr add 192.168.69.100/24 dev tap0
sudo ip -6 addr add fe80::100/64 dev tap0
sudo ip -6 addr add fdaa::100/64 dev tap0
sudo ip -6 route add fe80::/64 dev tap0
sudo ip -6 route add fdaa::/64 dev tap0
```
- IPv4, IPv6
- Ethernet and bare-IP mediums.
- TCP, UDP, DNS, DHCPv4, IGMPv4
- TCP sockets implement the `embedded-io` async traits.
Second, have something listening there. For example `nc -l 8000`
See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and
unimplemented features of the network protocols.
Then run the example located in the `examples` folder:
## Hardware support
```sh
cd $EMBASSY_ROOT/examples/std/
cargo run --bin net -- --static-ip
```
- [`esp-wifi`](https://github.com/esp-rs/esp-wifi) for WiFi support on bare-metal ESP32 chips. Maintained by Espressif.
- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W
- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support.
- [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5).
- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip.
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
## Examples
- For usage with Embassy HALs and network chip drivers, search [here](https://github.com/embassy-rs/embassy/tree/main/examples) for `eth` or `wifi`.
- The [`esp-wifi` repo](https://github.com/esp-rs/esp-wifi) has examples for use on bare-metal ESP32 chips.
- For usage on `std` platforms, see [the `std` examples](https://github.com/embassy-rs/embassy/tree/main/examples/std/src/bin)
## Adding support for new hardware
To add `embassy-net` support for new hardware (i.e. a new Ethernet or WiFi chip, or
an Ethernet/WiFi MCU peripheral), you have to implement the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver)
traits.
Alternatively, [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel) provides a higer-level API
to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via
packet queues for RX and TX.
Drivers should depend only on `embassy-net-driver` or `embassy-net-driver-channel`. Never on the main `embassy-net` crate.
This allows existing drivers to continue working for newer `embassy-net` major versions, without needing an update, if the driver
trait has not had breaking changes.
## Interoperability
This crate can run on any executor.
[`embassy-time`](https://crates.io/crates/embassy-net-driver) is used for timekeeping and timeouts. You must
link an `embassy-time` driver in your project to use this crate.
## License

View file

@ -51,8 +51,9 @@ where
Medium::Ethernet => phy::Medium::Ethernet,
#[cfg(feature = "medium-ip")]
Medium::Ip => phy::Medium::Ip,
#[allow(unreachable_patterns)]
_ => panic!(
"Unsupported medium {:?}. MAke sure to enable it in embassy-net's Cargo features.",
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
caps.medium
),
};

View file

@ -57,7 +57,7 @@ pub struct StackResources<const SOCK: usize> {
impl<const SOCK: usize> StackResources<SOCK> {
/// Create a new set of stack resources.
pub fn new() -> Self {
pub const fn new() -> Self {
#[cfg(feature = "dns")]
const INIT: Option<dns::DnsQuery> = None;
Self {
@ -235,12 +235,19 @@ impl<D: Driver + 'static> Stack<D> {
#[cfg(feature = "medium-ethernet")]
let medium = device.capabilities().medium;
let mut iface_cfg = smoltcp::iface::Config::new();
let hardware_addr = match medium {
#[cfg(feature = "medium-ethernet")]
Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())),
#[cfg(feature = "medium-ip")]
Medium::Ip => HardwareAddress::Ip,
#[allow(unreachable_patterns)]
_ => panic!(
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
medium
),
};
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
iface_cfg.random_seed = random_seed;
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
iface_cfg.hardware_addr = Some(HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())));
}
let iface = Interface::new(
iface_cfg,
@ -248,6 +255,7 @@ impl<D: Driver + 'static> Stack<D> {
inner: &mut device,
cx: None,
},
instant_to_smoltcp(Instant::now()),
);
let sockets = SocketSet::new(&mut resources.sockets[..]);
@ -411,7 +419,29 @@ impl<D: Driver + 'static> Stack<D> {
})
.await?;
use embassy_hal_common::drop::OnDrop;
#[must_use = "to delay the drop handler invocation to the end of the scope"]
struct OnDrop<F: FnOnce()> {
f: core::mem::MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
fn new(f: F) -> Self {
Self {
f: core::mem::MaybeUninit::new(f),
}
}
fn defuse(self) {
core::mem::forget(self)
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}
let drop = OnDrop::new(|| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);

View file

@ -104,7 +104,7 @@ impl<'a> UdpSocket<'a> {
pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.recv_slice(buf) {
Ok(x) => Poll::Ready(Ok(x)),
Ok((n, meta)) => Poll::Ready(Ok((n, meta.endpoint))),
// No data ready
Err(udp::RecvError::Exhausted) => {
s.register_recv_waker(cx.waker());

View file

@ -410,13 +410,13 @@ pub fn init(config: config::Config) -> Peripherals {
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`."
To fix this, erase UICR manually, for example using `probe-rs 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`."
To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`."
);
}
}
@ -432,7 +432,7 @@ pub fn init(config: config::Config) -> Peripherals {
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`."
To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`."
);
}
}

View file

@ -137,6 +137,11 @@ impl Task {
Self(ptr)
}
/// Triggers this task.
pub fn trigger(&mut self) {
unsafe { self.0.as_ptr().write_volatile(1) };
}
pub(crate) fn from_reg<T>(reg: &T) -> Self {
Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) })
}
@ -173,6 +178,16 @@ impl Event {
Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) })
}
/// Describes whether this Event is currently in a triggered state.
pub fn is_triggered(&self) -> bool {
unsafe { self.0.as_ptr().read_volatile() == 1 }
}
/// Clear the current register's triggered state, reverting it to 0.
pub fn clear(&mut self) {
unsafe { self.0.as_ptr().write_volatile(0) };
}
/// Address of publish register for this event.
#[cfg(feature = "_dppi")]
pub fn publish_reg(&self) -> *mut u32 {

View file

@ -76,7 +76,7 @@ embedded-storage = { version = "0.3" }
rand_core = "0.6.4"
fixed = "1.23.1"
rp-pac = { version = "5" }
rp-pac = { version = "6" }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}

View file

@ -112,8 +112,14 @@ impl<'d> Adc<'d> {
r.result().read().result().into()
}
pub fn blocking_read<PIN: Channel<Adc<'d>, ID = u8>>(&mut self, _pin: &mut PIN) -> u16 {
pub fn blocking_read<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 {
let r = Self::regs();
pin.pad_ctrl().modify(|w| {
w.set_ie(true);
let (pu, pd) = (false, false);
w.set_pue(pu);
w.set_pde(pd);
});
r.cs().modify(|w| {
w.set_ainsel(PIN::channel());
w.set_start_once(true)
@ -166,7 +172,7 @@ impl_pin!(PIN_29, 3);
impl<WORD, PIN> OneShot<Adc<'static>, WORD, PIN> for Adc<'static>
where
WORD: From<u16>,
PIN: Channel<Adc<'static>, ID = u8>,
PIN: Channel<Adc<'static>, ID = u8> + Pin,
{
type Error = ();
fn read(&mut self, pin: &mut PIN) -> nb::Result<WORD, Self::Error> {

View file

@ -46,13 +46,13 @@ static CLOCKS: Clocks = Clocks {
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PeriClkSrc {
Sys = ClkPeriCtrlAuxsrc::CLK_SYS.0,
PllSys = ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS.0,
PllUsb = ClkPeriCtrlAuxsrc::CLKSRC_PLL_USB.0,
Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH.0,
Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC.0,
// Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0.0,
// Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1.0,
Sys = ClkPeriCtrlAuxsrc::CLK_SYS as _,
PllSys = ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS as _,
PllUsb = ClkPeriCtrlAuxsrc::CLKSRC_PLL_USB as _,
Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _,
Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _,
// Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
// Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
}
#[non_exhaustive]
@ -251,12 +251,12 @@ pub struct SysClkConfig {
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UsbClkSrc {
PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB.0,
PllSys = ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS.0,
Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH.0,
Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC.0,
// Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0.0,
// Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1.0,
PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB as _,
PllSys = ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS as _,
Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _,
Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _,
// Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
// Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
}
pub struct UsbClkConfig {
@ -269,12 +269,12 @@ pub struct UsbClkConfig {
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AdcClkSrc {
PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB.0,
PllSys = ClkAdcCtrlAuxsrc::CLKSRC_PLL_SYS.0,
Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH.0,
Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC.0,
// Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0.0,
// Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1.0,
PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB as _,
PllSys = ClkAdcCtrlAuxsrc::CLKSRC_PLL_SYS as _,
Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _,
Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _,
// Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
// Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
}
pub struct AdcClkConfig {
@ -287,12 +287,12 @@ pub struct AdcClkConfig {
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RtcClkSrc {
PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB.0,
PllSys = ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS.0,
Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH.0,
Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC.0,
// Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0.0,
// Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1.0,
PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _,
PllSys = ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS as _,
Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _,
Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _,
// Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
// Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
}
pub struct RtcClkConfig {
@ -396,7 +396,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
w.set_src(ref_src);
w.set_auxsrc(ref_aux);
});
while c.clk_ref_selected().read() != 1 << ref_src.0 {}
while c.clk_ref_selected().read() != 1 << ref_src as u32 {}
c.clk_ref_div().write(|w| {
w.set_int(config.ref_clk.div);
});
@ -425,13 +425,13 @@ pub(crate) unsafe fn init(config: ClockConfig) {
CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed);
if sys_src != ClkSysCtrlSrc::CLK_REF {
c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF));
while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF.0 {}
while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF as u32 {}
}
c.clk_sys_ctrl().write(|w| {
w.set_auxsrc(sys_aux);
w.set_src(sys_src);
});
while c.clk_sys_selected().read() != 1 << sys_src.0 {}
while c.clk_sys_selected().read() != 1 << sys_src as u32 {}
c.clk_sys_div().write(|w| {
w.set_int(config.sys_clk.div_int);
w.set_frac(config.sys_clk.div_frac);
@ -442,7 +442,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
if let Some(src) = config.peri_clk_src {
c.clk_peri_ctrl().write(|w| {
w.set_enable(true);
w.set_auxsrc(ClkPeriCtrlAuxsrc(src as _));
w.set_auxsrc(ClkPeriCtrlAuxsrc::from_bits(src as _));
});
let peri_freq = match src {
PeriClkSrc::Sys => clk_sys_freq,
@ -468,7 +468,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
c.clk_usb_ctrl().write(|w| {
w.set_phase(conf.phase);
w.set_enable(true);
w.set_auxsrc(ClkUsbCtrlAuxsrc(conf.src as _));
w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _));
});
let usb_freq = match conf.src {
UsbClkSrc::PllUsb => pll_usb_freq,
@ -491,7 +491,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
c.clk_adc_ctrl().write(|w| {
w.set_phase(conf.phase);
w.set_enable(true);
w.set_auxsrc(ClkAdcCtrlAuxsrc(conf.src as _));
w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _));
});
let adc_in_freq = match conf.src {
AdcClkSrc::PllUsb => pll_usb_freq,
@ -517,7 +517,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
c.clk_rtc_ctrl().write(|w| {
w.set_phase(conf.phase);
w.set_enable(true);
w.set_auxsrc(ClkRtcCtrlAuxsrc(conf.src as _));
w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _));
});
let rtc_in_freq = match conf.src {
RtcClkSrc::PllUsb => pll_usb_freq,
@ -718,7 +718,7 @@ impl<'d, T: Pin> Drop for Gpin<'d, T> {
self.gpin
.io()
.ctrl()
.write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL.0));
.write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _));
}
}
@ -743,17 +743,17 @@ impl_gpoutpin!(PIN_25, 3);
#[repr(u8)]
pub enum GpoutSrc {
PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS.0,
// Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0.0,
// Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1.0,
PllUsb = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB.0,
Rosc = ClkGpoutCtrlAuxsrc::ROSC_CLKSRC.0,
Xosc = ClkGpoutCtrlAuxsrc::XOSC_CLKSRC.0,
Sys = ClkGpoutCtrlAuxsrc::CLK_SYS.0,
Usb = ClkGpoutCtrlAuxsrc::CLK_USB.0,
Adc = ClkGpoutCtrlAuxsrc::CLK_ADC.0,
Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC.0,
Ref = ClkGpoutCtrlAuxsrc::CLK_REF.0,
PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _,
// Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
// Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
PllUsb = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB as _,
Rosc = ClkGpoutCtrlAuxsrc::ROSC_CLKSRC as _,
Xosc = ClkGpoutCtrlAuxsrc::XOSC_CLKSRC as _,
Sys = ClkGpoutCtrlAuxsrc::CLK_SYS as _,
Usb = ClkGpoutCtrlAuxsrc::CLK_USB as _,
Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _,
Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _,
Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _,
}
pub struct Gpout<'d, T: GpoutPin> {
@ -780,7 +780,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
pub fn set_src(&self, src: GpoutSrc) {
let c = pac::CLOCKS;
c.clk_gpout_ctrl(self.gpout.number()).modify(|w| {
w.set_auxsrc(ClkGpoutCtrlAuxsrc(src as _));
w.set_auxsrc(ClkGpoutCtrlAuxsrc::from_bits(src as _));
});
}
@ -831,7 +831,7 @@ impl<'d, T: GpoutPin> Drop for Gpout<'d, T> {
self.gpout
.io()
.ctrl()
.write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL.0));
.write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _));
}
}

View file

@ -452,7 +452,7 @@ impl<'d, T: Pin> Flex<'d, T> {
});
pin.io().ctrl().write(|w| {
w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0.0);
w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _);
});
Self { pin }
@ -618,7 +618,7 @@ impl<'d, T: Pin> Drop for Flex<'d, T> {
fn drop(&mut self) {
self.pin.pad_ctrl().write(|_| {});
self.pin.io().ctrl().write(|w| {
w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL.0);
w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _);
});
}
}

View file

@ -834,7 +834,7 @@ impl<'d, PIO: Instance> Common<'d, PIO> {
/// of [`Pio`] do not keep pin registrations alive.**
pub fn make_pio_pin(&mut self, pin: impl Peripheral<P = impl PioPin + 'd> + 'd) -> Pin<'d, PIO> {
into_ref!(pin);
pin.io().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL.0));
pin.io().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _));
// we can be relaxed about this because we're &mut here and nothing is cached
PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed);
Pin {
@ -998,7 +998,7 @@ fn on_pio_drop<PIO: Instance>() {
let state = PIO::state();
if state.users.fetch_sub(1, Ordering::AcqRel) == 1 {
let used_pins = state.used_pins.load(Ordering::Relaxed);
let null = Gpio0ctrlFuncsel::NULL.0;
let null = Gpio0ctrlFuncsel::NULL as _;
// we only have 30 pins. don't test the other two since gpio() asserts.
for i in 0..30 {
if used_pins & (1 << i) != 0 {

View file

@ -353,6 +353,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
poll_fn(move |cx| {
BUS_WAKER.register(cx.waker());
// TODO: implement VBUS detection.
if !self.inited {
self.inited = true;
return Poll::Ready(Event::PowerDetected);

View file

@ -21,13 +21,16 @@ embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }
defmt = { version = "0.3", optional = true }
cortex-m = "0.7.6"
heapless = "0.7.16"
aligned = "0.4.1"
bit_field = "0.10.2"
stm32-device-signature = { version = "0.3.3", features = ["stm32wb5x"] }
stm32wb-hci = { version = "0.1.2", features = ["version-5-0"], optional = true }
[features]
defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt"]
ble = []
ble = ["dep:stm32wb-hci"]
mac = []
stm32wb10cc = [ "embassy-stm32/stm32wb10cc" ]

View file

@ -1,4 +1,5 @@
use std::env;
use std::path::PathBuf;
use std::{env, fs};
fn main() {
match env::vars()
@ -10,6 +11,16 @@ fn main() {
Err(GetOneError::None) => panic!("No stm32xx Cargo feature enabled"),
Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"),
}
let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
// ========
// stm32wb tl_mbox link sections
let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string();
fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap();
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rerun-if-changed=tl_mbox.x.in");
}
enum GetOneError {

View file

@ -52,7 +52,7 @@ impl CmdPacket {
p_cmd_serial,
CmdSerialStub {
ty: packet_type as u8,
cmd_code: cmd_code,
cmd_code,
payload_len: payload.len() as u8,
},
);

View file

@ -87,3 +87,7 @@ pub const fn divc(x: usize, y: usize) -> usize {
pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE;
#[allow(dead_code)]
pub const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE;
pub const TL_BLEEVT_CC_OPCODE: u8 = 0x0E;
pub const TL_BLEEVT_CS_OPCODE: u8 = 0x0F;
pub const TL_BLEEVT_VS_OPCODE: u8 = 0xFF;

View file

@ -1,6 +1,8 @@
use core::marker::PhantomData;
use core::{ptr, slice};
use super::PacketHeader;
use crate::consts::TL_EVT_HEADER_SIZE;
/**
* The payload of `Evt` for a command status event
@ -92,17 +94,22 @@ impl EvtPacket {
}
}
pub trait MemoryManager {
unsafe fn drop_event_packet(evt: *mut EvtPacket);
}
/// smart pointer to the [`EvtPacket`] that will dispose of [`EvtPacket`] buffer automatically
/// on [`Drop`]
#[derive(Debug)]
pub struct EvtBox {
pub struct EvtBox<T: MemoryManager> {
ptr: *mut EvtPacket,
mm: PhantomData<T>,
}
unsafe impl Send for EvtBox {}
impl EvtBox {
unsafe impl<T: MemoryManager> Send for EvtBox<T> {}
impl<T: MemoryManager> EvtBox<T> {
pub(super) fn new(ptr: *mut EvtPacket) -> Self {
Self { ptr }
Self { ptr, mm: PhantomData }
}
/// Returns information about the event
@ -124,22 +131,21 @@ impl EvtBox {
slice::from_raw_parts(p_payload, payload_len as usize)
}
}
}
impl Drop for EvtBox {
fn drop(&mut self) {
#[cfg(feature = "ble")]
pub fn serial<'a>(&'a self) -> &'a [u8] {
unsafe {
use crate::mm;
let evt_serial: *const EvtSerial = &(*self.ptr).evt_serial;
let evt_serial_buf: *const u8 = evt_serial.cast();
mm::MemoryManager::drop_event_packet(self.ptr)
};
let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE;
#[cfg(feature = "mac")]
unsafe {
use crate::mac;
mac::Mac::drop_event_packet(self.ptr)
slice::from_raw_parts(evt_serial_buf, len)
}
}
}
impl<T: MemoryManager> Drop for EvtBox<T> {
fn drop(&mut self) {
unsafe { T::drop_event_packet(self.ptr) };
}
}

View file

@ -0,0 +1,112 @@
use core::ptr;
use crate::cmd::CmdPacket;
use crate::consts::{TlPacketType, TL_EVT_HEADER_SIZE};
use crate::evt::{CcEvt, EvtPacket, EvtSerial};
use crate::tables::{DeviceInfoTable, RssInfoTable, SafeBootInfoTable, WirelessFwInfoTable, TL_DEVICE_INFO_TABLE};
const TL_BLEEVT_CC_OPCODE: u8 = 0x0e;
const LHCI_OPCODE_C1_DEVICE_INF: u16 = 0xfd62;
const PACKAGE_DATA_PTR: *const u8 = 0x1FFF_7500 as _;
const UID64_PTR: *const u32 = 0x1FFF_7580 as _;
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct LhciC1DeviceInformationCcrp {
pub status: u8,
pub rev_id: u16,
pub dev_code_id: u16,
pub package_type: u8,
pub device_type_id: u8,
pub st_company_id: u32,
pub uid64: u32,
pub uid96_0: u32,
pub uid96_1: u32,
pub uid96_2: u32,
pub safe_boot_info_table: SafeBootInfoTable,
pub rss_info_table: RssInfoTable,
pub wireless_fw_info_table: WirelessFwInfoTable,
pub app_fw_inf: u32,
}
impl Default for LhciC1DeviceInformationCcrp {
fn default() -> Self {
let DeviceInfoTable {
safe_boot_info_table,
rss_info_table,
wireless_fw_info_table,
} = unsafe { ptr::read_volatile(TL_DEVICE_INFO_TABLE.as_ptr()) };
let device_id = stm32_device_signature::device_id();
let uid96_0 = (device_id[3] as u32) << 24
| (device_id[2] as u32) << 16
| (device_id[1] as u32) << 8
| device_id[0] as u32;
let uid96_1 = (device_id[7] as u32) << 24
| (device_id[6] as u32) << 16
| (device_id[5] as u32) << 8
| device_id[4] as u32;
let uid96_2 = (device_id[11] as u32) << 24
| (device_id[10] as u32) << 16
| (device_id[9] as u32) << 8
| device_id[8] as u32;
let package_type = unsafe { *PACKAGE_DATA_PTR };
let uid64 = unsafe { *UID64_PTR };
let st_company_id = unsafe { *UID64_PTR.offset(1) } >> 8 & 0x00FF_FFFF;
let device_type_id = (unsafe { *UID64_PTR.offset(1) } & 0x000000FF) as u8;
LhciC1DeviceInformationCcrp {
status: 0,
rev_id: 0,
dev_code_id: 0,
package_type,
device_type_id,
st_company_id,
uid64,
uid96_0,
uid96_1,
uid96_2,
safe_boot_info_table,
rss_info_table,
wireless_fw_info_table,
app_fw_inf: (1 << 8), // 0.0.1
}
}
}
impl LhciC1DeviceInformationCcrp {
pub fn new() -> Self {
Self::default()
}
pub fn write(&self, cmd_packet: &mut CmdPacket) {
let self_size = core::mem::size_of::<LhciC1DeviceInformationCcrp>();
unsafe {
let cmd_packet_ptr: *mut CmdPacket = cmd_packet;
let evet_packet_ptr: *mut EvtPacket = cmd_packet_ptr.cast();
let evt_serial: *mut EvtSerial = &mut (*evet_packet_ptr).evt_serial;
let evt_payload = (*evt_serial).evt.payload.as_mut_ptr();
let evt_cc: *mut CcEvt = evt_payload.cast();
let evt_cc_payload_buf = (*evt_cc).payload.as_mut_ptr();
(*evt_serial).kind = TlPacketType::LocRsp as u8;
(*evt_serial).evt.evt_code = TL_BLEEVT_CC_OPCODE;
(*evt_serial).evt.payload_len = TL_EVT_HEADER_SIZE as u8 + self_size as u8;
(*evt_cc).cmd_code = LHCI_OPCODE_C1_DEVICE_INF;
(*evt_cc).num_cmd = 1;
let self_ptr: *const LhciC1DeviceInformationCcrp = self;
let self_buf = self_ptr.cast();
ptr::copy(self_buf, evt_cc_payload_buf, self_size);
}
}
}

View file

@ -1,4 +1,5 @@
#![no_std]
#![cfg_attr(feature = "ble", feature(async_fn_in_trait))]
// This must go FIRST so that all the other modules see its macros.
pub mod fmt;
@ -10,25 +11,24 @@ use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_stm32::interrupt;
use embassy_stm32::ipcc::{Config, Ipcc, ReceiveInterruptHandler, TransmitInterruptHandler};
use embassy_stm32::peripherals::IPCC;
use mm::MemoryManager;
use sys::Sys;
use sub::mm::MemoryManager;
use sub::sys::Sys;
use tables::*;
use unsafe_linked_list::LinkedListNode;
#[cfg(feature = "ble")]
pub mod ble;
pub mod channels;
pub mod cmd;
pub mod consts;
pub mod evt;
#[cfg(feature = "mac")]
pub mod mac;
pub mod mm;
pub mod lhci;
pub mod shci;
pub mod sys;
pub mod sub;
pub mod tables;
pub mod unsafe_linked_list;
#[cfg(feature = "ble")]
pub use crate::sub::ble::hci;
type PacketHeader = LinkedListNode;
pub struct TlMbox<'d> {
@ -37,9 +37,9 @@ pub struct TlMbox<'d> {
pub sys_subsystem: Sys,
pub mm_subsystem: MemoryManager,
#[cfg(feature = "ble")]
pub ble_subsystem: ble::Ble,
pub ble_subsystem: sub::ble::Ble,
#[cfg(feature = "mac")]
pub mac_subsystem: mac::Mac,
pub mac_subsystem: sub::mac::Mac,
}
impl<'d> TlMbox<'d> {
@ -126,12 +126,12 @@ impl<'d> TlMbox<'d> {
Self {
_ipcc: ipcc,
sys_subsystem: sys::Sys::new(),
sys_subsystem: sub::sys::Sys::new(),
#[cfg(feature = "ble")]
ble_subsystem: ble::Ble::new(),
ble_subsystem: sub::ble::Ble::new(),
#[cfg(feature = "mac")]
mac_subsystem: mac::Mac::new(),
mm_subsystem: mm::MemoryManager::new(),
mac_subsystem: sub::mac::Mac::new(),
mm_subsystem: sub::mm::MemoryManager::new(),
}
}
}

View file

@ -1,13 +1,16 @@
use core::marker::PhantomData;
use core::ptr;
use embassy_stm32::ipcc::Ipcc;
use hci::Opcode;
use crate::channels;
use crate::cmd::CmdPacket;
use crate::consts::TlPacketType;
use crate::evt::EvtBox;
use crate::consts::{TlPacketType, TL_BLEEVT_CC_OPCODE, TL_BLEEVT_CS_OPCODE};
use crate::evt::{EvtBox, EvtPacket, EvtStub};
use crate::sub::mm;
use crate::tables::{BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE};
use crate::unsafe_linked_list::LinkedListNode;
use crate::{channels, evt};
pub struct Ble {
phantom: PhantomData<Ble>,
@ -29,7 +32,7 @@ impl Ble {
Self { phantom: PhantomData }
}
/// `HW_IPCC_BLE_EvtNot`
pub async fn read(&self) -> EvtBox {
pub async fn tl_read(&self) -> EvtBox<Self> {
Ipcc::receive(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, || unsafe {
if let Some(node_ptr) = LinkedListNode::remove_head(EVT_QUEUE.as_mut_ptr()) {
Some(EvtBox::new(node_ptr.cast()))
@ -41,7 +44,7 @@ impl Ble {
}
/// `TL_BLE_SendCmd`
pub async fn write(&self, opcode: u16, payload: &[u8]) {
pub async fn tl_write(&self, opcode: u16, payload: &[u8]) {
Ipcc::send(channels::cpu1::IPCC_BLE_CMD_CHANNEL, || unsafe {
CmdPacket::write_into(BLE_CMD_BUFFER.as_mut_ptr(), TlPacketType::BleCmd, opcode, payload);
})
@ -61,3 +64,33 @@ impl Ble {
.await;
}
}
impl evt::MemoryManager for Ble {
/// SAFETY: passing a pointer to something other than a managed event packet is UB
unsafe fn drop_event_packet(evt: *mut EvtPacket) {
let stub = unsafe {
let p_evt_stub = &(*evt).evt_serial as *const _ as *const EvtStub;
ptr::read_volatile(p_evt_stub)
};
if !(stub.evt_code == TL_BLEEVT_CS_OPCODE || stub.evt_code == TL_BLEEVT_CC_OPCODE) {
mm::MemoryManager::drop_event_packet(evt);
}
}
}
pub extern crate stm32wb_hci as hci;
impl hci::Controller for Ble {
async fn controller_write(&mut self, opcode: Opcode, payload: &[u8]) {
self.tl_write(opcode.0, payload).await;
}
async fn controller_read_into(&self, buf: &mut [u8]) {
let evt_box = self.tl_read().await;
let evt_serial = evt_box.serial();
buf[..evt_serial.len()].copy_from_slice(evt_serial);
}
}

View file

@ -8,13 +8,13 @@ use embassy_futures::poll_once;
use embassy_stm32::ipcc::Ipcc;
use embassy_sync::waitqueue::AtomicWaker;
use crate::channels;
use crate::cmd::CmdPacket;
use crate::consts::TlPacketType;
use crate::evt::{EvtBox, EvtPacket};
use crate::tables::{
Mac802_15_4Table, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, TL_MAC_802_15_4_TABLE,
};
use crate::{channels, evt};
static MAC_WAKER: AtomicWaker = AtomicWaker::new();
static MAC_EVT_OUT: AtomicBool = AtomicBool::new(false);
@ -36,31 +36,10 @@ impl Mac {
Self { phantom: PhantomData }
}
/// SAFETY: passing a pointer to something other than a managed event packet is UB
pub(crate) unsafe fn drop_event_packet(_: *mut EvtPacket) {
// Write the ack
CmdPacket::write_into(
MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _,
TlPacketType::OtAck,
0,
&[],
);
// Clear the rx flag
let _ = poll_once(Ipcc::receive::<bool>(
channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL,
|| None,
));
// Allow a new read call
MAC_EVT_OUT.store(false, Ordering::SeqCst);
MAC_WAKER.wake();
}
/// `HW_IPCC_MAC_802_15_4_EvtNot`
///
/// This function will stall if the previous `EvtBox` has not been dropped
pub async fn read(&self) -> EvtBox {
pub async fn read(&self) -> EvtBox<Self> {
// Wait for the last event box to be dropped
poll_fn(|cx| {
MAC_WAKER.register(cx.waker());
@ -109,3 +88,26 @@ impl Mac {
.await;
}
}
impl evt::MemoryManager for Mac {
/// SAFETY: passing a pointer to something other than a managed event packet is UB
unsafe fn drop_event_packet(_: *mut EvtPacket) {
// Write the ack
CmdPacket::write_into(
MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _,
TlPacketType::OtAck,
0,
&[],
);
// Clear the rx flag
let _ = poll_once(Ipcc::receive::<bool>(
channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL,
|| None,
));
// Allow a new read call
MAC_EVT_OUT.store(false, Ordering::SeqCst);
MAC_WAKER.wake();
}
}

View file

@ -8,13 +8,13 @@ use cortex_m::interrupt;
use embassy_stm32::ipcc::Ipcc;
use embassy_sync::waitqueue::AtomicWaker;
use crate::channels;
use crate::consts::POOL_SIZE;
use crate::evt::EvtPacket;
use crate::tables::{
MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUF_QUEUE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE,
};
use crate::unsafe_linked_list::LinkedListNode;
use crate::{channels, evt};
static MM_WAKER: AtomicWaker = AtomicWaker::new();
static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
@ -43,16 +43,6 @@ impl MemoryManager {
Self { phantom: PhantomData }
}
#[allow(dead_code)]
/// SAFETY: passing a pointer to something other than a managed event packet is UB
pub(crate) unsafe fn drop_event_packet(evt: *mut EvtPacket) {
interrupt::free(|_| unsafe {
LinkedListNode::insert_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), evt as *mut _);
});
MM_WAKER.wake();
}
pub async fn run_queue(&self) {
loop {
poll_fn(|cx| unsafe {
@ -77,3 +67,14 @@ impl MemoryManager {
}
}
}
impl evt::MemoryManager for MemoryManager {
/// SAFETY: passing a pointer to something other than a managed event packet is UB
unsafe fn drop_event_packet(evt: *mut EvtPacket) {
interrupt::free(|_| unsafe {
LinkedListNode::insert_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), evt as *mut _);
});
MM_WAKER.wake();
}
}

View file

@ -0,0 +1,6 @@
#[cfg(feature = "ble")]
pub mod ble;
#[cfg(feature = "mac")]
pub mod mac;
pub mod mm;
pub mod sys;

View file

@ -6,6 +6,7 @@ use crate::consts::TlPacketType;
use crate::evt::{CcEvt, EvtBox, EvtPacket};
#[allow(unused_imports)]
use crate::shci::{SchiCommandStatus, ShciBleInitCmdParam, ShciOpcode};
use crate::sub::mm;
use crate::tables::{SysTable, WirelessFwInfoTable};
use crate::unsafe_linked_list::LinkedListNode;
use crate::{channels, Ipcc, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_DEVICE_INFO_TABLE, TL_SYS_TABLE};
@ -73,7 +74,7 @@ impl Sys {
}
/// `HW_IPCC_SYS_EvtNot`
pub async fn read(&self) -> EvtBox {
pub async fn read(&self) -> EvtBox<mm::MemoryManager> {
Ipcc::receive(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, || unsafe {
if let Some(node_ptr) = LinkedListNode::remove_head(SYSTEM_EVT_QUEUE.as_mut_ptr()) {
Some(EvtBox::new(node_ptr.cast()))

View file

@ -1,5 +1,6 @@
use core::mem::MaybeUninit;
use aligned::{Aligned, A4};
use bit_field::BitField;
use crate::cmd::{AclDataPacket, CmdPacket};
@ -164,9 +165,6 @@ pub struct Mac802_15_4Table {
pub evt_queue: *const u8,
}
#[repr(C, align(4))]
pub struct AlignedData<const L: usize>([u8; L]);
/// Reference table. Contains pointers to all other tables.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
@ -222,10 +220,9 @@ pub static mut FREE_BUF_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit
#[link_section = "MB_MEM1"]
pub static mut TRACES_EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
const CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE;
#[link_section = "MB_MEM2"]
pub static mut CS_BUFFER: MaybeUninit<AlignedData<CS_BUFFER_SIZE>> = MaybeUninit::uninit();
pub static mut CS_BUFFER: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE]>> =
MaybeUninit::uninit();
#[link_section = "MB_MEM2"]
pub static mut EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
@ -238,35 +235,30 @@ pub static mut SYSTEM_EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::unin
#[link_section = "MB_MEM2"]
pub static mut MAC_802_15_4_CMD_BUFFER: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
#[cfg(feature = "mac")]
const MAC_802_15_4_NOTIF_RSP_EVT_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255;
#[cfg(feature = "mac")]
#[link_section = "MB_MEM2"]
pub static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit<AlignedData<MAC_802_15_4_NOTIF_RSP_EVT_BUFFER_SIZE>> =
MaybeUninit::uninit();
pub static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit<
Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>,
> = MaybeUninit::uninit();
#[link_section = "MB_MEM2"]
pub static mut EVT_POOL: MaybeUninit<[u8; POOL_SIZE]> = MaybeUninit::uninit();
pub static mut EVT_POOL: MaybeUninit<Aligned<A4, [u8; POOL_SIZE]>> = MaybeUninit::uninit();
#[link_section = "MB_MEM2"]
pub static mut SYS_CMD_BUF: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
const SYS_SPARE_EVT_BUF_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255;
#[link_section = "MB_MEM2"]
pub static mut SYS_SPARE_EVT_BUF: MaybeUninit<AlignedData<SYS_SPARE_EVT_BUF_SIZE>> = MaybeUninit::uninit();
pub static mut SYS_SPARE_EVT_BUF: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>> =
MaybeUninit::uninit();
#[link_section = "MB_MEM1"]
pub static mut BLE_CMD_BUFFER: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
const BLE_SPARE_EVT_BUF_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255;
#[link_section = "MB_MEM2"]
pub static mut BLE_SPARE_EVT_BUF: MaybeUninit<AlignedData<BLE_SPARE_EVT_BUF_SIZE>> = MaybeUninit::uninit();
const HCI_ACL_DATA_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + 5 + 251;
pub static mut BLE_SPARE_EVT_BUF: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>> =
MaybeUninit::uninit();
#[link_section = "MB_MEM2"]
// fuck these "magic" numbers from ST ---v---v
pub static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; HCI_ACL_DATA_BUFFER_SIZE]> = MaybeUninit::uninit();
pub static mut HCI_ACL_DATA_BUFFER: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + 5 + 251]>> =
MaybeUninit::uninit();

View file

@ -57,7 +57,7 @@ sdio-host = "0.5.0"
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
critical-section = "1.1"
atomic-polyfill = "1.0.1"
stm32-metapac = "10"
stm32-metapac = "12"
vcell = "0.1.3"
bxcan = "0.7.0"
nb = "1.0.0"
@ -74,7 +74,7 @@ critical-section = { version = "1.1", features = ["std"] }
[build-dependencies]
proc-macro2 = "1.0.36"
quote = "1.0.15"
stm32-metapac = { version = "10", default-features = false, features = ["metadata"]}
stm32-metapac = { version = "12", default-features = false, features = ["metadata"]}
[features]
default = ["rt"]

View file

@ -699,6 +699,8 @@ fn main() {
// SDMMCv1 uses the same channel for both directions, so just implement for RX
(("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)),
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
(("dac", "CH1"), quote!(crate::dac::DmaCh1)),
(("dac", "CH2"), quote!(crate::dac::DmaCh2)),
]
.into();
@ -911,16 +913,6 @@ fn main() {
println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
}
// ========
// stm32wb tl_mbox link sections
if chip_name.starts_with("stm32wb") {
let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string();
fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap();
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rerun-if-changed=tl_mbox.x.in");
}
// =======
// Features for targeting groups of chips

View file

@ -211,10 +211,8 @@ impl<'d, T: Instance> Adc<'d, T> {
#[cfg(not(stm32g0))]
fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
let sample_time = sample_time.into();
if ch <= 9 {
T::regs().smpr1().modify(|reg| reg.set_smp(ch as _, sample_time));
} else {
T::regs().smpr2().modify(|reg| reg.set_smp((ch - 10) as _, sample_time));
}
T::regs()
.smpr(ch as usize / 10)
.modify(|reg| reg.set_smp(ch as usize % 10, sample_time));
}
}

View file

@ -116,10 +116,10 @@ impl<'d, T: Instance> Can<'d, T> {
T::regs().ier().write(|w| {
// TODO: fix metapac
w.set_errie(Errie(1));
w.set_fmpie(0, Fmpie(1));
w.set_fmpie(1, Fmpie(1));
w.set_tmeie(Tmeie(1));
w.set_errie(Errie::from_bits(1));
w.set_fmpie(0, Fmpie::from_bits(1));
w.set_fmpie(1, Fmpie::from_bits(1));
w.set_tmeie(Tmeie::from_bits(1));
});
T::regs().mcr().write(|w| {

View file

@ -1,260 +0,0 @@
#![macro_use]
use embassy_hal_common::{into_ref, PeripheralRef};
use crate::pac::dac;
use crate::rcc::RccPeripheral;
use crate::{peripherals, Peripheral};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
UnconfiguredChannel,
InvalidValue,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Channel {
Ch1,
Ch2,
}
impl Channel {
fn index(&self) -> usize {
match self {
Channel::Ch1 => 0,
Channel::Ch2 => 1,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Ch1Trigger {
Tim6,
Tim3,
Tim7,
Tim15,
Tim2,
Exti9,
Software,
}
impl Ch1Trigger {
fn tsel(&self) -> dac::vals::Tsel1 {
match self {
Ch1Trigger::Tim6 => dac::vals::Tsel1::TIM6_TRGO,
Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM3_TRGO,
Ch1Trigger::Tim7 => dac::vals::Tsel1::TIM7_TRGO,
Ch1Trigger::Tim15 => dac::vals::Tsel1::TIM15_TRGO,
Ch1Trigger::Tim2 => dac::vals::Tsel1::TIM2_TRGO,
Ch1Trigger::Exti9 => dac::vals::Tsel1::EXTI9,
Ch1Trigger::Software => dac::vals::Tsel1::SOFTWARE,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Ch2Trigger {
Tim6,
Tim8,
Tim7,
Tim5,
Tim2,
Tim4,
Exti9,
Software,
}
impl Ch2Trigger {
fn tsel(&self) -> dac::vals::Tsel2 {
match self {
Ch2Trigger::Tim6 => dac::vals::Tsel2::TIM6_TRGO,
Ch2Trigger::Tim8 => dac::vals::Tsel2::TIM8_TRGO,
Ch2Trigger::Tim7 => dac::vals::Tsel2::TIM7_TRGO,
Ch2Trigger::Tim5 => dac::vals::Tsel2::TIM5_TRGO,
Ch2Trigger::Tim2 => dac::vals::Tsel2::TIM2_TRGO,
Ch2Trigger::Tim4 => dac::vals::Tsel2::TIM4_TRGO,
Ch2Trigger::Exti9 => dac::vals::Tsel2::EXTI9,
Ch2Trigger::Software => dac::vals::Tsel2::SOFTWARE,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Alignment {
Left,
Right,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Value {
Bit8(u8),
Bit12(u16, Alignment),
}
pub struct Dac<'d, T: Instance> {
channels: u8,
_peri: PeripheralRef<'d, T>,
}
impl<'d, T: Instance> Dac<'d, T> {
pub fn new_1ch(peri: impl Peripheral<P = T> + 'd, _ch1: impl Peripheral<P = impl DacPin<T, 1>> + 'd) -> Self {
into_ref!(peri);
Self::new_inner(peri, 1)
}
pub fn new_2ch(
peri: impl Peripheral<P = T> + 'd,
_ch1: impl Peripheral<P = impl DacPin<T, 1>> + 'd,
_ch2: impl Peripheral<P = impl DacPin<T, 2>> + 'd,
) -> Self {
into_ref!(peri);
Self::new_inner(peri, 2)
}
fn new_inner(peri: PeripheralRef<'d, T>, channels: u8) -> Self {
T::enable();
T::reset();
T::regs().cr().modify(|reg| {
for ch in 0..channels {
reg.set_en(ch as usize, true);
}
});
Self { channels, _peri: peri }
}
/// Check the channel is configured
fn check_channel_exists(&self, ch: Channel) -> Result<(), Error> {
if ch == Channel::Ch2 && self.channels < 2 {
Err(Error::UnconfiguredChannel)
} else {
Ok(())
}
}
fn set_channel_enable(&mut self, ch: Channel, on: bool) -> Result<(), Error> {
self.check_channel_exists(ch)?;
T::regs().cr().modify(|reg| {
reg.set_en(ch.index(), on);
});
Ok(())
}
pub fn enable_channel(&mut self, ch: Channel) -> Result<(), Error> {
self.set_channel_enable(ch, true)
}
pub fn disable_channel(&mut self, ch: Channel) -> Result<(), Error> {
self.set_channel_enable(ch, false)
}
pub fn select_trigger_ch1(&mut self, trigger: Ch1Trigger) -> Result<(), Error> {
self.check_channel_exists(Channel::Ch1)?;
unwrap!(self.disable_channel(Channel::Ch1));
T::regs().cr().modify(|reg| {
reg.set_tsel1(trigger.tsel());
});
Ok(())
}
pub fn select_trigger_ch2(&mut self, trigger: Ch2Trigger) -> Result<(), Error> {
self.check_channel_exists(Channel::Ch2)?;
unwrap!(self.disable_channel(Channel::Ch2));
T::regs().cr().modify(|reg| {
reg.set_tsel2(trigger.tsel());
});
Ok(())
}
pub fn trigger(&mut self, ch: Channel) -> Result<(), Error> {
self.check_channel_exists(ch)?;
T::regs().swtrigr().write(|reg| {
reg.set_swtrig(ch.index(), true);
});
Ok(())
}
pub fn trigger_all(&mut self) {
T::regs().swtrigr().write(|reg| {
reg.set_swtrig(Channel::Ch1.index(), true);
reg.set_swtrig(Channel::Ch2.index(), true);
});
}
pub fn set(&mut self, ch: Channel, value: Value) -> Result<(), Error> {
self.check_channel_exists(ch)?;
match value {
Value::Bit8(v) => T::regs().dhr8r(ch.index()).write(|reg| reg.set_dhr(v)),
Value::Bit12(v, Alignment::Left) => T::regs().dhr12l(ch.index()).write(|reg| reg.set_dhr(v)),
Value::Bit12(v, Alignment::Right) => T::regs().dhr12r(ch.index()).write(|reg| reg.set_dhr(v)),
}
Ok(())
}
}
pub(crate) mod sealed {
pub trait Instance {
fn regs() -> &'static crate::pac::dac::Dac;
}
}
pub trait Instance: sealed::Instance + RccPeripheral + 'static {}
pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {}
foreach_peripheral!(
(dac, $inst:ident) => {
// H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented
#[cfg(rcc_h7)]
impl crate::rcc::sealed::RccPeripheral for peripherals::$inst {
fn frequency() -> crate::time::Hertz {
critical_section::with(|_| unsafe {
crate::rcc::get_freqs().apb1
})
}
fn reset() {
critical_section::with(|_| {
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true));
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false));
})
}
fn enable() {
critical_section::with(|_| {
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true));
})
}
fn disable() {
critical_section::with(|_| {
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false));
})
}
}
#[cfg(rcc_h7)]
impl crate::rcc::RccPeripheral for peripherals::$inst {}
impl crate::dac::sealed::Instance for peripherals::$inst {
fn regs() -> &'static crate::pac::dac::Dac {
&crate::pac::$inst
}
}
impl crate::dac::Instance for peripherals::$inst {}
};
);
macro_rules! impl_dac_pin {
($inst:ident, $pin:ident, $ch:expr) => {
impl crate::dac::DacPin<peripherals::$inst, $ch> for crate::peripherals::$pin {}
};
}

View file

@ -0,0 +1,570 @@
#![macro_use]
//! Provide access to the STM32 digital-to-analog converter (DAC).
use core::marker::PhantomData;
use embassy_hal_common::{into_ref, PeripheralRef};
use crate::pac::dac;
use crate::rcc::RccPeripheral;
use crate::{peripherals, Peripheral};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Curstom Errors
pub enum Error {
UnconfiguredChannel,
InvalidValue,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// DAC Channels
pub enum Channel {
Ch1,
Ch2,
}
impl Channel {
const fn index(&self) -> usize {
match self {
Channel::Ch1 => 0,
Channel::Ch2 => 1,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Trigger sources for CH1
pub enum Ch1Trigger {
Tim6,
Tim3,
Tim7,
Tim15,
Tim2,
Exti9,
Software,
}
impl Ch1Trigger {
fn tsel(&self) -> dac::vals::Tsel1 {
match self {
Ch1Trigger::Tim6 => dac::vals::Tsel1::TIM6_TRGO,
Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM3_TRGO,
Ch1Trigger::Tim7 => dac::vals::Tsel1::TIM7_TRGO,
Ch1Trigger::Tim15 => dac::vals::Tsel1::TIM15_TRGO,
Ch1Trigger::Tim2 => dac::vals::Tsel1::TIM2_TRGO,
Ch1Trigger::Exti9 => dac::vals::Tsel1::EXTI9,
Ch1Trigger::Software => dac::vals::Tsel1::SOFTWARE,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Trigger sources for CH2
pub enum Ch2Trigger {
Tim6,
Tim8,
Tim7,
Tim5,
Tim2,
Tim4,
Exti9,
Software,
}
impl Ch2Trigger {
fn tsel(&self) -> dac::vals::Tsel2 {
match self {
Ch2Trigger::Tim6 => dac::vals::Tsel2::TIM6_TRGO,
Ch2Trigger::Tim8 => dac::vals::Tsel2::TIM8_TRGO,
Ch2Trigger::Tim7 => dac::vals::Tsel2::TIM7_TRGO,
Ch2Trigger::Tim5 => dac::vals::Tsel2::TIM5_TRGO,
Ch2Trigger::Tim2 => dac::vals::Tsel2::TIM2_TRGO,
Ch2Trigger::Tim4 => dac::vals::Tsel2::TIM4_TRGO,
Ch2Trigger::Exti9 => dac::vals::Tsel2::EXTI9,
Ch2Trigger::Software => dac::vals::Tsel2::SOFTWARE,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Single 8 or 12 bit value that can be output by the DAC
pub enum Value {
// 8 bit value
Bit8(u8),
// 12 bit value stored in a u16, left-aligned
Bit12Left(u16),
// 12 bit value stored in a u16, right-aligned
Bit12Right(u16),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Array variant of [`Value`]
pub enum ValueArray<'a> {
// 8 bit values
Bit8(&'a [u8]),
// 12 bit value stored in a u16, left-aligned
Bit12Left(&'a [u16]),
// 12 bit values stored in a u16, right-aligned
Bit12Right(&'a [u16]),
}
/// Provide common functions for DAC channels
pub trait DacChannel<T: Instance, Tx> {
const CHANNEL: Channel;
/// Enable trigger of the given channel
fn set_trigger_enable(&mut self, on: bool) -> Result<(), Error> {
T::regs().cr().modify(|reg| {
reg.set_ten(Self::CHANNEL.index(), on);
});
Ok(())
}
/// Set mode register of the given channel
#[cfg(dac_v2)]
fn set_channel_mode(&mut self, val: u8) -> Result<(), Error> {
T::regs().mcr().modify(|reg| {
reg.set_mode(Self::CHANNEL.index(), val);
});
Ok(())
}
/// Set enable register of the given channel
fn set_channel_enable(&mut self, on: bool) -> Result<(), Error> {
T::regs().cr().modify(|reg| {
reg.set_en(Self::CHANNEL.index(), on);
});
Ok(())
}
/// Enable the DAC channel `ch`
fn enable_channel(&mut self) -> Result<(), Error> {
self.set_channel_enable(true)
}
/// Disable the DAC channel `ch`
fn disable_channel(&mut self) -> Result<(), Error> {
self.set_channel_enable(false)
}
/// Perform a software trigger on `ch`
fn trigger(&mut self) {
T::regs().swtrigr().write(|reg| {
reg.set_swtrig(Self::CHANNEL.index(), true);
});
}
/// Set a value to be output by the DAC on trigger.
///
/// The `value` is written to the corresponding "data holding register".
fn set(&mut self, value: Value) -> Result<(), Error> {
match value {
Value::Bit8(v) => T::regs().dhr8r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)),
Value::Bit12Left(v) => T::regs().dhr12l(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)),
Value::Bit12Right(v) => T::regs().dhr12r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)),
}
Ok(())
}
}
/// Hold two DAC channels
///
/// Note: This consumes the DAC `Instance` only once, allowing to get both channels simultaneously.
///
/// # Example for obtaining both DAC channels
///
/// ```ignore
/// // DMA channels and pins may need to be changed for your controller
/// let (dac_ch1, dac_ch2) =
/// embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split();
/// ```
pub struct Dac<'d, T: Instance, TxCh1, TxCh2> {
ch1: DacCh1<'d, T, TxCh1>,
ch2: DacCh2<'d, T, TxCh2>,
}
/// DAC CH1
///
/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously.
pub struct DacCh1<'d, T: Instance, Tx> {
/// To consume T
_peri: PeripheralRef<'d, T>,
#[allow(unused)] // For chips whose DMA is not (yet) supported
dma: PeripheralRef<'d, Tx>,
}
/// DAC CH2
///
/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously.
pub struct DacCh2<'d, T: Instance, Tx> {
/// Instead of PeripheralRef to consume T
phantom: PhantomData<&'d mut T>,
#[allow(unused)] // For chips whose DMA is not (yet) supported
dma: PeripheralRef<'d, Tx>,
}
impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> {
/// Obtain DAC CH1
pub fn new(
peri: impl Peripheral<P = T> + 'd,
dma: impl Peripheral<P = Tx> + 'd,
_pin: impl Peripheral<P = impl DacPin<T, 1>> + 'd,
) -> Self {
into_ref!(peri, dma);
T::enable();
T::reset();
let mut dac = Self { _peri: peri, dma };
// Configure each activated channel. All results can be `unwrap`ed since they
// will only error if the channel is not configured (i.e. ch1, ch2 are false)
#[cfg(dac_v2)]
dac.set_channel_mode(0).unwrap();
dac.enable_channel().unwrap();
dac.set_trigger_enable(true).unwrap();
dac
}
/// Select a new trigger for this channel
///
/// **Important**: This disables the channel!
pub fn select_trigger(&mut self, trigger: Ch1Trigger) -> Result<(), Error> {
unwrap!(self.disable_channel());
T::regs().cr().modify(|reg| {
reg.set_tsel1(trigger.tsel());
});
Ok(())
}
/// Write `data` to the DAC CH1 via DMA.
///
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set.
/// This will configure a circular DMA transfer that periodically outputs the `data`.
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled.
///
/// **Important:** Channel 1 has to be configured for the DAC instance!
#[cfg(all(bdma, not(dma)))] // It currently only works with BDMA-only chips (DMA should theoretically work though)
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error>
where
Tx: DmaCh1<T>,
{
let channel = Channel::Ch1.index();
debug!("Writing to channel {}", channel);
// Enable DAC and DMA
T::regs().cr().modify(|w| {
w.set_en(channel, true);
w.set_dmaen(channel, true);
});
let tx_request = self.dma.request();
let dma_channel = &self.dma;
let tx_options = crate::dma::TransferOptions {
circular,
half_transfer_ir: false,
complete_transfer_ir: !circular,
..Default::default()
};
// Initiate the correct type of DMA transfer depending on what data is passed
let tx_f = match data {
ValueArray::Bit8(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr8r(channel).as_ptr() as *mut u8,
tx_options,
)
},
ValueArray::Bit12Left(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12l(channel).as_ptr() as *mut u16,
tx_options,
)
},
ValueArray::Bit12Right(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12r(channel).as_ptr() as *mut u16,
tx_options,
)
},
};
tx_f.await;
// finish dma
// TODO: Do we need to check any status registers here?
T::regs().cr().modify(|w| {
// Disable the DAC peripheral
w.set_en(channel, false);
// Disable the DMA. TODO: Is this necessary?
w.set_dmaen(channel, false);
});
Ok(())
}
}
impl<'d, T: Instance, Tx> DacCh2<'d, T, Tx> {
/// Obtain DAC CH2
pub fn new(
_peri: impl Peripheral<P = T> + 'd,
dma: impl Peripheral<P = Tx> + 'd,
_pin: impl Peripheral<P = impl DacPin<T, 2>> + 'd,
) -> Self {
into_ref!(_peri, dma);
T::enable();
T::reset();
let mut dac = Self {
phantom: PhantomData,
dma,
};
// Configure each activated channel. All results can be `unwrap`ed since they
// will only error if the channel is not configured (i.e. ch1, ch2 are false)
#[cfg(dac_v2)]
dac.set_channel_mode(0).unwrap();
dac.enable_channel().unwrap();
dac.set_trigger_enable(true).unwrap();
dac
}
/// Select a new trigger for this channel
pub fn select_trigger(&mut self, trigger: Ch2Trigger) -> Result<(), Error> {
unwrap!(self.disable_channel());
T::regs().cr().modify(|reg| {
reg.set_tsel2(trigger.tsel());
});
Ok(())
}
/// Write `data` to the DAC CH2 via DMA.
///
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set.
/// This will configure a circular DMA transfer that periodically outputs the `data`.
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled.
///
/// **Important:** Channel 2 has to be configured for the DAC instance!
#[cfg(all(bdma, not(dma)))] // It currently only works with BDMA-only chips (DMA should theoretically work though)
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error>
where
Tx: DmaCh2<T>,
{
let channel = Channel::Ch2.index();
debug!("Writing to channel {}", channel);
// Enable DAC and DMA
T::regs().cr().modify(|w| {
w.set_en(channel, true);
w.set_dmaen(channel, true);
});
let tx_request = self.dma.request();
let dma_channel = &self.dma;
let tx_options = crate::dma::TransferOptions {
circular,
half_transfer_ir: false,
complete_transfer_ir: !circular,
..Default::default()
};
// Initiate the correct type of DMA transfer depending on what data is passed
let tx_f = match data {
ValueArray::Bit8(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr8r(channel).as_ptr() as *mut u8,
tx_options,
)
},
ValueArray::Bit12Left(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12l(channel).as_ptr() as *mut u16,
tx_options,
)
},
ValueArray::Bit12Right(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12r(channel).as_ptr() as *mut u16,
tx_options,
)
},
};
tx_f.await;
// finish dma
// TODO: Do we need to check any status registers here?
T::regs().cr().modify(|w| {
// Disable the DAC peripheral
w.set_en(channel, false);
// Disable the DMA. TODO: Is this necessary?
w.set_dmaen(channel, false);
});
Ok(())
}
}
impl<'d, T: Instance, TxCh1, TxCh2> Dac<'d, T, TxCh1, TxCh2> {
/// Create a new DAC instance with both channels.
///
/// This is used to obtain two independent channels via `split()` for use e.g. with DMA.
pub fn new(
peri: impl Peripheral<P = T> + 'd,
dma_ch1: impl Peripheral<P = TxCh1> + 'd,
dma_ch2: impl Peripheral<P = TxCh2> + 'd,
_pin_ch1: impl Peripheral<P = impl DacPin<T, 1>> + 'd,
_pin_ch2: impl Peripheral<P = impl DacPin<T, 2>> + 'd,
) -> Self {
into_ref!(peri, dma_ch1, dma_ch2);
T::enable();
T::reset();
let mut dac_ch1 = DacCh1 {
_peri: peri,
dma: dma_ch1,
};
let mut dac_ch2 = DacCh2 {
phantom: PhantomData,
dma: dma_ch2,
};
// Configure each activated channel. All results can be `unwrap`ed since they
// will only error if the channel is not configured (i.e. ch1, ch2 are false)
#[cfg(dac_v2)]
dac_ch1.set_channel_mode(0).unwrap();
dac_ch1.enable_channel().unwrap();
dac_ch1.set_trigger_enable(true).unwrap();
#[cfg(dac_v2)]
dac_ch2.set_channel_mode(0).unwrap();
dac_ch2.enable_channel().unwrap();
dac_ch2.set_trigger_enable(true).unwrap();
Self {
ch1: dac_ch1,
ch2: dac_ch2,
}
}
/// Split the DAC into CH1 and CH2 for independent use.
pub fn split(self) -> (DacCh1<'d, T, TxCh1>, DacCh2<'d, T, TxCh2>) {
(self.ch1, self.ch2)
}
/// Get mutable reference to CH1
pub fn ch1_mut(&mut self) -> &mut DacCh1<'d, T, TxCh1> {
&mut self.ch1
}
/// Get mutable reference to CH2
pub fn ch2_mut(&mut self) -> &mut DacCh2<'d, T, TxCh2> {
&mut self.ch2
}
/// Get reference to CH1
pub fn ch1(&mut self) -> &DacCh1<'d, T, TxCh1> {
&self.ch1
}
/// Get reference to CH2
pub fn ch2(&mut self) -> &DacCh2<'d, T, TxCh2> {
&self.ch2
}
}
impl<'d, T: Instance, Tx> DacChannel<T, Tx> for DacCh1<'d, T, Tx> {
const CHANNEL: Channel = Channel::Ch1;
}
impl<'d, T: Instance, Tx> DacChannel<T, Tx> for DacCh2<'d, T, Tx> {
const CHANNEL: Channel = Channel::Ch2;
}
pub(crate) mod sealed {
pub trait Instance {
fn regs() -> &'static crate::pac::dac::Dac;
}
}
pub trait Instance: sealed::Instance + RccPeripheral + 'static {}
dma_trait!(DmaCh1, Instance);
dma_trait!(DmaCh2, Instance);
/// Marks a pin that can be used with the DAC
pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {}
foreach_peripheral!(
(dac, $inst:ident) => {
// H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented
#[cfg(rcc_h7)]
impl crate::rcc::sealed::RccPeripheral for peripherals::$inst {
fn frequency() -> crate::time::Hertz {
critical_section::with(|_| unsafe { crate::rcc::get_freqs().apb1 })
}
fn reset() {
critical_section::with(|_| {
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true));
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false));
})
}
fn enable() {
critical_section::with(|_| {
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true));
})
}
fn disable() {
critical_section::with(|_| {
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false))
})
}
}
#[cfg(rcc_h7)]
impl crate::rcc::RccPeripheral for peripherals::$inst {}
impl crate::dac::sealed::Instance for peripherals::$inst {
fn regs() -> &'static crate::pac::dac::Dac {
&crate::pac::$inst
}
}
impl crate::dac::Instance for peripherals::$inst {}
};
);
macro_rules! impl_dac_pin {
($inst:ident, $pin:ident, $ch:expr) => {
impl crate::dac::DacPin<peripherals::$inst, $ch> for crate::peripherals::$pin {}
};
}

View file

@ -21,11 +21,22 @@ use crate::pac::bdma::{regs, vals};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TransferOptions {}
pub struct TransferOptions {
/// Enable circular DMA
pub circular: bool,
/// Enable half transfer interrupt
pub half_transfer_ir: bool,
/// Enable transfer complete interrupt
pub complete_transfer_ir: bool,
}
impl Default for TransferOptions {
fn default() -> Self {
Self {}
Self {
circular: false,
half_transfer_ir: false,
complete_transfer_ir: true,
}
}
}
@ -253,7 +264,7 @@ impl<'a, C: Channel> Transfer<'a, C> {
mem_len: usize,
incr_mem: bool,
data_size: WordSize,
_options: TransferOptions,
options: TransferOptions,
) -> Self {
let ch = channel.regs().ch(channel.num());
@ -283,7 +294,15 @@ impl<'a, C: Channel> Transfer<'a, C> {
}
w.set_dir(dir.into());
w.set_teie(true);
w.set_tcie(true);
w.set_tcie(options.complete_transfer_ir);
w.set_htie(options.half_transfer_ir);
if options.circular {
w.set_circ(vals::Circ::ENABLED);
debug!("Setting circular mode");
} else {
w.set_circ(vals::Circ::DISABLED);
}
w.set_pl(vals::Pl::VERYHIGH);
w.set_en(true);
});
@ -310,8 +329,9 @@ impl<'a, C: Channel> Transfer<'a, C> {
pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num());
let en = ch.cr().read().en();
let circular = ch.cr().read().circ() == vals::Circ::ENABLED;
let tcif = STATE.complete_count[self.channel.index()].load(Ordering::Acquire) != 0;
en && !tcif
en && (circular || !tcif)
}
/// Gets the total remaining transfers for the channel
@ -477,6 +497,8 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
let ch = self.channel.regs().ch(self.channel.num());
// Disable the channel. Keep the IEs enabled so the irqs still fire.
// If the channel is enabled and transfer is not completed, we need to perform
// two separate write access to the CR register to disable the channel.
ch.cr().write(|w| {
w.set_teie(true);
w.set_htie(true);

View file

@ -174,7 +174,7 @@ impl<'a> RDesRing<'a> {
// Receive descriptor unavailable
Rps::SUSPENDED => RunningState::Stopped,
// Closing receive descriptor
Rps(0b101) => RunningState::Running,
Rps::_RESERVED_5 => RunningState::Running,
// Transferring the receive packet data from receive buffer to host memory
Rps::RUNNINGWRITING => RunningState::Running,
_ => RunningState::Unknown,

View file

@ -243,7 +243,7 @@ mod tests {
for test_run in fn_results {
let (ckd, bits) = compute_dead_time_value(test_run.value);
assert_eq!(ckd.0, test_run.ckd.0);
assert_eq!(ckd.to_bits(), test_run.ckd.to_bits());
assert_eq!(bits, test_run.bits);
}
}

View file

@ -126,7 +126,7 @@ pub(crate) unsafe fn init(config: Config) {
});
while !RCC.cr().read().hsirdy() {}
(HSI_FREQ.0 >> div.0, Sw::HSI)
(HSI_FREQ.0 >> div.to_bits(), Sw::HSI)
}
ClockSrc::HSE(freq) => {
// Enable HSE
@ -157,7 +157,7 @@ pub(crate) unsafe fn init(config: Config) {
let mut set_flash_latency_after = false;
FLASH.acr().modify(|w| {
// Is the current flash latency less than what we need at the new SYSCLK?
if w.latency().0 <= target_flash_latency.0 {
if w.latency().to_bits() <= target_flash_latency.to_bits() {
// We must increase the number of wait states now
w.set_latency(target_flash_latency)
} else {
@ -171,12 +171,12 @@ pub(crate) unsafe fn init(config: Config) {
// > Flash memory.
//
// Enable flash prefetching if we have at least one wait state, and disable it otherwise.
w.set_prften(target_flash_latency.0 > 0);
w.set_prften(target_flash_latency.to_bits() > 0);
});
if !set_flash_latency_after {
// Spin until the effective flash latency is compatible with the clock change
while FLASH.acr().read().latency().0 < target_flash_latency.0 {}
while FLASH.acr().read().latency().to_bits() < target_flash_latency.to_bits() {}
}
// Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once
@ -218,7 +218,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}

View file

@ -1,3 +1,5 @@
use stm32_metapac::flash::vals::Latency;
use super::{set_freqs, Clocks};
use crate::pac::rcc::vals::{Hpre, Pllmul, Pllsrc, Ppre, Sw, Usbsw};
use crate::pac::{FLASH, RCC};
@ -85,14 +87,11 @@ pub(crate) unsafe fn init(config: Config) {
let timer_mul = if ppre == 1 { 1 } else { 2 };
FLASH.acr().write(|w| {
let latency = if real_sysclk <= 24_000_000 {
0
} else if real_sysclk <= 48_000_000 {
1
w.set_latency(if real_sysclk <= 24_000_000 {
Latency::WS0
} else {
2
};
w.latency().0 = latency;
Latency::WS1
});
});
match (config.hse.is_some(), use_hsi48) {
@ -134,20 +133,20 @@ pub(crate) unsafe fn init(config: Config) {
// TODO: Option to use CRS (Clock Recovery)
if let Some(pllmul_bits) = pllmul_bits {
RCC.cfgr().modify(|w| w.set_pllmul(Pllmul(pllmul_bits)));
RCC.cfgr().modify(|w| w.set_pllmul(Pllmul::from_bits(pllmul_bits)));
RCC.cr().modify(|w| w.set_pllon(true));
while !RCC.cr().read().pllrdy() {}
RCC.cfgr().modify(|w| {
w.set_ppre(Ppre(ppre_bits));
w.set_hpre(Hpre(hpre_bits));
w.set_ppre(Ppre::from_bits(ppre_bits));
w.set_hpre(Hpre::from_bits(hpre_bits));
w.set_sw(Sw::PLL)
});
} else {
RCC.cfgr().modify(|w| {
w.set_ppre(Ppre(ppre_bits));
w.set_hpre(Hpre(hpre_bits));
w.set_ppre(Ppre::from_bits(ppre_bits));
w.set_hpre(Hpre::from_bits(hpre_bits));
if config.hse.is_some() {
w.set_sw(Sw::HSE);

View file

@ -106,11 +106,11 @@ pub(crate) unsafe fn init(config: Config) {
// Only needed for stm32f103?
FLASH.acr().write(|w| {
w.set_latency(if real_sysclk <= 24_000_000 {
Latency(0b000)
Latency::WS0
} else if real_sysclk <= 48_000_000 {
Latency(0b001)
Latency::WS1
} else {
Latency(0b010)
Latency::WS2
});
});
@ -147,12 +147,13 @@ pub(crate) unsafe fn init(config: Config) {
if let Some(pllmul_bits) = pllmul_bits {
let pllctpre_flag: u8 = if config.pllxtpre { 1 } else { 0 };
RCC.cfgr().modify(|w| w.set_pllxtpre(Pllxtpre(pllctpre_flag)));
RCC.cfgr()
.modify(|w| w.set_pllxtpre(Pllxtpre::from_bits(pllctpre_flag)));
// enable PLL and wait for it to be ready
RCC.cfgr().modify(|w| {
w.set_pllmul(Pllmul(pllmul_bits));
w.set_pllsrc(Pllsrc(config.hse.is_some() as u8));
w.set_pllmul(Pllmul::from_bits(pllmul_bits));
w.set_pllsrc(Pllsrc::from_bits(config.hse.is_some() as u8));
});
RCC.cr().modify(|w| w.set_pllon(true));
@ -161,22 +162,19 @@ pub(crate) unsafe fn init(config: Config) {
// Only needed for stm32f103?
RCC.cfgr().modify(|w| {
w.set_adcpre(Adcpre(apre_bits));
w.set_ppre2(Ppre1(ppre2_bits));
w.set_ppre1(Ppre1(ppre1_bits));
w.set_hpre(Hpre(hpre_bits));
w.set_adcpre(Adcpre::from_bits(apre_bits));
w.set_ppre2(Ppre1::from_bits(ppre2_bits));
w.set_ppre1(Ppre1::from_bits(ppre1_bits));
w.set_hpre(Hpre::from_bits(hpre_bits));
#[cfg(not(rcc_f100))]
w.set_usbpre(Usbpre(usbpre as u8));
w.set_sw(Sw(if pllmul_bits.is_some() {
// PLL
0b10
w.set_usbpre(Usbpre::from_bits(usbpre as u8));
w.set_sw(if pllmul_bits.is_some() {
Sw::PLL
} else if config.hse.is_some() {
// HSE
0b1
Sw::HSE
} else {
// HSI
0b0
}));
Sw::HSI
});
});
set_freqs(Clocks {

View file

@ -485,7 +485,7 @@ pub(crate) unsafe fn init(config: Config) {
w.set_ppre1(config.apb1_pre.into());
w.set_ppre2(config.apb2_pre.into());
});
while RCC.cfgr().read().sws() != sw.0 {}
while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {}
// Turn off HSI to save power if we don't need it
if !config.hsi {

View file

@ -87,7 +87,7 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, plli2s: Opti
let sysclk = pllsysclk.unwrap_or(pllsrcclk);
if pllsysclk.is_none() && !pll48clk {
RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc(use_hse as u8)));
RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)));
return PllResults {
use_pll: false,
@ -141,9 +141,9 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, plli2s: Opti
RCC.pllcfgr().modify(|w| {
w.set_pllm(pllm as u8);
w.set_plln(plln as u16);
w.set_pllp(Pllp(pllp as u8));
w.set_pllp(Pllp::from_bits(pllp as u8));
w.set_pllq(pllq as u8);
w.set_pllsrc(Pllsrc(use_hse as u8));
w.set_pllsrc(Pllsrc::from_bits(use_hse as u8));
});
let real_pllsysclk = vco_in * plln / sysclk_div;
@ -323,7 +323,7 @@ fn flash_setup(sysclk: u32) {
critical_section::with(|_| {
FLASH
.acr()
.modify(|w| w.set_latency(Latency(((sysclk - 1) / FLASH_LATENCY_STEP) as u8)));
.modify(|w| w.set_latency(Latency::from_bits(((sysclk - 1) / FLASH_LATENCY_STEP) as u8)));
});
}
@ -440,8 +440,8 @@ pub(crate) unsafe fn init(config: Config) {
}
RCC.cfgr().modify(|w| {
w.set_ppre2(Ppre(ppre2_bits));
w.set_ppre1(Ppre(ppre1_bits));
w.set_ppre2(Ppre::from_bits(ppre2_bits));
w.set_ppre1(Ppre::from_bits(ppre1_bits));
w.set_hpre(hpre_bits);
});

View file

@ -30,7 +30,7 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48clk: bo
let sysclk = pllsysclk.unwrap_or(pllsrcclk);
if pllsysclk.is_none() && !pll48clk {
RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc(use_hse as u8)));
RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)));
return PllResults {
use_pll: false,
@ -83,9 +83,9 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48clk: bo
RCC.pllcfgr().modify(|w| {
w.set_pllm(pllm as u8);
w.set_plln(plln as u16);
w.set_pllp(Pllp(pllp as u8));
w.set_pllp(Pllp::from_bits(pllp as u8));
w.set_pllq(pllq as u8);
w.set_pllsrc(Pllsrc(use_hse as u8));
w.set_pllsrc(Pllsrc::from_bits(use_hse as u8));
});
let real_pllsysclk = vco_in * plln / sysclk_div;
@ -106,7 +106,7 @@ fn flash_setup(sysclk: u32) {
critical_section::with(|_| {
FLASH
.acr()
.modify(|w| w.set_latency(Latency(((sysclk - 1) / FLASH_LATENCY_STEP) as u8)));
.modify(|w| w.set_latency(Latency::from_bits(((sysclk - 1) / FLASH_LATENCY_STEP) as u8)));
});
}
@ -246,8 +246,8 @@ pub(crate) unsafe fn init(config: Config) {
}
RCC.cfgr().modify(|w| {
w.set_ppre2(Ppre(ppre2_bits));
w.set_ppre1(Ppre(ppre1_bits));
w.set_ppre2(Ppre::from_bits(ppre2_bits));
w.set_ppre1(Ppre::from_bits(ppre1_bits));
w.set_hpre(hpre_bits);
});

View file

@ -344,7 +344,7 @@ pub(crate) unsafe fn init(config: Config) {
});
while !RCC.cr().read().hsirdy() {}
(HSI_FREQ.0 >> div.0, Sw::HSI)
(HSI_FREQ.0 >> div.to_bits(), Sw::HSI)
}
ClockSrc::HSE(freq) => {
// Enable HSE
@ -381,7 +381,7 @@ pub(crate) unsafe fn init(config: Config) {
let mut set_flash_latency_after = false;
FLASH.acr().modify(|w| {
// Is the current flash latency less than what we need at the new SYSCLK?
if w.latency().0 <= target_flash_latency.0 {
if w.latency().to_bits() <= target_flash_latency.to_bits() {
// We must increase the number of wait states now
w.set_latency(target_flash_latency)
} else {
@ -395,12 +395,12 @@ pub(crate) unsafe fn init(config: Config) {
// > Flash memory.
//
// Enable flash prefetching if we have at least one wait state, and disable it otherwise.
w.set_prften(target_flash_latency.0 > 0);
w.set_prften(target_flash_latency.to_bits() > 0);
});
if !set_flash_latency_after {
// Spin until the effective flash latency is compatible with the clock change
while FLASH.acr().read().latency().0 < target_flash_latency.0 {}
while FLASH.acr().read().latency().to_bits() < target_flash_latency.to_bits() {}
}
// Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once
@ -442,7 +442,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}

View file

@ -3,6 +3,7 @@ use stm32_metapac::rcc::vals::{Hpre, Pllsrc, Ppre, Sw};
use stm32_metapac::FLASH;
use crate::pac::{PWR, RCC};
use crate::rcc::sealed::RccPeripheral;
use crate::rcc::{set_freqs, Clocks};
use crate::time::Hertz;
@ -316,6 +317,27 @@ impl Into<Hpre> for AHBPrescaler {
}
}
/// Sets the source for the 48MHz clock to the USB and RNG peripherals.
pub enum Clock48MhzSrc {
/// Use the High Speed Internal Oscillator. For USB usage, the CRS must be used to calibrate the
/// oscillator to comply with the USB specification for oscillator tolerance.
Hsi48(Option<CrsConfig>),
/// Use the PLLQ output. The PLL must be configured to output a 48MHz clock. For USB usage the
/// PLL needs to be using the HSE source to comply with the USB specification for oscillator
/// tolerance.
PllQ,
}
/// Sets the sync source for the Clock Recovery System (CRS).
pub enum CrsSyncSource {
/// Use an external GPIO to sync the CRS.
Gpio,
/// Use the Low Speed External oscillator to sync the CRS.
Lse,
/// Use the USB SOF to sync the CRS.
Usb,
}
/// Clocks configutation
pub struct Config {
pub mux: ClockSrc,
@ -326,6 +348,14 @@ pub struct Config {
/// Iff PLL is requested as the main clock source in the `mux` field then the PLL configuration
/// MUST turn on the PLLR output.
pub pll: Option<Pll>,
/// Sets the clock source for the 48MHz clock used by the USB and RNG peripherals.
pub clock_48mhz_src: Option<Clock48MhzSrc>,
}
/// Configuration for the Clock Recovery System (CRS) used to trim the HSI48 oscillator.
pub struct CrsConfig {
/// Sync source for the CRS.
pub sync_src: CrsSyncSource,
}
impl Default for Config {
@ -338,6 +368,7 @@ impl Default for Config {
apb2_pre: APBPrescaler::NotDivided,
low_power_run: false,
pll: None,
clock_48mhz_src: None,
}
}
}
@ -430,7 +461,7 @@ pub(crate) unsafe fn init(config: Config) {
assert!(pll_freq.is_some());
assert!(pll_freq.as_ref().unwrap().pll_r.is_some());
let freq = pll_freq.unwrap().pll_r.unwrap().0;
let freq = pll_freq.as_ref().unwrap().pll_r.unwrap().0;
assert!(freq <= 170_000_000);
@ -497,6 +528,50 @@ pub(crate) unsafe fn init(config: Config) {
}
};
// Setup the 48 MHz clock if needed
if let Some(clock_48mhz_src) = config.clock_48mhz_src {
let source = match clock_48mhz_src {
Clock48MhzSrc::PllQ => {
// Make sure the PLLQ is enabled and running at 48Mhz
let pllq_freq = pll_freq.as_ref().and_then(|f| f.pll_q);
assert!(pllq_freq.is_some() && pllq_freq.unwrap().0 == 48_000_000);
crate::pac::rcc::vals::Clk48sel::PLLQCLK
}
Clock48MhzSrc::Hsi48(crs_config) => {
// Enable HSI48
RCC.crrcr().modify(|w| w.set_hsi48on(true));
// Wait for HSI48 to turn on
while RCC.crrcr().read().hsi48rdy() == false {}
// Enable and setup CRS if needed
if let Some(crs_config) = crs_config {
crate::peripherals::CRS::enable();
let sync_src = match crs_config.sync_src {
CrsSyncSource::Gpio => crate::pac::crs::vals::Syncsrc::GPIO,
CrsSyncSource::Lse => crate::pac::crs::vals::Syncsrc::LSE,
CrsSyncSource::Usb => crate::pac::crs::vals::Syncsrc::USB,
};
crate::pac::CRS.cfgr().modify(|w| {
w.set_syncsrc(sync_src);
});
// These are the correct settings for standard USB operation. If other settings
// are needed there will need to be additional config options for the CRS.
crate::pac::CRS.cr().modify(|w| {
w.set_autotrimen(true);
w.set_cen(true);
});
}
crate::pac::rcc::vals::Clk48sel::HSI48
}
};
RCC.ccipr().modify(|w| w.set_clk48sel(source));
}
if config.low_power_run {
assert!(sys_clk <= 2_000_000);
PWR.cr1().modify(|w| w.set_lpr(true));

View file

@ -601,22 +601,22 @@ pub(crate) unsafe fn init(mut config: Config) {
// Core Prescaler / AHB Prescaler / APB3 Prescaler
RCC.d1cfgr().modify(|w| {
w.set_d1cpre(Hpre(d1cpre_bits));
w.set_d1ppre(Dppre(ppre3_bits));
w.set_d1cpre(Hpre::from_bits(d1cpre_bits));
w.set_d1ppre(Dppre::from_bits(ppre3_bits));
w.set_hpre(hpre_bits)
});
// Ensure core prescaler value is valid before future lower
// core voltage
while RCC.d1cfgr().read().d1cpre().0 != d1cpre_bits {}
while RCC.d1cfgr().read().d1cpre().to_bits() != d1cpre_bits {}
// APB1 / APB2 Prescaler
RCC.d2cfgr().modify(|w| {
w.set_d2ppre1(Dppre(ppre1_bits));
w.set_d2ppre2(Dppre(ppre2_bits));
w.set_d2ppre1(Dppre::from_bits(ppre1_bits));
w.set_d2ppre2(Dppre::from_bits(ppre2_bits));
});
// APB4 Prescaler
RCC.d3cfgr().modify(|w| w.set_d3ppre(Dppre(ppre4_bits)));
RCC.d3cfgr().modify(|w| w.set_d3ppre(Dppre::from_bits(ppre4_bits)));
// Peripheral Clock (per_ck)
RCC.d1ccipr().modify(|w| w.set_ckpersel(ckpersel));
@ -640,7 +640,7 @@ pub(crate) unsafe fn init(mut config: Config) {
_ => Sw::HSI,
};
RCC.cfgr().modify(|w| w.set_sw(sw));
while RCC.cfgr().read().sws() != sw.0 {}
while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {}
// IO compensation cell - Requires CSI clock and SYSCFG
assert!(RCC.cr().read().csirdy());
@ -806,7 +806,8 @@ mod pll {
RCC.pllcfgr().modify(|w| w.set_pllfracen(plln, false));
let vco_ck = ref_x_ck * pll_x_n;
RCC.plldivr(plln).modify(|w| w.set_divp1(Divp((pll_x_p - 1) as u8)));
RCC.plldivr(plln)
.modify(|w| w.set_divp1(Divp::from_bits((pll_x_p - 1) as u8)));
RCC.pllcfgr().modify(|w| w.set_divpen(plln, true));
// Calulate additional output dividers

View file

@ -1,7 +1,7 @@
use crate::pac::rcc::vals::{Hpre, Msirange, Plldiv, Pllmul, Pllsrc, Ppre, Sw};
use crate::pac::RCC;
#[cfg(crs)]
use crate::pac::{CRS, SYSCFG};
use crate::pac::{crs, CRS, SYSCFG};
use crate::rcc::{set_freqs, Clocks};
use crate::time::Hertz;
@ -293,7 +293,7 @@ pub(crate) unsafe fn init(config: Config) {
AHBPrescaler::NotDivided => sys_clk,
pre => {
let pre: Hpre = pre.into();
let pre = 1 << (pre.0 as u32 - 7);
let pre = 1 << (pre.to_bits() as u32 - 7);
sys_clk / pre
}
};
@ -302,7 +302,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}
@ -312,7 +312,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}
@ -338,7 +338,7 @@ pub(crate) unsafe fn init(config: Config) {
CRS.cfgr().write(|w|
// Select LSE as synchronization source
w.set_syncsrc(0b01));
w.set_syncsrc(crs::vals::Syncsrc::LSE));
CRS.cr().modify(|w| {
w.set_autotrimen(true);
w.set_cen(true);

View file

@ -294,7 +294,7 @@ pub(crate) unsafe fn init(config: Config) {
AHBPrescaler::NotDivided => sys_clk,
pre => {
let pre: Hpre = pre.into();
let pre = 1 << (pre.0 as u32 - 7);
let pre = 1 << (pre.to_bits() as u32 - 7);
sys_clk / pre
}
};
@ -303,7 +303,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}
@ -313,7 +313,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}

View file

@ -635,7 +635,7 @@ pub(crate) unsafe fn init(config: Config) {
AHBPrescaler::NotDivided => sys_clk,
pre => {
let pre: Hpre = pre.into();
let pre = 1 << (pre.0 as u32 - 7);
let pre = 1 << (pre.to_bits() as u32 - 7);
sys_clk / pre
}
};
@ -644,7 +644,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}
@ -654,7 +654,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}

View file

@ -461,7 +461,7 @@ pub(crate) unsafe fn init(config: Config) {
AHBPrescaler::NotDivided => sys_clk,
pre => {
let pre: Hpre = pre.into();
let pre = 1 << (pre.0 as u32 - 7);
let pre = 1 << (pre.to_bits() as u32 - 7);
sys_clk / pre
}
};
@ -470,7 +470,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}
@ -480,7 +480,7 @@ pub(crate) unsafe fn init(config: Config) {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
let pre: Ppre = pre.into();
let pre: u8 = 1 << (pre.0 - 3);
let pre: u8 = 1 << (pre.to_bits() - 3);
let freq = ahb_freq / pre as u32;
(freq, freq * 2)
}

View file

@ -126,7 +126,7 @@ pub enum PllM {
impl Into<Pllm> for PllM {
fn into(self) -> Pllm {
Pllm(self as u8)
Pllm::from_bits(self as u8)
}
}

View file

@ -36,7 +36,7 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
#[cfg(rtc_v2wb)]
let rtcsel = reg.rtcsel();
#[cfg(not(rtc_v2wb))]
let rtcsel = reg.rtcsel().0;
let rtcsel = reg.rtcsel().to_bits();
if !reg.rtcen() || rtcsel != clock_config {
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
@ -54,7 +54,7 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
// Select RTC source
#[cfg(not(rtc_v2wb))]
w.set_rtcsel(Rtcsel(clock_config));
w.set_rtcsel(Rtcsel::from_bits(clock_config));
#[cfg(rtc_v2wb)]
w.set_rtcsel(clock_config);
w.set_rtcen(true);

View file

@ -26,7 +26,7 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
let config_rtcsel = rtc_config.clock_config as u8;
#[cfg(not(any(rcc_wl5, rcc_wle)))]
let config_rtcsel = crate::pac::rcc::vals::Rtcsel(config_rtcsel);
let config_rtcsel = crate::pac::rcc::vals::Rtcsel::from_bits(config_rtcsel);
if !reg.rtcen() || reg.rtcsel() != config_rtcsel {
crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true));

View file

@ -227,7 +227,11 @@ const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOp
fifo_threshold: Some(crate::dma::FifoThreshold::Full),
};
#[cfg(all(sdmmc_v1, not(dma)))]
const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions {};
const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions {
circular: false,
half_transfer_ir: false,
complete_transfer_ir: true,
};
/// SDMMC configuration
///

View file

@ -650,7 +650,7 @@ fn compute_baud_rate(clocks: Hertz, freq: Hertz) -> Br {
_ => 0b111,
};
Br(val)
Br::from_bits(val)
}
trait RegsExt {
@ -772,7 +772,7 @@ fn set_rxdmaen(regs: Regs, val: bool) {
fn finish_dma(regs: Regs) {
#[cfg(spi_v2)]
while regs.sr().read().ftlvl() > 0 {}
while regs.sr().read().ftlvl().to_bits() > 0 {}
#[cfg(any(spi_v3, spi_v4, spi_v5))]
while !regs.sr().read().txc() {}

View file

@ -869,7 +869,7 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, kind: Kind, enable_rx:
_ => vals::Ps::EVEN,
});
#[cfg(not(usart_v1))]
w.set_over8(vals::Over8(over8 as _));
w.set_over8(vals::Over8::from_bits(over8 as _));
});
#[cfg(not(usart_v1))]

View file

@ -97,8 +97,8 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
}
epr.set_dtog_rx(false);
epr.set_dtog_tx(false);
epr.set_stat_rx(Stat(0));
epr.set_stat_tx(Stat(0));
epr.set_stat_rx(Stat::from_bits(0));
epr.set_stat_tx(Stat::from_bits(0));
epr.set_ctr_rx(!epr.ctr_rx());
epr.set_ctr_tx(!epr.ctr_tx());
regs.epr(index).write_value(epr);
@ -143,8 +143,8 @@ fn invariant(mut r: regs::Epr) -> regs::Epr {
r.set_ctr_tx(true); // don't clear
r.set_dtog_rx(false); // don't toggle
r.set_dtog_tx(false); // don't toggle
r.set_stat_rx(Stat(0));
r.set_stat_tx(Stat(0));
r.set_stat_rx(Stat::from_bits(0));
r.set_stat_tx(Stat::from_bits(0));
r
}
@ -480,56 +480,57 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
poll_fn(move |cx| {
BUS_WAKER.register(cx.waker());
if self.inited {
let regs = T::regs();
if IRQ_RESUME.load(Ordering::Acquire) {
IRQ_RESUME.store(false, Ordering::Relaxed);
return Poll::Ready(Event::Resume);
}
if IRQ_RESET.load(Ordering::Acquire) {
IRQ_RESET.store(false, Ordering::Relaxed);
trace!("RESET");
regs.daddr().write(|w| {
w.set_ef(true);
w.set_add(0);
});
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat::NAK);
w.set_stat_tx(Stat::NAK);
});
for i in 1..EP_COUNT {
regs.epr(i).write(|w| {
w.set_ea(i as _);
w.set_ep_type(self.ep_types[i - 1]);
})
}
for w in &EP_IN_WAKERS {
w.wake()
}
for w in &EP_OUT_WAKERS {
w.wake()
}
return Poll::Ready(Event::Reset);
}
if IRQ_SUSPEND.load(Ordering::Acquire) {
IRQ_SUSPEND.store(false, Ordering::Relaxed);
return Poll::Ready(Event::Suspend);
}
Poll::Pending
} else {
// TODO: implement VBUS detection.
if !self.inited {
self.inited = true;
return Poll::Ready(Event::PowerDetected);
}
let regs = T::regs();
if IRQ_RESUME.load(Ordering::Acquire) {
IRQ_RESUME.store(false, Ordering::Relaxed);
return Poll::Ready(Event::Resume);
}
if IRQ_RESET.load(Ordering::Acquire) {
IRQ_RESET.store(false, Ordering::Relaxed);
trace!("RESET");
regs.daddr().write(|w| {
w.set_ef(true);
w.set_add(0);
});
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat::NAK);
w.set_stat_tx(Stat::NAK);
});
for i in 1..EP_COUNT {
regs.epr(i).write(|w| {
w.set_ea(i as _);
w.set_ep_type(self.ep_types[i - 1]);
})
}
for w in &EP_IN_WAKERS {
w.wake()
}
for w in &EP_OUT_WAKERS {
w.wake()
}
return Poll::Ready(Event::Reset);
}
if IRQ_SUSPEND.load(Ordering::Acquire) {
IRQ_SUSPEND.store(false, Ordering::Relaxed);
return Poll::Ready(Event::Suspend);
}
Poll::Pending
})
.await
}
@ -550,7 +551,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
true => Stat::STALL,
};
let mut w = invariant(r);
w.set_stat_tx(Stat(r.stat_tx().0 ^ want_stat.0));
w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits()));
reg.write_value(w);
}
}
@ -569,7 +570,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
true => Stat::STALL,
};
let mut w = invariant(r);
w.set_stat_rx(Stat(r.stat_rx().0 ^ want_stat.0));
w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits()));
reg.write_value(w);
}
}
@ -605,7 +606,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
break;
}
let mut w = invariant(r);
w.set_stat_tx(Stat(r.stat_tx().0 ^ want_stat.0));
w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits()));
reg.write_value(w);
}
EP_IN_WAKERS[ep_addr.index()].wake();
@ -621,7 +622,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
break;
}
let mut w = invariant(r);
w.set_stat_rx(Stat(r.stat_rx().0 ^ want_stat.0));
w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits()));
reg.write_value(w);
}
EP_OUT_WAKERS[ep_addr.index()].wake();
@ -762,8 +763,8 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
regs.epr(index).write(|w| {
w.set_ep_type(convert_type(self.info.ep_type));
w.set_ea(self.info.addr.index() as _);
w.set_stat_rx(Stat(Stat::NAK.0 ^ Stat::VALID.0));
w.set_stat_tx(Stat(0));
w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
w.set_stat_tx(Stat::from_bits(0));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
});
@ -804,8 +805,8 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
regs.epr(index).write(|w| {
w.set_ep_type(convert_type(self.info.ep_type));
w.set_ea(self.info.addr.index() as _);
w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0));
w.set_stat_rx(Stat(0));
w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
w.set_stat_rx(Stat::from_bits(0));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
});
@ -868,19 +869,19 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
let mut stat_tx = 0;
if first {
// change NAK -> VALID
stat_rx ^= Stat::NAK.0 ^ Stat::VALID.0;
stat_tx ^= Stat::NAK.0 ^ Stat::STALL.0;
stat_rx ^= Stat::NAK.to_bits() ^ Stat::VALID.to_bits();
stat_tx ^= Stat::NAK.to_bits() ^ Stat::STALL.to_bits();
}
if last {
// change STALL -> VALID
stat_tx ^= Stat::STALL.0 ^ Stat::NAK.0;
stat_tx ^= Stat::STALL.to_bits() ^ Stat::NAK.to_bits();
}
// Note: if this is the first AND last transfer, the above effectively
// changes stat_tx like NAK -> NAK, so noop.
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat(stat_rx));
w.set_stat_tx(Stat(stat_tx));
w.set_stat_rx(Stat::from_bits(stat_rx));
w.set_stat_tx(Stat::from_bits(stat_tx));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
});
@ -907,11 +908,11 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat(match last {
w.set_stat_rx(Stat::from_bits(match last {
// If last, set STAT_RX=STALL.
true => Stat::NAK.0 ^ Stat::STALL.0,
true => Stat::NAK.to_bits() ^ Stat::STALL.to_bits(),
// Otherwise, set STAT_RX=VALID, to allow the host to send the next packet.
false => Stat::NAK.0 ^ Stat::VALID.0,
false => Stat::NAK.to_bits() ^ Stat::VALID.to_bits(),
}));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
@ -936,17 +937,17 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
let mut stat_rx = 0;
if first {
// change NAK -> STALL
stat_rx ^= Stat::NAK.0 ^ Stat::STALL.0;
stat_rx ^= Stat::NAK.to_bits() ^ Stat::STALL.to_bits();
}
if last {
// change STALL -> VALID
stat_rx ^= Stat::STALL.0 ^ Stat::VALID.0;
stat_rx ^= Stat::STALL.to_bits() ^ Stat::VALID.to_bits();
}
// Note: if this is the first AND last transfer, the above effectively
// does a change of NAK -> VALID.
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat(stat_rx));
w.set_stat_rx(Stat::from_bits(stat_rx));
w.set_ep_kind(last); // set OUT_STATUS if last.
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
@ -976,7 +977,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
let regs = T::regs();
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0));
w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
w.set_ep_kind(last); // set OUT_STATUS if last.
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
@ -997,8 +998,8 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
let epr = regs.epr(0).read();
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0));
w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::VALID.0));
w.set_stat_rx(Stat::from_bits(epr.stat_rx().to_bits() ^ Stat::STALL.to_bits()));
w.set_stat_tx(Stat::from_bits(epr.stat_tx().to_bits() ^ Stat::VALID.to_bits()));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
});
@ -1028,8 +1029,8 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
let epr = regs.epr(0).read();
regs.epr(0).write(|w| {
w.set_ep_type(EpType::CONTROL);
w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0));
w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::STALL.0));
w.set_stat_rx(Stat::from_bits(epr.stat_rx().to_bits() ^ Stat::STALL.to_bits()));
w.set_stat_tx(Stat::from_bits(epr.stat_tx().to_bits() ^ Stat::STALL.to_bits()));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
});

View file

@ -6,8 +6,8 @@ use atomic_polyfill::{AtomicBool, AtomicU16, Ordering};
use embassy_hal_common::{into_ref, Peripheral};
use embassy_sync::waitqueue::AtomicWaker;
use embassy_usb_driver::{
self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, EndpointOut,
EndpointType, Event, Unsupported,
self, Bus as _, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo,
EndpointOut, EndpointType, Event, Unsupported,
};
use futures::future::poll_fn;
@ -31,7 +31,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
let state = T::state();
let ints = r.gintsts().read();
if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() {
if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() || ints.otgint() || ints.srqint() {
// Mask interrupts and notify `Bus` to process them
r.gintmsk().write(|_| {});
T::state().bus_waker.wake();
@ -97,7 +97,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
vals::Pktstsd::SETUP_DATA_DONE => {
trace!("SETUP_DATA_DONE ep={}", ep_num);
}
x => trace!("unknown PKTSTS: {}", x.0),
x => trace!("unknown PKTSTS: {}", x.to_bits()),
}
}
@ -124,7 +124,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
}
state.ep_in_wakers[ep_num].wake();
trace!("in ep={} irq val={:b}", ep_num, ep_ints.0);
trace!("in ep={} irq val={:08x}", ep_num, ep_ints.0);
}
ep_mask >>= 1;
@ -144,7 +144,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
// // clear all
// r.doepint(ep_num).write_value(ep_ints);
// state.ep_out_wakers[ep_num].wake();
// trace!("out ep={} irq val={=u32:b}", ep_num, ep_ints.0);
// trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0);
// }
// ep_mask >>= 1;
@ -256,7 +256,34 @@ struct EndpointData {
fifo_size_words: u16,
}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Config {
/// Enable VBUS detection.
///
/// The USB spec requires USB devices monitor for USB cable plug/unplug and react accordingly.
/// This is done by checkihg whether there is 5V on the VBUS pin or not.
///
/// If your device is bus-powered (powers itself from the USB host via VBUS), then this is optional.
/// (if there's no power in VBUS your device would be off anyway, so it's fine to always assume
/// there's power in VBUS, i.e. the USB cable is always plugged in.)
///
/// If your device is self-powered (i.e. it gets power from a source other than the USB cable, and
/// therefore can stay powered through USB cable plug/unplug) then you MUST set this to true.
///
/// If you set this to true, you must connect VBUS to PA9 for FS, PB13 for HS, possibly with a
/// voltage divider. See ST application note AN4879 and the reference manual for more details.
pub vbus_detection: bool,
}
impl Default for Config {
fn default() -> Self {
Self { vbus_detection: true }
}
}
pub struct Driver<'d, T: Instance> {
config: Config,
phantom: PhantomData<&'d mut T>,
ep_in: [Option<EndpointData>; MAX_EP_COUNT],
ep_out: [Option<EndpointData>; MAX_EP_COUNT],
@ -279,6 +306,7 @@ impl<'d, T: Instance> Driver<'d, T> {
dp: impl Peripheral<P = impl DpPin<T>> + 'd,
dm: impl Peripheral<P = impl DmPin<T>> + 'd,
ep_out_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(dp, dm);
@ -286,6 +314,7 @@ impl<'d, T: Instance> Driver<'d, T> {
dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
Self {
config,
phantom: PhantomData,
ep_in: [None; MAX_EP_COUNT],
ep_out: [None; MAX_EP_COUNT],
@ -318,6 +347,7 @@ impl<'d, T: Instance> Driver<'d, T> {
ulpi_d6: impl Peripheral<P = impl UlpiD6Pin<T>> + 'd,
ulpi_d7: impl Peripheral<P = impl UlpiD7Pin<T>> + 'd,
ep_out_buffer: &'d mut [u8],
config: Config,
) -> Self {
assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB");
@ -327,6 +357,7 @@ impl<'d, T: Instance> Driver<'d, T> {
);
Self {
config,
phantom: PhantomData,
ep_in: [None; MAX_EP_COUNT],
ep_out: [None; MAX_EP_COUNT],
@ -464,11 +495,12 @@ impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
(
Bus {
config: self.config,
phantom: PhantomData,
ep_in: self.ep_in,
ep_out: self.ep_out,
phy_type: self.phy_type,
enabled: false,
inited: false,
},
ControlPipe {
_phantom: PhantomData,
@ -481,11 +513,12 @@ impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
}
pub struct Bus<'d, T: Instance> {
config: Config,
phantom: PhantomData<&'d mut T>,
ep_in: [Option<EndpointData>; MAX_EP_COUNT],
ep_out: [Option<EndpointData>; MAX_EP_COUNT],
phy_type: PhyType,
enabled: bool,
inited: bool,
}
impl<'d, T: Instance> Bus<'d, T> {
@ -498,282 +531,14 @@ impl<'d, T: Instance> Bus<'d, T> {
w.set_iepint(true);
w.set_oepint(true);
w.set_rxflvlm(true);
w.set_srqim(true);
w.set_otgint(true);
});
}
}
impl<'d, T: Instance> Bus<'d, T> {
fn init_fifo(&mut self) {
trace!("init_fifo");
let r = T::regs();
// Configure RX fifo size. All endpoints share the same FIFO area.
let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out);
trace!("configuring rx fifo size={}", rx_fifo_size_words);
r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words));
// Configure TX (USB in direction) fifo size for each endpoint
let mut fifo_top = rx_fifo_size_words;
for i in 0..T::ENDPOINT_COUNT {
if let Some(ep) = self.ep_in[i] {
trace!(
"configuring tx fifo ep={}, offset={}, size={}",
i,
fifo_top,
ep.fifo_size_words
);
let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) };
dieptxf.write(|w| {
w.set_fd(ep.fifo_size_words);
w.set_sa(fifo_top);
});
fifo_top += ep.fifo_size_words;
}
}
assert!(
fifo_top <= T::FIFO_DEPTH_WORDS,
"FIFO allocations exceeded maximum capacity"
);
}
fn configure_endpoints(&mut self) {
trace!("configure_endpoints");
let r = T::regs();
// Configure IN endpoints
for (index, ep) in self.ep_in.iter().enumerate() {
if let Some(ep) = ep {
critical_section::with(|_| {
r.diepctl(index).write(|w| {
if index == 0 {
w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
} else {
w.set_mpsiz(ep.max_packet_size);
w.set_eptyp(to_eptyp(ep.ep_type));
w.set_sd0pid_sevnfrm(true);
}
});
});
}
}
// Configure OUT endpoints
for (index, ep) in self.ep_out.iter().enumerate() {
if let Some(ep) = ep {
critical_section::with(|_| {
r.doepctl(index).write(|w| {
if index == 0 {
w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
} else {
w.set_mpsiz(ep.max_packet_size);
w.set_eptyp(to_eptyp(ep.ep_type));
w.set_sd0pid_sevnfrm(true);
}
});
r.doeptsiz(index).modify(|w| {
w.set_xfrsiz(ep.max_packet_size as _);
if index == 0 {
w.set_rxdpid_stupcnt(1);
} else {
w.set_pktcnt(1);
}
});
});
}
}
// Enable IRQs for allocated endpoints
r.daintmsk().modify(|w| {
w.set_iepm(ep_irq_mask(&self.ep_in));
// OUT interrupts not used, handled in RXFLVL
// w.set_oepm(ep_irq_mask(&self.ep_out));
});
}
fn disable(&mut self) {
T::Interrupt::disable();
<T as RccPeripheral>::disable();
#[cfg(stm32l4)]
crate::pac::PWR.cr2().modify(|w| w.set_usv(false));
// Cannot disable PWR, because other peripherals might be using it
}
}
impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
async fn poll(&mut self) -> Event {
poll_fn(move |cx| {
// TODO: implement VBUS detection
if !self.enabled {
return Poll::Ready(Event::PowerDetected);
}
let r = T::regs();
T::state().bus_waker.register(cx.waker());
let ints = r.gintsts().read();
if ints.usbrst() {
trace!("reset");
self.init_fifo();
self.configure_endpoints();
// Reset address
critical_section::with(|_| {
r.dcfg().modify(|w| {
w.set_dad(0);
});
});
r.gintsts().write(|w| w.set_usbrst(true)); // clear
Self::restore_irqs();
}
if ints.enumdne() {
trace!("enumdne");
let speed = r.dsts().read().enumspd();
trace!(" speed={}", speed.0);
r.gusbcfg().modify(|w| {
w.set_trdt(calculate_trdt(speed, T::frequency()));
});
r.gintsts().write(|w| w.set_enumdne(true)); // clear
Self::restore_irqs();
return Poll::Ready(Event::Reset);
}
if ints.usbsusp() {
trace!("suspend");
r.gintsts().write(|w| w.set_usbsusp(true)); // clear
Self::restore_irqs();
return Poll::Ready(Event::Suspend);
}
if ints.wkupint() {
trace!("resume");
r.gintsts().write(|w| w.set_wkupint(true)); // clear
Self::restore_irqs();
return Poll::Ready(Event::Resume);
}
Poll::Pending
})
.await
}
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled);
assert!(
ep_addr.index() < T::ENDPOINT_COUNT,
"endpoint_set_stalled index {} out of range",
ep_addr.index()
);
let regs = T::regs();
match ep_addr.direction() {
Direction::Out => {
critical_section::with(|_| {
regs.doepctl(ep_addr.index()).modify(|w| {
w.set_stall(stalled);
});
});
T::state().ep_out_wakers[ep_addr.index()].wake();
}
Direction::In => {
critical_section::with(|_| {
regs.diepctl(ep_addr.index()).modify(|w| {
w.set_stall(stalled);
});
});
T::state().ep_in_wakers[ep_addr.index()].wake();
}
}
}
fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
assert!(
ep_addr.index() < T::ENDPOINT_COUNT,
"endpoint_is_stalled index {} out of range",
ep_addr.index()
);
let regs = T::regs();
match ep_addr.direction() {
Direction::Out => regs.doepctl(ep_addr.index()).read().stall(),
Direction::In => regs.diepctl(ep_addr.index()).read().stall(),
}
}
fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) {
trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled);
assert!(
ep_addr.index() < T::ENDPOINT_COUNT,
"endpoint_set_enabled index {} out of range",
ep_addr.index()
);
let r = T::regs();
match ep_addr.direction() {
Direction::Out => {
critical_section::with(|_| {
// cancel transfer if active
if !enabled && r.doepctl(ep_addr.index()).read().epena() {
r.doepctl(ep_addr.index()).modify(|w| {
w.set_snak(true);
w.set_epdis(true);
})
}
r.doepctl(ep_addr.index()).modify(|w| {
w.set_usbaep(enabled);
})
});
// Wake `Endpoint::wait_enabled()`
T::state().ep_out_wakers[ep_addr.index()].wake();
}
Direction::In => {
critical_section::with(|_| {
// cancel transfer if active
if !enabled && r.diepctl(ep_addr.index()).read().epena() {
r.diepctl(ep_addr.index()).modify(|w| {
w.set_snak(true);
w.set_epdis(true);
})
}
r.diepctl(ep_addr.index()).modify(|w| {
w.set_usbaep(enabled);
})
});
// Wake `Endpoint::wait_enabled()`
T::state().ep_in_wakers[ep_addr.index()].wake();
}
}
}
async fn enable(&mut self) {
trace!("enable");
fn init(&mut self) {
#[cfg(stm32l4)]
{
crate::peripherals::PWR::enable();
@ -908,9 +673,9 @@ impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
// F429-like chips have the GCCFG.NOVBUSSENS bit
r.gccfg_v1().modify(|w| {
w.set_novbussens(true);
w.set_novbussens(!self.config.vbus_detection);
w.set_vbusasen(false);
w.set_vbusbsen(false);
w.set_vbusbsen(self.config.vbus_detection);
w.set_sofouten(false);
});
}
@ -923,12 +688,12 @@ impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
});
r.gccfg_v2().modify(|w| {
w.set_vbden(false);
w.set_vbden(self.config.vbus_detection);
});
// Force B-peripheral session
r.gotgctl().modify(|w| {
w.set_bvaloen(true);
w.set_bvaloen(!self.config.vbus_detection);
w.set_bvaloval(true);
});
}
@ -960,16 +725,352 @@ impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
// Connect
r.dctl().write(|w| w.set_sdis(false));
}
self.enabled = true;
fn init_fifo(&mut self) {
trace!("init_fifo");
let r = T::regs();
// Configure RX fifo size. All endpoints share the same FIFO area.
let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out);
trace!("configuring rx fifo size={}", rx_fifo_size_words);
r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words));
// Configure TX (USB in direction) fifo size for each endpoint
let mut fifo_top = rx_fifo_size_words;
for i in 0..T::ENDPOINT_COUNT {
if let Some(ep) = self.ep_in[i] {
trace!(
"configuring tx fifo ep={}, offset={}, size={}",
i,
fifo_top,
ep.fifo_size_words
);
let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) };
dieptxf.write(|w| {
w.set_fd(ep.fifo_size_words);
w.set_sa(fifo_top);
});
fifo_top += ep.fifo_size_words;
}
}
assert!(
fifo_top <= T::FIFO_DEPTH_WORDS,
"FIFO allocations exceeded maximum capacity"
);
// Flush fifos
r.grstctl().write(|w| {
w.set_rxfflsh(true);
w.set_txfflsh(true);
w.set_txfnum(0x10);
});
loop {
let x = r.grstctl().read();
if !x.rxfflsh() && !x.txfflsh() {
break;
}
}
}
fn configure_endpoints(&mut self) {
trace!("configure_endpoints");
let r = T::regs();
// Configure IN endpoints
for (index, ep) in self.ep_in.iter().enumerate() {
if let Some(ep) = ep {
critical_section::with(|_| {
r.diepctl(index).write(|w| {
if index == 0 {
w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
} else {
w.set_mpsiz(ep.max_packet_size);
w.set_eptyp(to_eptyp(ep.ep_type));
w.set_sd0pid_sevnfrm(true);
w.set_txfnum(index as _);
w.set_snak(true);
}
});
});
}
}
// Configure OUT endpoints
for (index, ep) in self.ep_out.iter().enumerate() {
if let Some(ep) = ep {
critical_section::with(|_| {
r.doepctl(index).write(|w| {
if index == 0 {
w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
} else {
w.set_mpsiz(ep.max_packet_size);
w.set_eptyp(to_eptyp(ep.ep_type));
w.set_sd0pid_sevnfrm(true);
}
});
r.doeptsiz(index).modify(|w| {
w.set_xfrsiz(ep.max_packet_size as _);
if index == 0 {
w.set_rxdpid_stupcnt(1);
} else {
w.set_pktcnt(1);
}
});
});
}
}
// Enable IRQs for allocated endpoints
r.daintmsk().modify(|w| {
w.set_iepm(ep_irq_mask(&self.ep_in));
// OUT interrupts not used, handled in RXFLVL
// w.set_oepm(ep_irq_mask(&self.ep_out));
});
}
fn disable_all_endpoints(&mut self) {
for i in 0..T::ENDPOINT_COUNT {
self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::In), false);
self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::Out), false);
}
}
fn disable(&mut self) {
T::Interrupt::disable();
<T as RccPeripheral>::disable();
#[cfg(stm32l4)]
crate::pac::PWR.cr2().modify(|w| w.set_usv(false));
// Cannot disable PWR, because other peripherals might be using it
}
}
impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
async fn poll(&mut self) -> Event {
poll_fn(move |cx| {
if !self.inited {
self.init();
self.inited = true;
// If no vbus detection, just return a single PowerDetected event at startup.
if !self.config.vbus_detection {
return Poll::Ready(Event::PowerDetected);
}
}
let r = T::regs();
T::state().bus_waker.register(cx.waker());
let ints = r.gintsts().read();
if ints.srqint() {
trace!("vbus detected");
r.gintsts().write(|w| w.set_srqint(true)); // clear
Self::restore_irqs();
if self.config.vbus_detection {
return Poll::Ready(Event::PowerDetected);
}
}
if ints.otgint() {
let otgints = r.gotgint().read();
r.gotgint().write_value(otgints); // clear all
Self::restore_irqs();
if otgints.sedet() {
trace!("vbus removed");
if self.config.vbus_detection {
self.disable_all_endpoints();
return Poll::Ready(Event::PowerRemoved);
}
}
}
if ints.usbrst() {
trace!("reset");
self.init_fifo();
self.configure_endpoints();
// Reset address
critical_section::with(|_| {
r.dcfg().modify(|w| {
w.set_dad(0);
});
});
r.gintsts().write(|w| w.set_usbrst(true)); // clear
Self::restore_irqs();
}
if ints.enumdne() {
trace!("enumdne");
let speed = r.dsts().read().enumspd();
trace!(" speed={}", speed.to_bits());
r.gusbcfg().modify(|w| {
w.set_trdt(calculate_trdt(speed, T::frequency()));
});
r.gintsts().write(|w| w.set_enumdne(true)); // clear
Self::restore_irqs();
return Poll::Ready(Event::Reset);
}
if ints.usbsusp() {
trace!("suspend");
r.gintsts().write(|w| w.set_usbsusp(true)); // clear
Self::restore_irqs();
return Poll::Ready(Event::Suspend);
}
if ints.wkupint() {
trace!("resume");
r.gintsts().write(|w| w.set_wkupint(true)); // clear
Self::restore_irqs();
return Poll::Ready(Event::Resume);
}
Poll::Pending
})
.await
}
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled);
assert!(
ep_addr.index() < T::ENDPOINT_COUNT,
"endpoint_set_stalled index {} out of range",
ep_addr.index()
);
let regs = T::regs();
match ep_addr.direction() {
Direction::Out => {
critical_section::with(|_| {
regs.doepctl(ep_addr.index()).modify(|w| {
w.set_stall(stalled);
});
});
T::state().ep_out_wakers[ep_addr.index()].wake();
}
Direction::In => {
critical_section::with(|_| {
regs.diepctl(ep_addr.index()).modify(|w| {
w.set_stall(stalled);
});
});
T::state().ep_in_wakers[ep_addr.index()].wake();
}
}
}
fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
assert!(
ep_addr.index() < T::ENDPOINT_COUNT,
"endpoint_is_stalled index {} out of range",
ep_addr.index()
);
let regs = T::regs();
match ep_addr.direction() {
Direction::Out => regs.doepctl(ep_addr.index()).read().stall(),
Direction::In => regs.diepctl(ep_addr.index()).read().stall(),
}
}
fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) {
trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled);
assert!(
ep_addr.index() < T::ENDPOINT_COUNT,
"endpoint_set_enabled index {} out of range",
ep_addr.index()
);
let r = T::regs();
match ep_addr.direction() {
Direction::Out => {
critical_section::with(|_| {
// cancel transfer if active
if !enabled && r.doepctl(ep_addr.index()).read().epena() {
r.doepctl(ep_addr.index()).modify(|w| {
w.set_snak(true);
w.set_epdis(true);
})
}
r.doepctl(ep_addr.index()).modify(|w| {
w.set_usbaep(enabled);
});
// Flush tx fifo
r.grstctl().write(|w| {
w.set_txfflsh(true);
w.set_txfnum(ep_addr.index() as _);
});
loop {
let x = r.grstctl().read();
if !x.txfflsh() {
break;
}
}
});
// Wake `Endpoint::wait_enabled()`
T::state().ep_out_wakers[ep_addr.index()].wake();
}
Direction::In => {
critical_section::with(|_| {
// cancel transfer if active
if !enabled && r.diepctl(ep_addr.index()).read().epena() {
r.diepctl(ep_addr.index()).modify(|w| {
w.set_snak(true); // set NAK
w.set_epdis(true);
})
}
r.diepctl(ep_addr.index()).modify(|w| {
w.set_usbaep(enabled);
w.set_cnak(enabled); // clear NAK that might've been set by SNAK above.
})
});
// Wake `Endpoint::wait_enabled()`
T::state().ep_in_wakers[ep_addr.index()].wake();
}
}
}
async fn enable(&mut self) {
trace!("enable");
// TODO: enable the peripheral once enable/disable semantics are cleared up in embassy-usb
}
async fn disable(&mut self) {
trace!("disable");
Bus::disable(self);
self.enabled = false;
// TODO: disable the peripheral once enable/disable semantics are cleared up in embassy-usb
//Bus::disable(self);
}
async fn remote_wakeup(&mut self) -> Result<(), Unsupported> {
@ -1112,11 +1213,16 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
state.ep_in_wakers[index].register(cx.waker());
let diepctl = r.diepctl(index).read();
let dtxfsts = r.dtxfsts(index).read();
info!("diepctl {:08x} ftxfsts {:08x}", diepctl.0, dtxfsts.0);
if !diepctl.usbaep() {
trace!("write ep={:?} wait for prev: error disabled", self.info.addr);
Poll::Ready(Err(EndpointError::Disabled))
} else if !diepctl.epena() {
trace!("write ep={:?} wait for prev: ready", self.info.addr);
Poll::Ready(Ok(()))
} else {
trace!("write ep={:?} wait for prev: pending", self.info.addr);
Poll::Pending
}
})
@ -1141,6 +1247,7 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
Poll::Pending
} else {
trace!("write ep={:?} wait for fifo: ready", self.info.addr);
Poll::Ready(())
}
})

View file

@ -49,7 +49,7 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> {
let wdg = T::regs();
wdg.kr().write(|w| w.set_key(Key::ENABLE));
wdg.pr().write(|w| w.set_pr(Pr(pr)));
wdg.pr().write(|w| w.set_pr(Pr::from_bits(pr)));
wdg.rlr().write(|w| w.set_rl(rl));
trace!(

View file

@ -282,7 +282,7 @@ where
/// returns the amount of bytes written.
///
/// If it is not possible to write a nonzero amount of bytes because the pipe's buffer is full,
/// this method will wait until it is. See [`try_write`](Self::try_write) for a variant that
/// this method will wait until it isn't. See [`try_write`](Self::try_write) for a variant that
/// returns an error instead of waiting.
///
/// It is not guaranteed that all bytes in the buffer are written, even if there's enough
@ -319,7 +319,7 @@ where
/// returns the amount of bytes read.
///
/// If it is not possible to read a nonzero amount of bytes because the pipe's buffer is empty,
/// this method will wait until it is. See [`try_read`](Self::try_read) for a variant that
/// this method will wait until it isn't. See [`try_read`](Self::try_read) for a variant that
/// returns an error instead of waiting.
///
/// It is not guaranteed that all bytes in the buffer are read, even if there's enough

View file

@ -1,4 +1,5 @@
//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class.
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the CDC-NCM class.
use embassy_futures::select::{select, Either};
use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::LinkState;
@ -79,7 +80,7 @@ impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>;
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).
/// Obtain a driver for using the CDC-NCM class with [`embassy-net`](https://crates.io/crates/embassy-net).
pub fn into_embassy_net_device<const MTU: usize, const N_RX: usize, const N_TX: usize>(
self,
state: &'d mut State<MTU, N_RX, N_TX>,

View file

@ -11,8 +11,8 @@
//! - 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),
//! 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
//! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
//! 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>
use core::intrinsics::copy_nonoverlapping;
use core::mem::{size_of, MaybeUninit};

View file

@ -23,7 +23,7 @@ mod config {
use embassy_futures::select::{select, Either};
use heapless::Vec;
pub use crate::builder::{Builder, Config};
pub use crate::builder::{Builder, Config, FunctionBuilder, InterfaceAltBuilder, InterfaceBuilder};
use crate::config::*;
use crate::control::*;
use crate::descriptor::*;

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace nRF82840_xxAA with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip nRF52840_xxAA"
# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip nRF52840_xxAA"
[build]
target = "thumbv7em-none-eabi"

View file

@ -3,7 +3,7 @@ build-std = ["core"]
build-std-features = ["panic_immediate_abort"]
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs-cli run --chip RP2040"
runner = "probe-rs run --chip RP2040"
[build]
target = "thumbv6m-none-eabi"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace STM32F429ZITx with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32F303VCTx"
# replace STM32F429ZITx with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32F303VCTx"
[build]
target = "thumbv7em-none-eabihf"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace STM32F429ZITx with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32F767ZITx"
# replace STM32F429ZITx with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32F767ZITx"
[build]
target = "thumbv7em-none-eabihf"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace STM32F429ZITx with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32H743ZITx"
# replace STM32F429ZITx with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32H743ZITx"
[build]
target = "thumbv7em-none-eabihf"

View file

@ -1,5 +1,5 @@
#!/bin/bash
probe-rs-cli erase --chip STM32H743ZITx
probe-rs erase --chip STM32H743ZITx
mv ../../bootloader/stm32/memory.x ../../bootloader/stm32/memory-old.x
cp memory-bl.x ../../bootloader/stm32/memory.x

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32L072CZTx"
# replace your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32L072CZTx"
[build]
target = "thumbv6m-none-eabi"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32L151CBxxA"
# replace your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32L151CBxxA"
[build]
target = "thumbv7m-none-eabi"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32L475VG"
# replace your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32L475VG"
[build]
target = "thumbv7em-none-eabihf"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip STM32WLE5JCIx"
# replace your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32WLE5JCIx"
[build]
target = "thumbv7em-none-eabihf"

View file

@ -4,7 +4,7 @@ build-std-features = ["panic_immediate_abort"]
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
#runner = "./fruitrunner"
runner = "probe-rs-cli run --chip nrf52840_xxAA"
runner = "probe-rs run --chip nrf52840_xxAA"
rustflags = [
# Code-size optimizations.

View file

@ -1,5 +1,5 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs-cli run --chip RP2040"
runner = "probe-rs run --chip RP2040"
[build]
target = "thumbv6m-none-eabi"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace nRF82840_xxAA with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip nRF52840_xxAA"
# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip nRF52840_xxAA"
[build]
target = "thumbv7em-none-eabi"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace nRF82840_xxAA with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip nRF52840_xxAA"
# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip nRF52840_xxAA"
[build]
target = "thumbv7em-none-eabi"

View file

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace nRF82840_xxAA with your chip as listed in `probe-rs-cli chip list`
runner = "probe-rs-cli run --chip nRF52840_xxAA"
# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip nRF52840_xxAA"
[build]
target = "thumbv7em-none-eabi"

View file

@ -6,8 +6,24 @@ license = "MIT OR Apache-2.0"
[features]
default = ["nightly"]
nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-time/nightly", "embassy-time/unstable-traits", "static_cell/nightly",
"embassy-usb", "embedded-io/async", "embassy-net", "embassy-lora", "lora-phy", "lorawan-device", "lorawan"]
nightly = [
"embedded-hal-async",
"embassy-executor/nightly",
"embassy-nrf/nightly",
"embassy-net/nightly",
"embassy-net-esp-hosted",
"embassy-nrf/unstable-traits",
"embassy-time/nightly",
"embassy-time/unstable-traits",
"static_cell/nightly",
"embassy-usb",
"embedded-io/async",
"embassy-net",
"embassy-lora",
"lora-phy",
"lorawan-device",
"lorawan",
]
[dependencies]
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
@ -22,6 +38,7 @@ embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["ti
lora-phy = { version = "1", optional = true }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }
embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"], optional = true }
defmt = "0.3"
defmt-rtt = "0.4"
@ -35,3 +52,4 @@ rand = { version = "0.8.4", default-features = false }
embedded-storage = "0.3.0"
usbd-hid = "0.6.0"
serde = { version = "1.0.136", default-features = false }
embedded-hal-async = { version = "0.2.0-alpha.1", optional = true }

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