Merge branch 'main' into issue-1974-add-sai-driver

This commit is contained in:
Tyler 2023-09-29 20:02:24 -06:00 committed by GitHub
commit 2f9b59c5cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
304 changed files with 11896 additions and 5156 deletions

4
.github/bors.toml vendored
View File

@ -1,4 +0,0 @@
status = [
"all",
]
delete_merged_branches = true

View File

@ -8,6 +8,10 @@ export RUSTUP_HOME=/ci/cache/rustup
export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
# needed for "dumb HTTP" transport support
# used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json

5
.github/ci/build.sh vendored
View File

@ -11,8 +11,13 @@ if [ -f /ci/secrets/teleprobe-token.txt ]; then
echo Got teleprobe token!
export TELEPROBE_HOST=https://teleprobe.embassy.dev
export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt)
export TELEPROBE_CACHE=/ci/cache/teleprobe_cache.json
fi
# needed for "dumb HTTP" transport support
# used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json

3
.github/ci/doc.sh vendored
View File

@ -39,6 +39,7 @@ docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/g
docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup
docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup
docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static
docserver-builder -i ./embassy-net-adin1110 -o webroot/crates/embassy-net-adin1110/git.zup
export KUBECONFIG=/ci/secrets/kubeconfig.yml
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
@ -47,8 +48,8 @@ kubectl cp webroot/static $POD:/data
# build and upload stm32 last
# so that it doesn't prevent other crates from getting docs updates when it breaks.
rm -rf webroot
docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
kubectl cp webroot/crates $POD:/data

6
.github/ci/test.sh vendored
View File

@ -8,6 +8,10 @@ export RUSTUP_HOME=/ci/cache/rustup
export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
# needed for "dumb HTTP" transport support
# used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json
@ -28,3 +32,5 @@ cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --featu
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti
cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml

27
ci.sh
View File

@ -19,6 +19,19 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread,integrated-timers \
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
@ -72,6 +85,8 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f730i8,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h753zi,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h735zg,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \
@ -79,6 +94,7 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wb15cc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l072cz,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l073cz,defmt,exti,time-driver-any,unstable-traits,low-power \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f378cc,defmt,exti,time-driver-any,unstable-traits \
@ -131,9 +147,10 @@ cargo batch \
--- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32l5 \
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \
--- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \
@ -156,11 +173,17 @@ cargo batch \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --out-dir out/tests/stm32l4r5zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --out-dir out/tests/stm32l552ze \
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
$BUILD_EXTRA
rm out/tests/nrf52840-dk/wifi_esp_hosted_perf
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
echo No teleprobe token found, skipping running HIL tests

View File

@ -11,8 +11,8 @@ log = ["dep:log"]
firmware-logs = []
[dependencies]
embassy-time = { version = "0.1.2", path = "../embassy-time"}
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
embassy-time = { version = "0.1.3", path = "../embassy-time"}
embassy-sync = { version = "0.3.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"}
atomic-polyfill = "0.1.5"
@ -31,4 +31,4 @@ num_enum = { version = "0.5.7", default-features = false }
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/"
target = "thumbv6m-none-eabi"
features = ["defmt", "firmware-logs"]
features = ["defmt", "firmware-logs"]

View File

@ -96,6 +96,7 @@ pub(crate) const IOCTL_CMD_UP: u32 = 2;
pub(crate) const IOCTL_CMD_DOWN: u32 = 3;
pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26;
pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30;
pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52;
pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64;
pub(crate) const IOCTL_CMD_SET_AP: u32 = 118;
pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263;

View File

@ -124,7 +124,7 @@ impl<'a> Control<'a> {
Timer::after(Duration::from_millis(100)).await;
// set wifi up
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
self.up().await;
Timer::after(Duration::from_millis(100)).await;
@ -138,6 +138,16 @@ impl<'a> Control<'a> {
debug!("INIT DONE");
}
/// Set the WiFi interface up.
async fn up(&mut self) {
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
}
/// Set the interface down.
async fn down(&mut self) {
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
}
pub async fn set_power_management(&mut self, mode: PowerManagementMode) {
// power save mode
let mode_num = mode.mode();
@ -256,13 +266,13 @@ impl<'a> Control<'a> {
}
// Temporarily set wifi down
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
self.down().await;
// Turn off APSTA mode
self.set_iovar_u32("apsta", 0).await;
// Set wifi up again
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
self.up().await;
// Turn on AP mode
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await;
@ -423,6 +433,11 @@ impl<'a> Control<'a> {
events: &self.events,
}
}
/// Leave the wifi, with which we are currently associated.
pub async fn leave(&mut self) {
self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await;
info!("Disassociated")
}
}
pub struct Scanner<'a> {

View File

@ -83,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -226,7 +229,8 @@ impl<T, E> Try for Result<T, E> {
}
}
pub struct Bytes<'a>(pub &'a [u8]);
#[allow(unused)]
pub(crate) struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {

View File

@ -27,7 +27,7 @@ use ioctl::IoctlState;
use crate::bus::Bus;
pub use crate::bus::SpiBusCyw43;
pub use crate::control::{Control, Error as ControlError};
pub use crate::control::{Control, Error as ControlError, Scanner};
pub use crate::runner::Runner;
pub use crate::structs::BssInfo;

View File

@ -8,3 +8,5 @@
* xref:bootloader.adoc[Bootloader]
* xref:examples.adoc[Examples]
* xref:developer.adoc[Developer]
** xref:developer_stm32.adoc[Developer: STM32]

View File

@ -0,0 +1 @@
= Developer Documentation

View File

@ -0,0 +1,79 @@
= Developer Documentation: STM32
== Understanding metapac
When a project that imports `embassy-stm32` is compiled, that project selects the feature corresponding to the chip that project is using. Based on that feature, `embassy-stm32` selects supported link:https://anysilicon.com/ip-intellectual-property-core-semiconductors/[IP] for the chip, and enables the corresponding HAL implementations. But how does `embassy-stm32` know what IP the chip contains, out of the hundreds of chips that we support? It's a long story that starts with `stm32-data-sources`.
== `stm32-data-sources`
link:https://github.com/embassy-rs/stm32-data-sources[`stm32-data-sources`] is as mostly barren repository. It has no README, no documentation, and few watchers. But it's the core of what makes `embassy-stm32` possible. The data for every chip that we support is taken in part from a corresponding XML file like link:https://github.com/embassy-rs/stm32-data-sources/blob/b8b85202e22a954d6c59d4a43d9795d34cff05cf/cubedb/mcu/STM32F051K4Ux.xml[`STM32F051K4Ux.xml`]. In that file, you'll see lines like the following:
[source,xml]
----
<IP InstanceName="I2C1" Name="I2C" Version="i2c2_v1_1_Cube"/>
<!-- snip -->
<IP ConfigFile="TIM-STM32F0xx" InstanceName="TIM1" Name="TIM1_8F0" Version="gptimer2_v2_x_Cube"/>
----
These lines indicate that this chip has an i2c, and that it's version is "v1_1". It also indicates that it has a general purpose timer that with a version of "v2_x". From this data, it's possible to determine which implementations should be included in `embassy-stm32`. But actually doing that is another matter.
== `stm32-data`
While all users of this project are familiar with `embassy-stm32`, fewer are familiar with the project that powers it: `stm32-data`. This project doesn't just aim to generate data for `embassy-stm32`, but for machine consumption in general. To acheive this, information from multiple files from the `stm32-data-sources` project are combined and parsed to assign register block implementations for each supported IP. The core of this matching resides in `chips.rs`:
[source,rust]
----
(".*:I2C:i2c2_v1_1", ("i2c", "v2", "I2C")),
// snip
(r".*TIM\d.*:gptimer.*", ("timer", "v1", "TIM_GP16")),
----
In this case, the i2c version corresponds to our "v2" and the general purpose timer version corresponds to our "v1". Therefore, the `i2c_v2.yaml` and `timer_v1.yaml` register block implementations are assigned to those IP, respectively. The result is that these lines arr generated in `STM32F051K4.json`:
[source,json]
----
{
"name": "I2C1",
"address": 1073763328,
"registers": {
"kind": "i2c",
"version": "v2",
"block": "I2C"
},
// snip
}
// snip
{
"name": "TIM1",
"address": 1073818624,
"registers": {
"kind": "timer",
"version": "v1",
"block": "TIM_ADV"
},
// snip
}
----
In addition to register blocks, data for pin and RCC mapping is also generated and consumed by `embassy-stm32`. `stm32-metapac-gen` is used to package and publish the data as a crate.
== `embassy-stm32`
In the `lib.rs` file located in the root of `embassy-stm32`, you'll see this line:
[source,rust]
----
#[cfg(i2c)]
pub mod i2c;
----
And in the `mod.rs` of the i2c mod, you'll see this:
[source,rust]
----
#[cfg_attr(i2c_v2, path = "v2.rs")]
----
Because i2c is supported for STM32F051K4 and its version corresponds to our "v2", the `i2c` and `i2c_v2`, configuration directives will be present, and `embassy-stm32` will include these files, respectively. This and other configuration directives and tables are generated from the data for chip, allowing `embassy-stm32` to expressively and clearly adapt logic and implementations to what is required for each chip. Compared to other projects across the embedded ecosystem, `embassy-stm32` is the only project that can re-use code across the entire stm32 lineup and remove difficult-to-implement unsafe logic to the HAL.

View File

@ -4,14 +4,9 @@ Embassy is a project to make async/await a first-class option for embedded devel
== What is async?
Software written without async may block on I/O operations. In an std environment, such as a PC, software can handle this either by using threads or non-blocking operations.
When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task, known as a thread, can be executed if available, or the CPU can be put to sleep until another such task is ready to perform more work. Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted they do not transfer control back to the kernel within an allotted time. But if tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread. In Rust, these lightweight tasks, known as 'coroutines' or 'goroutines' in other languages, are implemented with async.
With threads, one thread blocks on an I/O operation, another is able to take its place. However, even on a PC, threads are relatively heavy, and therefore some programming languages, such as Go, have implemented a concept called coroutines or 'goroutines' that are much lighter and less-intensive than threads.
The other way to handle blocking I/O operations is to support polling the state of the underlying peripherals to check whether it is available to perform the requested operation. In programming languages without builtin async support,
this requires building a complex loop checking for events.
In Rust, non-blocking operations can be implemented using async-await. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute. Compared to alternatives such as an RTOS, async can yield better performance and lower power consumption because the executor doesn't have to guess when a future is ready to execute. However, program size may be higher than other alternatives, which may be a problem for certain space-constrained devices with very low memory. On the devices Embassy supports, such as stm32 and nrf, memory is generally large enough to accommodate the modestly-increased program size.
Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute. Compared to alternatives such as an RTOS, async can yield better performance and lower power consumption because the executor doesn't have to guess when a future is ready to execute. However, program size may be higher than other alternatives, which may be a problem for certain space-constrained devices with very low memory. On the devices Embassy supports, such as stm32 and nrf, memory is generally large enough to accommodate the modestly-increased program size.
== What is Embassy?

View File

@ -28,7 +28,7 @@ digest = "0.10"
log = { version = "0.4", optional = true }
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
embassy-sync = { version = "0.3.0", path = "../../embassy-sync" }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true }
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -20,8 +20,8 @@ default = ["time"]
[dependencies]
embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
"unproven",
] }

View File

@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.3.0 - TBD
## 0.3.0 - 2023-08-25
- Replaced Pender. Implementations now must define an extern function called `__pender`.
- Made `raw::AvailableTask` public
- Made `SpawnToken::new_failed` public
- You can now use arbitrary expressions to specify `#[task(pool_size = X)]`
## 0.2.1 - 2023-08-10

View File

@ -58,8 +58,8 @@ log = { version = "0.4.14", optional = true }
rtos-trace = { version = "0.1.2", optional = true }
futures-util = { version = "0.3.17", default-features = false }
embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true}
embassy-macros = { version = "0.2.1", path = "../embassy-macros" }
embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true}
atomic-polyfill = "1.0.1"
critical-section = "1.1"
static_cell = "1.1"

View File

@ -1,5 +1,3 @@
const THREAD_PENDER: usize = usize::MAX;
#[export_name = "__pender"]
#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
fn __pender(context: *mut ()) {
@ -48,13 +46,14 @@ fn __pender(context: *mut ()) {
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
pub(super) const THREAD_PENDER: usize = usize::MAX;
use core::arch::asm;
use core::marker::PhantomData;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main;
use crate::arch::THREAD_PENDER;
use crate::{raw, Spawner};
/// Thread mode executor, using WFE/SEV.

View File

@ -73,9 +73,10 @@ mod thread {
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
unsafe {
let executor = &self.inner;
self.ctx.closure.write(Closure::new(move |_| {
let future = Closure::new(move |_| {
executor.poll();
}));
});
self.ctx.closure.write_in_place(|| future);
init(self.inner.spawner());
}
}

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -203,7 +203,7 @@ impl<F: Future + 'static> AvailableTask<F> {
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
unsafe {
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
self.task.future.write(future());
self.task.future.write_in_place(future);
let task = TaskRef::new(self.task);

View File

@ -17,8 +17,9 @@ impl<T> UninitCell<T> {
&mut *self.as_mut_ptr()
}
pub unsafe fn write(&self, val: T) {
ptr::write(self.as_mut_ptr(), val)
#[inline(never)]
pub unsafe fn write_in_place(&self, func: impl FnOnce() -> T) {
ptr::write(self.as_mut_ptr(), func())
}
pub unsafe fn drop_in_place(&self) {

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -20,15 +20,12 @@ defmt = ["dep:defmt", "lorawan-device/defmt"]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
embedded-hal-async = { version = "=1.0.0-rc.1" }
embedded-hal = { version = "0.2", features = ["unproven"] }
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"], optional = true }
[patch.crates-io]
lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"}
lora-phy = { version = "2" }
lorawan-device = { version = "0.11.0", default-features = false, features = ["async"], optional = true }

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -67,24 +67,20 @@ where
self.board_type = board_type;
}
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
let pwr = pac::PWR;
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW));
pac::PWR.subghzspicr().modify(|w| w.set_nss(false));
Ok(())
}
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
let pwr = pac::PWR;
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH));
pac::PWR.subghzspicr().modify(|w| w.set_nss(true));
Ok(())
}
async fn reset(&mut self, _delay: &mut impl DelayUs) -> Result<(), RadioError> {
let rcc = pac::RCC;
rcc.csr().modify(|w| w.set_rfrst(true));
rcc.csr().modify(|w| w.set_rfrst(false));
pac::RCC.csr().modify(|w| w.set_rfrst(true));
pac::RCC.csr().modify(|w| w.set_rfrst(false));
Ok(())
}
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
let pwr = pac::PWR;
while pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY {}
while pac::PWR.sr2().read().rfbusys() {}
Ok(())
}
@ -284,11 +280,7 @@ where
self.busy.wait_for_low().await.map_err(|_| Busy)
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
if self.board_type != BoardType::RpPicoWaveshareSx1262 {
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
} else {
self.dio1.wait_for_rising_edge().await.map_err(|_| DIO1)?;
}
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
Ok(())
}

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-macros"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"

View File

@ -0,0 +1,42 @@
[package]
name = "embassy-net-adin1110"
version = "0.2.0"
description = "embassy-net driver for the ADIN1110 ethernet chip"
keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet", "async"]
categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
license = "MIT OR Apache-2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
heapless = "0.7.16"
defmt = { version = "0.3", optional = true }
log = { version = "0.4", default-features = false, optional = true }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" }
embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
embassy-time = { version = "0.1.3" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
bitfield = "0.14.0"
[dev-dependencies]
# reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged.
#embedded-hal-mock = { git = "https://github.com/dbrgn/embedded-hal-mock", branch = "1-alpha", features = ["embedded-hal-async", "eh1"] }] }
embedded-hal-mock = { git = "https://github.com/newAM/embedded-hal-mock", branch = "eh1-rc.1", features = ["embedded-hal-async", "eh1"] }
crc = "3.0.1"
env_logger = "0.10"
critical-section = { version = "1.1.2", features = ["std"] }
futures-test = "0.3.28"
[features]
default = [ ]
defmt = [ "dep:defmt", "embedded-hal-1/defmt-03" ]
log = ["dep:log"]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-adin1110/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]

View File

@ -0,0 +1,88 @@
# SPE ADIN1110 `embassy-net` integration
[`embassy-net`](https://crates.io/crates/embassy-net) integration for the `Analog ADIN1110` SPI SPE ethernet chips.
## What is SPE or Single Pair Ethernet / 10 BASE-T1L
SPE stands for Single Pair Ethernet. As the names implies, SPE uses differential signalling with 2 wires (a twisted-pair) in a cable as the physical medium.
SPE is full-duplex - it can transmit and receive ethernet packets at the same time. SPE is still ethernet, only the physical layer is different.
SPE also supports [`PoDL (Power over Data Line)`](https://www.ti.com/lit/an/snla395/snla395.pdf), power delivery from 0.5 up to 50 Watts, similar to [`PoE`](https://en.wikipedia.org/wiki/Power_over_Ethernet), but an additional hardware and handshake protocol are needed.
SPE has many link speeds but only `10 BASE-T1L` is able to reach cable lengths up to 1000 meters in `2.4 Vpp` transmit amplitude.
Currently in 2023, none of the standards are compatible with each other.
Thus `10 BASE-T1L` won't work with a `10 BASE-T1S`, `100 BASE-T1` or any standard `x BASE-T`.
In the industry SPE is also called [`APL (Advanced Physical Layer)`](https://www.ethernet-apl.org), and is based on the `10 BASE-T1L` standard.
APL can be used in [`intrinsic safety applications/explosion hazardous areas`](https://en.wikipedia.org/wiki/Electrical_equipment_in_hazardous_areas) which has its own name and standard called [`2-WISE (2-wire intrinsically safe ethernet) IEC TS 60079-47:2021`](https://webstore.iec.ch/publication/64292).
`10 BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and PDoL is not supported.
## Supported SPI modes
`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf)
Both modes support with and without additional CRC.
Currently only `Generic` SPI with or without CRC is supported.
*NOTE:* SPI Mode is selected by the hardware pins `SPI_CFG0` and `SPI_CFG1`. Software can't detect nor change the mode.
## Hardware
- Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) for an example.
- [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit (End Of Life)`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!**
## Other SPE chips
* [`Analog ADIN2111`](https://www.analog.com/en/products/adin2111.html) 2 Port SPI version. Can work with this driver.
* [`Analog ADIN1100`](https://www.analog.com/en/products/adin1100.html) RGMII version.
## Testing
ADIN1110 library can tested on the host with a mock SPI driver.
$ `cargo test --target x86_64-unknown-linux-gnu`
## Benchmark
- Benchmarked on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html), with [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) example.
Basic `ping` benchmark
```rust,ignore
# ping <IP> -c 60
60 packets transmitted, 60 received, 0% packet loss, time 59066ms
rtt min/avg/max/mdev = 1.089/1.161/1.237/0.018 ms
# ping <IP> -s 1472 -M do -c 60
60 packets transmitted, 60 received, 0% packet loss, time 59066ms
rtt min/avg/max/mdev = 5.122/5.162/6.177/0.133 ms
```
HTTP load generator benchmark with [`oha`](https://github.com/hatoo/oha)
```rust,ignore
# oha -c 1 http://<IP> -z 60s
Summary:
Success rate: 50.00%
Total: 60.0005 secs
Slowest: 0.0055 secs
Fastest: 0.0033 secs
Average: 0.0034 secs
Requests/sec: 362.1971
Total data: 2.99 MiB
Size/request: 289 B
Size/sec: 51.11 KiB
```
## 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

@ -0,0 +1,359 @@
pub const CRC32R_LOOKUP_TABLE: [u32; 256] = [
0x0000_0000,
0x7707_3096,
0xEE0E_612C,
0x9909_51BA,
0x076D_C419,
0x706A_F48F,
0xE963_A535,
0x9E64_95A3,
0x0EDB_8832,
0x79DC_B8A4,
0xE0D5_E91E,
0x97D2_D988,
0x09B6_4C2B,
0x7EB1_7CBD,
0xE7B8_2D07,
0x90BF_1D91,
0x1DB7_1064,
0x6AB0_20F2,
0xF3B9_7148,
0x84BE_41DE,
0x1ADA_D47D,
0x6DDD_E4EB,
0xF4D4_B551,
0x83D3_85C7,
0x136C_9856,
0x646B_A8C0,
0xFD62_F97A,
0x8A65_C9EC,
0x1401_5C4F,
0x6306_6CD9,
0xFA0F_3D63,
0x8D08_0DF5,
0x3B6E_20C8,
0x4C69_105E,
0xD560_41E4,
0xA267_7172,
0x3C03_E4D1,
0x4B04_D447,
0xD20D_85FD,
0xA50A_B56B,
0x35B5_A8FA,
0x42B2_986C,
0xDBBB_C9D6,
0xACBC_F940,
0x32D8_6CE3,
0x45DF_5C75,
0xDCD6_0DCF,
0xABD1_3D59,
0x26D9_30AC,
0x51DE_003A,
0xC8D7_5180,
0xBFD0_6116,
0x21B4_F4B5,
0x56B3_C423,
0xCFBA_9599,
0xB8BD_A50F,
0x2802_B89E,
0x5F05_8808,
0xC60C_D9B2,
0xB10B_E924,
0x2F6F_7C87,
0x5868_4C11,
0xC161_1DAB,
0xB666_2D3D,
0x76DC_4190,
0x01DB_7106,
0x98D2_20BC,
0xEFD5_102A,
0x71B1_8589,
0x06B6_B51F,
0x9FBF_E4A5,
0xE8B8_D433,
0x7807_C9A2,
0x0F00_F934,
0x9609_A88E,
0xE10E_9818,
0x7F6A_0DBB,
0x086D_3D2D,
0x9164_6C97,
0xE663_5C01,
0x6B6B_51F4,
0x1C6C_6162,
0x8565_30D8,
0xF262_004E,
0x6C06_95ED,
0x1B01_A57B,
0x8208_F4C1,
0xF50F_C457,
0x65B0_D9C6,
0x12B7_E950,
0x8BBE_B8EA,
0xFCB9_887C,
0x62DD_1DDF,
0x15DA_2D49,
0x8CD3_7CF3,
0xFBD4_4C65,
0x4DB2_6158,
0x3AB5_51CE,
0xA3BC_0074,
0xD4BB_30E2,
0x4ADF_A541,
0x3DD8_95D7,
0xA4D1_C46D,
0xD3D6_F4FB,
0x4369_E96A,
0x346E_D9FC,
0xAD67_8846,
0xDA60_B8D0,
0x4404_2D73,
0x3303_1DE5,
0xAA0A_4C5F,
0xDD0D_7CC9,
0x5005_713C,
0x2702_41AA,
0xBE0B_1010,
0xC90C_2086,
0x5768_B525,
0x206F_85B3,
0xB966_D409,
0xCE61_E49F,
0x5EDE_F90E,
0x29D9_C998,
0xB0D0_9822,
0xC7D7_A8B4,
0x59B3_3D17,
0x2EB4_0D81,
0xB7BD_5C3B,
0xC0BA_6CAD,
0xEDB8_8320,
0x9ABF_B3B6,
0x03B6_E20C,
0x74B1_D29A,
0xEAD5_4739,
0x9DD2_77AF,
0x04DB_2615,
0x73DC_1683,
0xE363_0B12,
0x9464_3B84,
0x0D6D_6A3E,
0x7A6A_5AA8,
0xE40E_CF0B,
0x9309_FF9D,
0x0A00_AE27,
0x7D07_9EB1,
0xF00F_9344,
0x8708_A3D2,
0x1E01_F268,
0x6906_C2FE,
0xF762_575D,
0x8065_67CB,
0x196C_3671,
0x6E6B_06E7,
0xFED4_1B76,
0x89D3_2BE0,
0x10DA_7A5A,
0x67DD_4ACC,
0xF9B9_DF6F,
0x8EBE_EFF9,
0x17B7_BE43,
0x60B0_8ED5,
0xD6D6_A3E8,
0xA1D1_937E,
0x38D8_C2C4,
0x4FDF_F252,
0xD1BB_67F1,
0xA6BC_5767,
0x3FB5_06DD,
0x48B2_364B,
0xD80D_2BDA,
0xAF0A_1B4C,
0x3603_4AF6,
0x4104_7A60,
0xDF60_EFC3,
0xA867_DF55,
0x316E_8EEF,
0x4669_BE79,
0xCB61_B38C,
0xBC66_831A,
0x256F_D2A0,
0x5268_E236,
0xCC0C_7795,
0xBB0B_4703,
0x2202_16B9,
0x5505_262F,
0xC5BA_3BBE,
0xB2BD_0B28,
0x2BB4_5A92,
0x5CB3_6A04,
0xC2D7_FFA7,
0xB5D0_CF31,
0x2CD9_9E8B,
0x5BDE_AE1D,
0x9B64_C2B0,
0xEC63_F226,
0x756A_A39C,
0x026D_930A,
0x9C09_06A9,
0xEB0E_363F,
0x7207_6785,
0x0500_5713,
0x95BF_4A82,
0xE2B8_7A14,
0x7BB1_2BAE,
0x0CB6_1B38,
0x92D2_8E9B,
0xE5D5_BE0D,
0x7CDC_EFB7,
0x0BDB_DF21,
0x86D3_D2D4,
0xF1D4_E242,
0x68DD_B3F8,
0x1FDA_836E,
0x81BE_16CD,
0xF6B9_265B,
0x6FB0_77E1,
0x18B7_4777,
0x8808_5AE6,
0xFF0F_6A70,
0x6606_3BCA,
0x1101_0B5C,
0x8F65_9EFF,
0xF862_AE69,
0x616B_FFD3,
0x166C_CF45,
0xA00A_E278,
0xD70D_D2EE,
0x4E04_8354,
0x3903_B3C2,
0xA767_2661,
0xD060_16F7,
0x4969_474D,
0x3E6E_77DB,
0xAED1_6A4A,
0xD9D6_5ADC,
0x40DF_0B66,
0x37D8_3BF0,
0xA9BC_AE53,
0xDEBB_9EC5,
0x47B2_CF7F,
0x30B5_FFE9,
0xBDBD_F21C,
0xCABA_C28A,
0x53B3_9330,
0x24B4_A3A6,
0xBAD0_3605,
0xCDD7_0693,
0x54DE_5729,
0x23D9_67BF,
0xB366_7A2E,
0xC461_4AB8,
0x5D68_1B02,
0x2A6F_2B94,
0xB40B_BE37,
0xC30C_8EA1,
0x5A05_DF1B,
0x2D02_EF8D,
];
/// Generate Ethernet Frame Check Sequence
#[allow(non_camel_case_types)]
#[derive(Debug)]
pub struct ETH_FCS(pub u32);
impl ETH_FCS {
pub const CRC32_OK: u32 = 0x2144_df1c;
#[must_use]
pub fn new(data: &[u8]) -> Self {
let fcs = data.iter().fold(u32::MAX, |crc, byte| {
let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte;
CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8)
}) ^ u32::MAX;
Self(fcs)
}
#[must_use]
pub fn update(self, data: &[u8]) -> Self {
let fcs = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| {
let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte;
CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8)
}) ^ u32::MAX;
Self(fcs)
}
#[must_use]
pub fn crc_ok(&self) -> bool {
self.0 == Self::CRC32_OK
}
#[must_use]
pub fn hton_bytes(&self) -> [u8; 4] {
self.0.to_le_bytes()
}
#[must_use]
pub fn hton(&self) -> u32 {
self.0.to_le()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc32_ethernet_frame() {
let packet_a = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xff, 0x06, 0x00, 0x01, 0x08, 0x00,
0x06, 0x04, 0x00, 0x01, 0x00, 0xe0, 0x4c, 0x68, 0x0e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xc0, 0xa8, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x65, 0x90, 0x3d,
];
let packet_b = &[
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00,
0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c,
];
// Packet A
let own_crc = ETH_FCS::new(&packet_a[0..60]);
let crc_bytes = own_crc.hton_bytes();
println!("{:08x} {:02x?}", own_crc.0, crc_bytes);
assert_eq!(&crc_bytes, &packet_a[60..64]);
let own_crc = ETH_FCS::new(packet_a);
println!("{:08x}", own_crc.0);
assert_eq!(own_crc.0, ETH_FCS::CRC32_OK);
// Packet B
let own_crc = ETH_FCS::new(&packet_b[0..60]);
let crc_bytes = own_crc.hton_bytes();
println!("{:08x} {:02x?}", own_crc.0, crc_bytes);
assert_eq!(&crc_bytes, &packet_b[60..64]);
let own_crc = ETH_FCS::new(packet_b);
println!("{:08x}", own_crc.0);
assert_eq!(own_crc.0, ETH_FCS::CRC32_OK);
}
#[test]
fn crc32_update() {
let full_data = &[
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00,
0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c,
];
let (part_a, part_b) = full_data.split_at(16);
let crc_partially = ETH_FCS::new(part_a).update(part_b);
let crc_full = ETH_FCS::new(full_data);
assert_eq!(crc_full.0, crc_partially.0);
}
}

View File

@ -0,0 +1,53 @@
/// CRC-8/ITU
const CRC8X_TABLE: [u8; 256] = [
0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e,
0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb,
0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8,
0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6,
0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d,
0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50,
0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95,
0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f,
0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a,
0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e,
0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7,
0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc,
0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3,
];
/// Calculate the crc of a pease of data.
pub fn crc8(data: &[u8]) -> u8 {
data.iter().fold(0, |crc, &byte| CRC8X_TABLE[usize::from(byte ^ crc)])
}
#[cfg(test)]
mod tests {
use ::crc::{Crc, CRC_8_SMBUS};
use super::crc8;
#[test]
fn spi_header_crc8() {
let data = &[0x80, 0x00];
let c = Crc::<u8>::new(&CRC_8_SMBUS);
let mut dig = c.digest();
dig.update(data);
let sw_crc = dig.finalize();
let own_crc = crc8(data);
assert_eq!(own_crc, sw_crc);
assert_eq!(own_crc, 182);
let data = &[0x80, 0x01];
let mut dig = c.digest();
dig.update(data);
let sw_crc = dig.finalize();
let own_crc = crc8(data);
assert_eq!(own_crc, sw_crc);
assert_eq!(own_crc, 177);
}
}

View File

@ -0,0 +1,254 @@
#![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)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::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 _ignored = ($( & $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 _ignored = ($( & $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 _ignored = ($( & $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 _ignored = ($( & $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 _ignored = ($( & $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)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,176 @@
/// PHY Address: (0..=0x1F), 5-bits long.
#[allow(dead_code)]
type PhyAddr = u8;
/// PHY Register: (0..=0x1F), 5-bits long.
#[allow(dead_code)]
type RegC22 = u8;
/// PHY Register Clause 45.
#[allow(dead_code)]
type RegC45 = u16;
/// PHY Register Value
#[allow(dead_code)]
type RegVal = u16;
#[allow(dead_code)]
const REG13: RegC22 = 13;
#[allow(dead_code)]
const REG14: RegC22 = 14;
#[allow(dead_code)]
const PHYADDR_MASK: u8 = 0x1f;
#[allow(dead_code)]
const DEV_MASK: u8 = 0x1f;
#[allow(dead_code)]
#[repr(u16)]
enum Reg13Op {
Addr = 0b00 << 14,
Write = 0b01 << 14,
PostReadIncAddr = 0b10 << 14,
Read = 0b11 << 14,
}
/// `MdioBus` trait
/// Driver needs to implement the Clause 22
/// Optional Clause 45 is the device supports this.
///
/// Clause 45 methodes are bases on <https://www.ieee802.org/3/efm/public/nov02/oam/pannell_oam_1_1102.pdf>
pub trait MdioBus {
type Error;
/// Read, Clause 22
async fn read_cl22(&mut self, phy_id: PhyAddr, reg: RegC22) -> Result<RegVal, Self::Error>;
/// Write, Clause 22
async fn write_cl22(&mut self, phy_id: PhyAddr, reg: RegC22, reg_val: RegVal) -> Result<(), Self::Error>;
/// Read, Clause 45
/// This is the default implementation.
/// Many hardware these days support direct Clause 45 operations.
/// Implement this function when your hardware supports it.
async fn read_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45)) -> Result<RegVal, Self::Error> {
// Write FN
let val = (Reg13Op::Addr as RegVal) | RegVal::from(regc45.0 & DEV_MASK);
self.write_cl22(phy_id, REG13, val).await?;
// Write Addr
self.write_cl22(phy_id, REG14, regc45.1).await?;
// Write FN
let val = (Reg13Op::Read as RegVal) | RegVal::from(regc45.0 & DEV_MASK);
self.write_cl22(phy_id, REG13, val).await?;
// Write Addr
self.read_cl22(phy_id, REG14).await
}
/// Write, Clause 45
/// This is the default implementation.
/// Many hardware these days support direct Clause 45 operations.
/// Implement this function when your hardware supports it.
async fn write_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45), reg_val: RegVal) -> Result<(), Self::Error> {
let dev_addr = RegVal::from(regc45.0 & DEV_MASK);
let reg = regc45.1;
// Write FN
let val = (Reg13Op::Addr as RegVal) | dev_addr;
self.write_cl22(phy_id, REG13, val).await?;
// Write Addr
self.write_cl22(phy_id, REG14, reg).await?;
// Write FN
let val = (Reg13Op::Write as RegVal) | dev_addr;
self.write_cl22(phy_id, REG13, val).await?;
// Write Addr
self.write_cl22(phy_id, REG14, reg_val).await
}
}
#[cfg(test)]
mod tests {
use core::convert::Infallible;
use super::{MdioBus, PhyAddr, RegC22, RegVal};
#[derive(Debug, PartialEq, Eq)]
enum A {
Read(PhyAddr, RegC22),
Write(PhyAddr, RegC22, RegVal),
}
struct MockMdioBus(Vec<A>);
impl MockMdioBus {
pub fn clear(&mut self) {
self.0.clear();
}
}
impl MdioBus for MockMdioBus {
type Error = Infallible;
async fn write_cl22(
&mut self,
phy_id: super::PhyAddr,
reg: super::RegC22,
reg_val: super::RegVal,
) -> Result<(), Self::Error> {
self.0.push(A::Write(phy_id, reg, reg_val));
Ok(())
}
async fn read_cl22(
&mut self,
phy_id: super::PhyAddr,
reg: super::RegC22,
) -> Result<super::RegVal, Self::Error> {
self.0.push(A::Read(phy_id, reg));
Ok(0)
}
}
#[futures_test::test]
async fn read_test() {
let mut mdiobus = MockMdioBus(Vec::with_capacity(20));
mdiobus.clear();
mdiobus.read_cl22(0x01, 0x00).await.unwrap();
assert_eq!(mdiobus.0, vec![A::Read(0x01, 0x00)]);
mdiobus.clear();
mdiobus.read_cl45(0x01, (0xBB, 0x1234)).await.unwrap();
assert_eq!(
mdiobus.0,
vec![
#[allow(clippy::identity_op)]
A::Write(0x01, 13, (0b00 << 14) | 27),
A::Write(0x01, 14, 0x1234),
A::Write(0x01, 13, (0b11 << 14) | 27),
A::Read(0x01, 14)
]
);
}
#[futures_test::test]
async fn write_test() {
let mut mdiobus = MockMdioBus(Vec::with_capacity(20));
mdiobus.clear();
mdiobus.write_cl22(0x01, 0x00, 0xABCD).await.unwrap();
assert_eq!(mdiobus.0, vec![A::Write(0x01, 0x00, 0xABCD)]);
mdiobus.clear();
mdiobus.write_cl45(0x01, (0xBB, 0x1234), 0xABCD).await.unwrap();
assert_eq!(
mdiobus.0,
vec![
A::Write(0x01, 13, 27),
A::Write(0x01, 14, 0x1234),
A::Write(0x01, 13, (0b01 << 14) | 27),
A::Write(0x01, 14, 0xABCD)
]
);
}
}

View File

@ -0,0 +1,143 @@
use crate::mdio::MdioBus;
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u8)]
/// Clause 22 Registers
pub enum RegsC22 {
/// MII Control Register
CONTROL = 0x00,
/// MII Status Register
STATUS = 0x01,
/// PHY Identifier 1 Register
PHY_ID1 = 0x02,
/// PHY Identifier 2 Register.
PHY_ID2 = 0x03,
}
/// Clause 45 Registers
#[allow(non_snake_case, dead_code)]
pub mod RegsC45 {
/// Device Address: 0x01
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u16)]
pub enum DA1 {
/// PMA/PMD Control 1 Register
PMA_PMD_CNTRL1 = 0x0000,
/// PMA/PMD Status 1 Register
PMA_PMD_STAT1 = 0x0001,
/// MSE Value Register
MSE_VAL = 0x830B,
}
impl DA1 {
#[must_use]
pub fn into(self) -> (u8, u16) {
(0x01, self as u16)
}
}
/// Device Address: 0x03
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u16)]
pub enum DA3 {
/// PCS Control 1 Register
PCS_CNTRL1 = 0x0000,
/// PCS Status 1 Register
PCS_STAT1 = 0x0001,
/// PCS Status 2 Register
PCS_STAT2 = 0x0008,
}
impl DA3 {
#[must_use]
pub fn into(self) -> (u8, u16) {
(0x03, self as u16)
}
}
/// Device Address: 0x07
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u16)]
pub enum DA7 {
/// Extra Autonegotiation Status Register
AN_STATUS_EXTRA = 0x8001,
}
impl DA7 {
#[must_use]
pub fn into(self) -> (u8, u16) {
(0x07, self as u16)
}
}
/// Device Address: 0x1E
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u16)]
pub enum DA1E {
/// System Interrupt Status Register
CRSM_IRQ_STATUS = 0x0010,
/// System Interrupt Mask Register
CRSM_IRQ_MASK = 0x0020,
/// Pin Mux Configuration 1 Register
DIGIO_PINMUX = 0x8c56,
/// LED Control Register.
LED_CNTRL = 0x8C82,
/// LED Polarity Register
LED_POLARITY = 0x8C83,
}
impl DA1E {
#[must_use]
pub fn into(self) -> (u8, u16) {
(0x1e, self as u16)
}
}
/// Device Address: 0x1F
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u16)]
pub enum DA1F {
/// PHY Subsystem Interrupt Status Register
PHY_SYBSYS_IRQ_STATUS = 0x0011,
/// PHY Subsystem Interrupt Mask Register
PHY_SYBSYS_IRQ_MASK = 0x0021,
}
impl DA1F {
#[must_use]
pub fn into(self) -> (u8, u16) {
(0x1f, self as u16)
}
}
}
/// 10-BASE-T1x PHY functions.
pub struct Phy10BaseT1x(u8);
impl Default for Phy10BaseT1x {
fn default() -> Self {
Self(0x01)
}
}
impl Phy10BaseT1x {
/// Get the both parts of the PHYID.
pub async fn get_id<MDIOBUS, MDE>(&self, mdiobus: &mut MDIOBUS) -> Result<u32, MDE>
where
MDIOBUS: MdioBus<Error = MDE>,
MDE: core::fmt::Debug,
{
let mut phyid = u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID1 as u8).await?) << 16;
phyid |= u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID2 as u8).await?);
Ok(phyid)
}
/// Get the Mean Squared Error Value.
pub async fn get_sqi<MDIOBUS, MDE>(&self, mdiobus: &mut MDIOBUS) -> Result<u16, MDE>
where
MDIOBUS: MdioBus<Error = MDE>,
MDE: core::fmt::Debug,
{
mdiobus.read_cl45(self.0, RegsC45::DA1::MSE_VAL.into()).await
}
}

View File

@ -0,0 +1,416 @@
use core::fmt::{Debug, Display};
use bitfield::{bitfield, bitfield_bitrange, bitfield_fields};
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u16)]
/// SPI REGISTER DETAILS
/// Table 38.
pub enum SpiRegisters {
IDVER = 0x00,
PHYID = 0x01,
CAPABILITY = 0x02,
RESET = 0x03,
CONFIG0 = 0x04,
CONFIG2 = 0x06,
STATUS0 = 0x08,
STATUS1 = 0x09,
IMASK0 = 0x0C,
IMASK1 = 0x0D,
MDIO_ACC = 0x20,
TX_FSIZE = 0x30,
TX = 0x31,
TX_SPACE = 0x32,
FIFO_CLR = 0x36,
ADDR_FILT_UPR0 = 0x50,
ADDR_FILT_LWR0 = 0x51,
ADDR_FILT_UPR1 = 0x52,
ADDR_FILT_LWR1 = 0x53,
ADDR_MSK_LWR0 = 0x70,
ADDR_MSK_UPR0 = 0x71,
ADDR_MSK_LWR1 = 0x72,
ADDR_MSK_UPR1 = 0x73,
RX_FSIZE = 0x90,
RX = 0x91,
}
impl Display for SpiRegisters {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<SpiRegisters> for u16 {
fn from(val: SpiRegisters) -> Self {
val as u16
}
}
impl From<u16> for SpiRegisters {
fn from(value: u16) -> Self {
match value {
0x00 => Self::IDVER,
0x01 => Self::PHYID,
0x02 => Self::CAPABILITY,
0x03 => Self::RESET,
0x04 => Self::CONFIG0,
0x06 => Self::CONFIG2,
0x08 => Self::STATUS0,
0x09 => Self::STATUS1,
0x0C => Self::IMASK0,
0x0D => Self::IMASK1,
0x20 => Self::MDIO_ACC,
0x30 => Self::TX_FSIZE,
0x31 => Self::TX,
0x32 => Self::TX_SPACE,
0x36 => Self::FIFO_CLR,
0x50 => Self::ADDR_FILT_UPR0,
0x51 => Self::ADDR_FILT_LWR0,
0x52 => Self::ADDR_FILT_UPR1,
0x53 => Self::ADDR_FILT_LWR1,
0x70 => Self::ADDR_MSK_LWR0,
0x71 => Self::ADDR_MSK_UPR0,
0x72 => Self::ADDR_MSK_LWR1,
0x73 => Self::ADDR_MSK_UPR1,
0x90 => Self::RX_FSIZE,
0x91 => Self::RX,
e => panic!("Unknown value {}", e),
}
}
}
// Register definitions
bitfield! {
/// Status0 Register bits
pub struct Status0(u32);
impl Debug;
u32;
/// Control Data Protection Error
pub cdpe, _ : 12;
/// Transmit Frame Check Squence Error
pub txfcse, _: 11;
/// Transmit Time Stamp Capture Available C
pub ttscac, _ : 10;
/// Transmit Time Stamp Capture Available B
pub ttscab, _ : 9;
/// Transmit Time Stamp Capture Available A
pub ttscaa, _ : 8;
/// PHY Interrupt for Port 1
pub phyint, _ : 7;
/// Reset Complete
pub resetc, _ : 6;
/// Header error
pub hdre, _ : 5;
/// Loss of Frame Error
pub lofe, _ : 4;
/// Receiver Buffer Overflow Error
pub rxboe, _ : 3;
/// Host Tx FIFO Under Run Error
pub txbue, _ : 2;
/// Host Tx FIFO Overflow
pub txboe, _ : 1;
/// Transmit Protocol Error
pub txpe, _ : 0;
}
bitfield! {
/// Status1 Register bits
pub struct Status1(u32);
impl Debug;
u32;
/// ECC Error on Reading the Frame Size from a Tx FIFO
pub tx_ecc_err, set_tx_ecc_err: 12;
/// ECC Error on Reading the Frame Size from an Rx FIFO
pub rx_ecc_err, set_rx_ecc_err : 11;
/// Detected an Error on an SPI Transaction
pub spi_err, set_spi_err: 10;
/// Rx MAC Interframe Gap Error
pub p1_rx_ifg_err, set_p1_rx_ifg_err : 8;
/// Port1 Rx Ready High Priority
pub p1_rx_rdy_hi, set_p1_rx_rdy_hi : 5;
/// Port 1 Rx FIFO Contains Data
pub p1_rx_rdy, set_p1_rx_rdy : 4;
/// Tx Ready
pub tx_rdy, set_tx_rdy : 3;
/// Link Status Changed
pub link_change, set_link_change : 1;
/// Port 1 Link Status
pub p1_link_status, _ : 0;
}
bitfield! {
/// Config0 Register bits
pub struct Config0(u32);
impl Debug;
u32;
/// Configuration Synchronization
pub sync, set_sync : 15;
/// Transmit Frame Check Sequence Validation Enable
pub txfcsve, set_txfcsve : 14;
/// !CS Align Receive Frame Enable
pub csarfe, set_csarfe : 13;
/// Zero Align Receive Frame Enable
pub zarfe, set_zarfe : 12;
/// Transmit Credit Threshold
pub tcxthresh, set_tcxthresh : 11, 10;
/// Transmit Cut Through Enable
pub txcte, set_txcte : 9;
/// Receive Cut Through Enable
pub rxcte, set_rxcte : 8;
/// Frame Time Stamp Enable
pub ftse, set_ftse : 7;
/// Receive Frame Time Stamp Select
pub ftss, set_ftss : 6;
/// Enable Control Data Read Write Protection
pub prote, set_prote : 5;
/// Enable TX Data Chunk Sequence and Retry
pub seqe, set_seqe : 4;
/// Chunk Payload Selector (N).
pub cps, set_cps : 2, 0;
}
bitfield! {
/// Config2 Register bits
pub struct Config2(u32);
impl Debug;
u32;
/// Assert TX_RDY When the Tx FIFO is Empty
pub tx_rdy_on_empty, set_tx_rdy_on_empty : 8;
/// Determines If the SFD is Detected in the PHY or MAC
pub sdf_detect_src, set_sdf_detect_src : 7;
/// Statistics Clear on Reading
pub stats_clr_on_rd, set_stats_clr_on_rd : 6;
/// Enable SPI CRC
pub crc_append, set_crc_append : 5;
/// Admit Frames with IFG Errors on Port 1 (P1)
pub p1_rcv_ifg_err_frm, set_p1_rcv_ifg_err_frm : 4;
/// Forward Frames Not Matching Any MAC Address to the Host
pub p1_fwd_unk2host, set_p1_fwd_unk2host : 2;
/// SPI to MDIO Bridge MDC Clock Speed
pub mspeed, set_mspeed : 0;
}
bitfield! {
/// IMASK0 Register bits
pub struct IMask0(u32);
impl Debug;
u32;
/// Control Data Protection Error Mask
pub cppem, set_cppem : 12;
/// Transmit Frame Check Sequence Error Mask
pub txfcsem, set_txfcsem : 11;
/// Transmit Time Stamp Capture Available C Mask
pub ttscacm, set_ttscacm : 10;
/// Transmit Time Stamp Capture Available B Mask
pub ttscabm, set_ttscabm : 9;
/// Transmit Time Stamp Capture Available A Mask
pub ttscaam, set_ttscaam : 8;
/// Physical Layer Interrupt Mask
pub phyintm, set_phyintm : 7;
/// RESET Complete Mask
pub resetcm, set_resetcm : 6;
/// Header Error Mask
pub hdrem, set_hdrem : 5;
/// Loss of Frame Error Mask
pub lofem, set_lofem : 4;
/// Receive Buffer Overflow Error Mask
pub rxboem, set_rxboem : 3;
/// Transmit Buffer Underflow Error Mask
pub txbuem, set_txbuem : 2;
/// Transmit Buffer Overflow Error Mask
pub txboem, set_txboem : 1;
/// Transmit Protocol Error Mask
pub txpem, set_txpem : 0;
}
bitfield! {
/// IMASK1 Register bits
pub struct IMask1(u32);
impl Debug;
u32;
/// Mask Bit for TXF_ECC_ERR
pub tx_ecc_err_mask, set_tx_ecc_err_mask : 12;
/// Mask Bit for RXF_ECC_ERR
pub rx_ecc_err_mask, set_rx_ecc_err_mask : 11;
/// Mask Bit for SPI_ERR
/// This field is only used with the generic SPI protocol
pub spi_err_mask, set_spi_err_mask : 10;
/// Mask Bit for RX_IFG_ERR
pub p1_rx_ifg_err_mask, set_p1_rx_ifg_err_mask : 8;
/// Mask Bit for P1_RX_RDY
/// This field is only used with the generic SPI protocol
pub p1_rx_rdy_mask, set_p1_rx_rdy_mask : 4;
/// Mask Bit for TX_FRM_DONE
/// This field is only used with the generic SPI protocol
pub tx_rdy_mask, set_tx_rdy_mask : 3;
/// Mask Bit for LINK_CHANGE
pub link_change_mask, set_link_change_mask : 1;
}
/// LED Functions
#[repr(u8)]
pub enum LedFunc {
LinkupTxRxActicity = 0,
LinkupTxActicity,
LinkupRxActicity,
LinkupOnly,
TxRxActivity,
TxActivity,
RxActivity,
LinkupRxEr,
LinkupRxTxEr,
RxEr,
RxTxEr,
TxSop,
RxSop,
On,
Off,
Blink,
TxLevel2P4,
TxLevel1P0,
Master,
Slave,
IncompatiableLinkCfg,
AnLinkGood,
AnComplete,
TsTimer,
LocRcvrStatus,
RemRcvrStatus,
Clk25Ref,
TxTCLK,
Clk120MHz,
}
impl From<LedFunc> for u8 {
fn from(val: LedFunc) -> Self {
val as u8
}
}
impl From<u8> for LedFunc {
fn from(value: u8) -> Self {
match value {
0 => LedFunc::LinkupTxRxActicity,
1 => LedFunc::LinkupTxActicity,
2 => LedFunc::LinkupRxActicity,
3 => LedFunc::LinkupOnly,
4 => LedFunc::TxRxActivity,
5 => LedFunc::TxActivity,
6 => LedFunc::RxActivity,
7 => LedFunc::LinkupRxEr,
8 => LedFunc::LinkupRxTxEr,
9 => LedFunc::RxEr,
10 => LedFunc::RxTxEr,
11 => LedFunc::TxSop,
12 => LedFunc::RxSop,
13 => LedFunc::On,
14 => LedFunc::Off,
15 => LedFunc::Blink,
16 => LedFunc::TxLevel2P4,
17 => LedFunc::TxLevel1P0,
18 => LedFunc::Master,
19 => LedFunc::Slave,
20 => LedFunc::IncompatiableLinkCfg,
21 => LedFunc::AnLinkGood,
22 => LedFunc::AnComplete,
23 => LedFunc::TsTimer,
24 => LedFunc::LocRcvrStatus,
25 => LedFunc::RemRcvrStatus,
26 => LedFunc::Clk25Ref,
27 => LedFunc::TxTCLK,
28 => LedFunc::Clk120MHz,
e => panic!("Invalid value {}", e),
}
}
}
/// LED Control Register
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct LedCntrl(pub u16);
bitfield_bitrange! {struct LedCntrl(u16)}
impl LedCntrl {
bitfield_fields! {
u8;
/// LED 0 Pin Function
pub from into LedFunc, led0_function, set_led0_function: 4, 0;
/// LED 0 Mode Selection
pub led0_mode, set_led0_mode: 5;
/// Qualify Certain LED 0 Options with Link Status.
pub led0_link_st_qualify, set_led0_link_st_qualify: 6;
/// LED 0 Enable
pub led0_en, set_led0_en: 7;
/// LED 1 Pin Function
pub from into LedFunc, led1_function, set_led1_function: 12, 8;
/// /// LED 1 Mode Selection
pub led1_mode, set_led1_mode: 13;
/// Qualify Certain LED 1 Options with Link Status.
pub led1_link_st_qualify, set_led1_link_st_qualify: 14;
/// LED 1 Enable
pub led1_en, set_led1_en: 15;
}
pub fn new() -> Self {
LedCntrl(0)
}
}
// LED Polarity
#[repr(u8)]
pub enum LedPol {
AutoSense = 0,
ActiveHigh,
ActiveLow,
}
impl From<LedPol> for u8 {
fn from(val: LedPol) -> Self {
val as u8
}
}
impl From<u8> for LedPol {
fn from(value: u8) -> Self {
match value {
0 => LedPol::AutoSense,
1 => LedPol::ActiveHigh,
2 => LedPol::ActiveLow,
e => panic!("Invalid value {}", e),
}
}
}
/// LED Control Register
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct LedPolarity(pub u16);
bitfield_bitrange! {struct LedPolarity(u16)}
impl LedPolarity {
bitfield_fields! {
u8;
/// LED 1 Polarity
pub from into LedPol, led1_polarity, set_led1_polarity: 3, 2;
/// LED 0 Polarity
pub from into LedPol, led0_polarity, set_led0_polarity: 1, 0;
}
}
/// SPI Header
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct SpiHeader(pub u16);
bitfield_bitrange! {struct SpiHeader(u16)}
impl SpiHeader {
bitfield_fields! {
u16;
/// Mask Bit for TXF_ECC_ERR
pub control, set_control : 15;
pub full_duplex, set_full_duplex : 14;
/// Read or Write to register
pub write, set_write : 13;
/// Registers ID/addr
pub from into SpiRegisters, addr, set_addr: 11, 0;
}
}

View File

@ -24,6 +24,6 @@ features = ["defmt"]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -8,11 +8,13 @@ use core::cell::RefCell;
use core::mem::MaybeUninit;
use core::task::{Context, Poll};
use driver::HardwareAddress;
pub use embassy_net_driver as driver;
use embassy_net_driver::{Capabilities, LinkState, Medium};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration;
use embassy_sync::zerocopy_channel;
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
rx: [PacketBuf<MTU>; N_RX],
@ -73,6 +75,18 @@ impl<'d, const MTU: usize> Runner<'d, MTU> {
)
}
pub fn borrow_split(&mut self) -> (StateRunner<'_>, RxRunner<'_, MTU>, TxRunner<'_, MTU>) {
(
StateRunner { shared: self.shared },
RxRunner {
rx_chan: self.rx_chan.borrow(),
},
TxRunner {
tx_chan: self.tx_chan.borrow(),
},
)
}
pub fn state_runner(&self) -> StateRunner<'d> {
StateRunner { shared: self.shared }
}
@ -117,24 +131,24 @@ impl<'d, const MTU: usize> Runner<'d, MTU> {
}
pub async fn tx_buf(&mut self) -> &mut [u8] {
let p = self.tx_chan.recv().await;
let p = self.tx_chan.receive().await;
&mut p.buf[..p.len]
}
pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.tx_chan.try_recv()?;
let p = self.tx_chan.try_receive()?;
Some(&mut p.buf[..p.len])
}
pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.tx_chan.poll_recv(cx) {
match self.tx_chan.poll_receive(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]),
Poll::Pending => Poll::Pending,
}
}
pub fn tx_done(&mut self) {
self.tx_chan.recv_done();
self.tx_chan.receive_done();
}
}
@ -191,24 +205,24 @@ impl<'d, const MTU: usize> RxRunner<'d, MTU> {
impl<'d, const MTU: usize> TxRunner<'d, MTU> {
pub async fn tx_buf(&mut self) -> &mut [u8] {
let p = self.tx_chan.recv().await;
let p = self.tx_chan.receive().await;
&mut p.buf[..p.len]
}
pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.tx_chan.try_recv()?;
let p = self.tx_chan.try_receive()?;
Some(&mut p.buf[..p.len])
}
pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.tx_chan.poll_recv(cx) {
match self.tx_chan.poll_receive(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]),
Poll::Pending => Poll::Pending,
}
}
pub fn tx_done(&mut self) {
self.tx_chan.recv_done();
self.tx_chan.receive_done();
}
}
@ -218,7 +232,11 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
) -> (Runner<'d, MTU>, Device<'d, MTU>) {
let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU;
caps.medium = Medium::Ethernet;
caps.medium = match &hardware_address {
HardwareAddress::Ethernet(_) => Medium::Ethernet,
HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
HardwareAddress::Ip => Medium::Ip,
};
// safety: this is a self-referential struct, however:
// - it can't move while the `'d` borrow is active.
@ -276,7 +294,7 @@ impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() {
if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() {
Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() }))
} else {
None
@ -320,9 +338,9 @@ impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> {
F: FnOnce(&mut [u8]) -> R,
{
// NOTE(unwrap): we checked the queue wasn't full when creating the token.
let pkt = unwrap!(self.rx.try_recv());
let pkt = unwrap!(self.rx.try_receive());
let r = f(&mut pkt.buf[..pkt.len]);
self.rx.recv_done();
self.rx.receive_done();
r
}
}
@ -344,215 +362,3 @@ impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> {
r
}
}
mod zerocopy_channel {
use core::cell::RefCell;
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::{Context, Poll};
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration;
pub struct Channel<'a, M: RawMutex, T> {
buf: *mut T,
phantom: PhantomData<&'a mut T>,
state: Mutex<M, RefCell<State>>,
}
impl<'a, M: RawMutex, T> Channel<'a, M, T> {
pub fn new(buf: &'a mut [T]) -> Self {
let len = buf.len();
assert!(len != 0);
Self {
buf: buf.as_mut_ptr(),
phantom: PhantomData,
state: Mutex::new(RefCell::new(State {
len,
front: 0,
back: 0,
full: false,
send_waker: WakerRegistration::new(),
recv_waker: WakerRegistration::new(),
})),
}
}
pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) {
(Sender { channel: self }, Receiver { channel: self })
}
}
pub struct Sender<'a, M: RawMutex, T> {
channel: &'a Channel<'a, M, T>,
}
impl<'a, M: RawMutex, T> Sender<'a, M, T> {
pub fn borrow(&mut self) -> Sender<'_, M, T> {
Sender { channel: self.channel }
}
pub fn try_send(&mut self) -> Option<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }),
None => None,
}
})
}
pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }),
None => {
s.recv_waker.register(cx.waker());
Poll::Pending
}
}
})
}
pub async fn send(&mut self) -> &mut T {
let i = poll_fn(|cx| {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Poll::Ready(i),
None => {
s.recv_waker.register(cx.waker());
Poll::Pending
}
}
})
})
.await;
unsafe { &mut *self.channel.buf.add(i) }
}
pub fn send_done(&mut self) {
self.channel.state.lock(|s| s.borrow_mut().push_done())
}
}
pub struct Receiver<'a, M: RawMutex, T> {
channel: &'a Channel<'a, M, T>,
}
impl<'a, M: RawMutex, T> Receiver<'a, M, T> {
pub fn borrow(&mut self) -> Receiver<'_, M, T> {
Receiver { channel: self.channel }
}
pub fn try_recv(&mut self) -> Option<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }),
None => None,
}
})
}
pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }),
None => {
s.send_waker.register(cx.waker());
Poll::Pending
}
}
})
}
pub async fn recv(&mut self) -> &mut T {
let i = poll_fn(|cx| {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Poll::Ready(i),
None => {
s.send_waker.register(cx.waker());
Poll::Pending
}
}
})
})
.await;
unsafe { &mut *self.channel.buf.add(i) }
}
pub fn recv_done(&mut self) {
self.channel.state.lock(|s| s.borrow_mut().pop_done())
}
}
struct State {
len: usize,
/// Front index. Always 0..=(N-1)
front: usize,
/// Back index. Always 0..=(N-1).
back: usize,
/// Used to distinguish "empty" and "full" cases when `front == back`.
/// May only be `true` if `front == back`, always `false` otherwise.
full: bool,
send_waker: WakerRegistration,
recv_waker: WakerRegistration,
}
impl State {
fn increment(&self, i: usize) -> usize {
if i + 1 == self.len {
0
} else {
i + 1
}
}
fn is_full(&self) -> bool {
self.full
}
fn is_empty(&self) -> bool {
self.front == self.back && !self.full
}
fn push_index(&mut self) -> Option<usize> {
match self.is_full() {
true => None,
false => Some(self.back),
}
}
fn push_done(&mut self) {
assert!(!self.is_full());
self.back = self.increment(self.back);
if self.back == self.front {
self.full = true;
}
self.send_waker.wake();
}
fn pop_index(&mut self) -> Option<usize> {
match self.is_empty() {
true => None,
false => Some(self.front),
}
}
fn pop_done(&mut self) {
assert!(!self.is_empty());
self.front = self.increment(self.front);
self.full = false;
self.recv_waker.wake();
}
}
}

View File

@ -11,7 +11,7 @@ edition = "2021"
embedded-hal = { version = "1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
embassy-time = { version = "0.1.2", path = "../embassy-time" }
embassy-time = { version = "0.1.3", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
defmt = { version = "0.3", optional = true }
@ -20,4 +20,4 @@ log = { version = "0.4.14", optional = true }
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-enc28j60-v$VERSION/embassy-net-enc28j60/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-enc28j60/src/"
target = "thumbv7em-none-eabi"
target = "thumbv7em-none-eabi"

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -194,10 +194,15 @@ where
self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen());
}
/// Returns the device's MAC address
pub fn address(&self) -> [u8; 6] {
self.mac_addr
}
/// Flushes the transmit buffer, ensuring all pending transmissions have completed
/// NOTE: The returned packet *must* be `read` or `ignore`-d, otherwise this method will always
/// return `None` on subsequent invocations
pub fn receive<'a>(&mut self, buf: &'a mut [u8]) -> Option<&'a mut [u8]> {
pub fn receive(&mut self, buf: &mut [u8]) -> Option<usize> {
if self.pending_packets() == 0 {
// Errata #6: we can't rely on PKTIF so we check PKTCNT
return None;
@ -241,7 +246,7 @@ where
self.next_packet = next_packet;
Some(&mut buf[..len as usize])
Some(len as usize)
}
fn wait_tx_ready(&mut self) {
@ -642,9 +647,8 @@ where
fn receive(&mut self, cx: &mut core::task::Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let rx_buf = unsafe { &mut RX_BUF };
let tx_buf = unsafe { &mut TX_BUF };
if let Some(pkt) = self.receive(rx_buf) {
let n = pkt.len();
Some((RxToken { buf: &mut pkt[..n] }, TxToken { buf: tx_buf, eth: self }))
if let Some(n) = self.receive(rx_buf) {
Some((RxToken { buf: &mut rx_buf[..n] }, TxToken { buf: tx_buf, eth: self }))
} else {
cx.waker().wake_by_ref();
None

View File

@ -7,8 +7,8 @@ edition = "2021"
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.1.2", path = "../embassy-time" }
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
embassy-time = { version = "0.1.3", path = "../embassy-time" }
embassy-sync = { version = "0.3.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"}
@ -23,4 +23,4 @@ heapless = "0.7.16"
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]
features = ["defmt"]

View File

@ -19,6 +19,8 @@ pub struct Control<'a> {
}
#[allow(unused)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum WifiMode {
None = 0,
Sta = 1,
@ -26,6 +28,18 @@ enum WifiMode {
ApSta = 3,
}
pub use proto::CtrlWifiSecProt as Security;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Status {
pub ssid: String<32>,
pub bssid: [u8; 6],
pub rssi: i32,
pub channel: u32,
pub security: Security,
}
macro_rules! ioctl {
($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => {
let mut msg = proto::CtrlMsg {
@ -34,7 +48,9 @@ macro_rules! ioctl {
payload: Some(proto::CtrlMsgPayload::$req_variant($req)),
};
$self.ioctl(&mut msg).await?;
let Some(proto::CtrlMsgPayload::$resp_variant($resp)) = msg.payload else {
#[allow(unused_mut)]
let Some(proto::CtrlMsgPayload::$resp_variant(mut $resp)) = msg.payload
else {
warn!("unexpected response variant");
return Err(Error::Internal);
};
@ -66,6 +82,19 @@ impl<'a> Control<'a> {
Ok(())
}
pub async fn get_status(&mut self) -> Result<Status, Error> {
let req = proto::CtrlMsgReqGetApConfig {};
ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp);
trim_nulls(&mut resp.ssid);
Ok(Status {
ssid: resp.ssid,
bssid: parse_mac(&resp.bssid)?,
rssi: resp.rssi as _,
channel: resp.chnl,
security: resp.sec_prot,
})
}
pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> {
let req = proto::CtrlMsgReqConnectAp {
ssid: String::from(ssid),
@ -98,27 +127,7 @@ impl<'a> Control<'a> {
mode: WifiMode::Sta as _,
};
ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp);
// 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];
if mac.len() != 17 {
warn!("unexpected MAC respnse length");
return Err(Error::Internal);
}
for (i, b) in res.iter_mut().enumerate() {
*b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1])
}
Ok(res)
parse_mac(&resp.mac)
}
async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> {
@ -167,3 +176,35 @@ impl<'a> Control<'a> {
Ok(())
}
}
// WHY IS THIS A STRING? WHYYYY
fn parse_mac(mac: &str) -> Result<[u8; 6], Error> {
fn nibble_from_hex(b: u8) -> Result<u8, Error> {
match b {
b'0'..=b'9' => Ok(b - b'0'),
b'a'..=b'f' => Ok(b + 0xa - b'a'),
b'A'..=b'F' => Ok(b + 0xa - b'A'),
_ => {
warn!("invalid hex digit {}", b);
Err(Error::Internal)
}
}
}
let mac = mac.as_bytes();
let mut res = [0; 6];
if mac.len() != 17 {
warn!("unexpected MAC length");
return Err(Error::Internal);
}
for (i, b) in res.iter_mut().enumerate() {
*b = (nibble_from_hex(mac[i * 3])? << 4) | nibble_from_hex(mac[i * 3 + 1])?
}
Ok(res)
}
fn trim_nulls<const N: usize>(s: &mut String<N>) {
while s.chars().rev().next() == Some(0 as char) {
s.pop();
}
}

View File

@ -93,7 +93,7 @@ macro_rules! unreachable {
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*);
::defmt::unreachable!($($x)*)
};
}
@ -229,7 +229,8 @@ impl<T, E> Try for Result<T, E> {
}
}
pub struct Bytes<'a>(pub &'a [u8]);
#[allow(unused)]
pub(crate) struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {

View File

@ -0,0 +1,28 @@
[package]
name = "embassy-net-ppp"
version = "0.1.0"
description = "embassy-net driver for PPP over Serial"
keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "ethernet", "async"]
categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
license = "MIT OR Apache-2.0"
edition = "2021"
[features]
defmt = ["dep:defmt", "ppproto/defmt"]
log = ["dep:log", "ppproto/log"]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embedded-io-async = { version = "0.5.0" }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
ppproto = { version = "0.1.2"}
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-ppp/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]

19
embassy-net-ppp/README.md Normal file
View File

@ -0,0 +1,19 @@
# `embassy-net-ppp`
[`embassy-net`](https://crates.io/crates/embassy-net) integration for PPP over Serial.
## Interoperability
This crate can run on any executor.
It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-async).
## 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.

258
embassy-net-ppp/src/fmt.rs Normal file
View File

@ -0,0 +1,258 @@
#![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
}
}
#[allow(unused)]
pub(crate) 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)
}
}

185
embassy-net-ppp/src/lib.rs Normal file
View File

@ -0,0 +1,185 @@
#![no_std]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
// must be first
mod fmt;
use core::convert::Infallible;
use core::mem::MaybeUninit;
use embassy_futures::select::{select, Either};
use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::LinkState;
use embedded_io_async::{BufRead, Write, WriteAllError};
use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction};
pub use ppproto::{Config, Ipv4Status};
const MTU: usize = 1500;
/// Type alias for the embassy-net driver.
pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
/// Internal state for the embassy-net integration.
pub struct State<const N_RX: usize, const N_TX: usize> {
ch_state: ch::State<MTU, N_RX, N_TX>,
}
impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
/// Create a new `State`.
pub const fn new() -> Self {
Self {
ch_state: ch::State::new(),
}
}
}
/// Background runner for the driver.
///
/// You must call `.run()` in a background task for the driver to operate.
pub struct Runner<'d> {
ch: ch::Runner<'d, MTU>,
}
/// Error returned by [`Runner::run`].
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RunError<E> {
/// Reading from the serial port failed.
Read(E),
/// Writing to the serial port failed.
Write(E),
/// Writing to the serial port wrote zero bytes, indicating it can't accept more data.
WriteZero,
/// Writing to the serial got EOF.
Eof,
/// PPP protocol was terminated by the peer
Terminated,
}
impl<E> From<WriteAllError<E>> for RunError<E> {
fn from(value: WriteAllError<E>) -> Self {
match value {
WriteAllError::Other(e) => Self::Write(e),
WriteAllError::WriteZero => Self::WriteZero,
}
}
}
impl<'d> Runner<'d> {
/// You must call this in a background task for the driver to operate.
///
/// If reading/writing to the underlying serial port fails, the link state
/// is set to Down and the error is returned.
///
/// It is allowed to cancel this function's future (i.e. drop it). This will terminate
/// the PPP connection and set the link state to Down.
///
/// After this function returns or is canceled, you can call it again to establish
/// a new PPP connection.
pub async fn run<RW: BufRead + Write>(
&mut self,
mut rw: RW,
config: ppproto::Config<'_>,
mut on_ipv4_up: impl FnMut(Ipv4Status),
) -> Result<Infallible, RunError<RW::Error>> {
let mut ppp = PPPoS::new(config);
ppp.open().unwrap();
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split();
state_chan.set_link_state(LinkState::Down);
let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down));
let mut rx_buf = [0; 2048];
let mut tx_buf = [0; 2048];
let mut needs_poll = true;
let mut was_up = false;
loop {
let rx_fut = async {
let buf = rx_chan.rx_buf().await;
let rx_data = match needs_poll {
true => &[][..],
false => match rw.fill_buf().await {
Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof),
Ok(rx_data) => rx_data,
Err(e) => return Err(RunError::Read(e)),
},
};
Ok((buf, rx_data))
};
let tx_fut = tx_chan.tx_buf();
match select(rx_fut, tx_fut).await {
Either::First(r) => {
needs_poll = false;
let (buf, rx_data) = r?;
let n = ppp.consume(rx_data, &mut rx_buf);
rw.consume(n);
match ppp.poll(&mut tx_buf, &mut rx_buf) {
PPPoSAction::None => {}
PPPoSAction::Received(rg) => {
let pkt = &rx_buf[rg];
buf[..pkt.len()].copy_from_slice(pkt);
rx_chan.rx_done(pkt.len());
}
PPPoSAction::Transmit(n) => rw.write_all(&tx_buf[..n]).await?,
}
let status = ppp.status();
match status.phase {
ppproto::Phase::Dead => {
return Err(RunError::Terminated);
}
ppproto::Phase::Open => {
if !was_up {
on_ipv4_up(status.ipv4.unwrap());
}
was_up = true;
state_chan.set_link_state(LinkState::Up);
}
_ => {
was_up = false;
state_chan.set_link_state(LinkState::Down);
}
}
}
Either::Second(pkt) => {
match ppp.send(pkt, &mut tx_buf) {
Ok(n) => rw.write_all(&tx_buf[..n]).await?,
Err(BufferFullError) => unreachable!(),
}
tx_chan.tx_done();
}
}
}
}
}
/// Create a PPP embassy-net driver instance.
///
/// This returns two structs:
/// - a `Device` that you must pass to the `embassy-net` stack.
/// - a `Runner`. You must call `.run()` on it in a background task.
pub fn new<'a, const N_RX: usize, const N_TX: usize>(state: &'a mut State<N_RX, N_TX>) -> (Device<'a>, Runner<'a>) {
let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip);
(device, Runner { ch: runner })
}
struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
fn new(f: F) -> Self {
Self { f: MaybeUninit::new(f) }
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}

View File

@ -11,7 +11,7 @@ edition = "2021"
embedded-hal = { version = "1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
embassy-time = { version = "0.1.2", path = "../embassy-time" }
embassy-time = { version = "0.1.3", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
defmt = { version = "0.3", optional = true }
@ -19,4 +19,4 @@ defmt = { version = "0.3", optional = true }
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-wiznet/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]
features = ["defmt"]

View File

@ -9,6 +9,7 @@ categories = [
"embedded",
"no-std",
"asynchronous",
"network-programming",
]
[package.metadata.embassy_docs]
@ -50,8 +51,8 @@ smoltcp = { version = "0.10.0", default-features = false, features = [
] }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
embassy-time = { version = "0.1.2", path = "../embassy-time" }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time" }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embedded-io-async = { version = "0.5.0", optional = true }
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }

View File

@ -22,13 +22,13 @@ where
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.inner
.receive(self.cx.as_deref_mut().unwrap())
.receive(unwrap!(self.cx.as_deref_mut()))
.map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx)))
}
/// Construct a transmit token.
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
self.inner.transmit(self.cx.as_deref_mut().unwrap()).map(TxTokenAdapter)
self.inner.transmit(unwrap!(self.cx.as_deref_mut())).map(TxTokenAdapter)
}
/// Get a description of device capabilities.

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -3,6 +3,9 @@
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
#[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))]
compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6");
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
@ -20,7 +23,7 @@ use core::future::{poll_fn, Future};
use core::task::{Context, Poll};
pub use embassy_net_driver as driver;
use embassy_net_driver::{Driver, LinkState, Medium};
use embassy_net_driver::{Driver, LinkState};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
use futures::pin_mut;
@ -133,6 +136,8 @@ impl Default for DhcpConfig {
}
/// Network stack configuration.
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Config {
/// IPv4 configuration
#[cfg(feature = "proto-ipv4")]
@ -163,10 +168,11 @@ impl Config {
}
}
/// IPv6 configuration with dynamic addressing.
/// IPv4 configuration with dynamic addressing.
///
/// # Example
/// ```rust
/// # use embassy_net::Config;
/// let _cfg = Config::dhcpv4(Default::default());
/// ```
#[cfg(feature = "dhcpv4")]
@ -181,23 +187,27 @@ impl Config {
/// Network stack IPv4 configuration.
#[cfg(feature = "proto-ipv4")]
#[derive(Debug, Clone, Default)]
pub enum ConfigV4 {
/// Do not configure IPv4.
#[default]
None,
/// Use a static IPv4 address configuration.
Static(StaticConfigV4),
/// Use DHCP to obtain an IP address configuration.
#[cfg(feature = "dhcpv4")]
Dhcp(DhcpConfig),
/// Do not configure IPv6.
None,
}
/// Network stack IPv6 configuration.
#[cfg(feature = "proto-ipv6")]
#[derive(Debug, Clone, Default)]
pub enum ConfigV6 {
/// Do not configure IPv6.
#[default]
None,
/// Use a static IPv6 address configuration.
Static(StaticConfigV6),
/// Do not configure IPv6.
None,
}
/// A network stack.
@ -217,6 +227,7 @@ struct Inner<D: Driver> {
static_v6: Option<StaticConfigV6>,
#[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>,
config_waker: WakerRegistration,
#[cfg(feature = "dns")]
dns_socket: SocketHandle,
#[cfg(feature = "dns")]
@ -240,11 +251,14 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress
driver::HardwareAddress::Ip => HardwareAddress::Ip,
#[allow(unreachable_patterns)]
_ => panic!("Unsupported address {:?}. Make sure to enable medium-ethernet or medium-ieee802154 in embassy-net's Cargo features.", addr),
_ => panic!(
"Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.",
addr
),
}
}
impl<D: Driver + 'static> Stack<D> {
impl<D: Driver> Stack<D> {
/// Create a new network stack.
pub fn new<const SOCK: usize>(
mut device: D,
@ -276,7 +290,6 @@ impl<D: Driver + 'static> Stack<D> {
next_local_port,
};
#[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
let mut inner = Inner {
device,
link_up: false,
@ -286,6 +299,7 @@ impl<D: Driver + 'static> Stack<D> {
static_v6: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
config_waker: WakerRegistration::new(),
#[cfg(feature = "dns")]
dns_socket: socket.sockets.add(dns::Socket::new(
&[],
@ -295,30 +309,11 @@ impl<D: Driver + 'static> Stack<D> {
dns_waker: WakerRegistration::new(),
};
#[cfg(feature = "medium-ieee802154")]
let _ = config;
#[cfg(feature = "proto-ipv4")]
match config.ipv4 {
ConfigV4::Static(config) => {
inner.apply_config_v4(&mut socket, config);
}
#[cfg(feature = "dhcpv4")]
ConfigV4::Dhcp(config) => {
let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new();
inner.apply_dhcp_config(&mut dhcp_socket, config);
let handle = socket.sockets.add(dhcp_socket);
inner.dhcp_socket = Some(handle);
}
ConfigV4::None => {}
}
inner.set_config_v4(&mut socket, config.ipv4);
#[cfg(feature = "proto-ipv6")]
match config.ipv6 {
ConfigV6::Static(config) => {
inner.apply_config_v6(&mut socket, config);
}
ConfigV6::None => {}
}
inner.set_config_v6(&mut socket, config.ipv6);
inner.apply_static_config(&mut socket);
Self {
socket: RefCell::new(socket),
@ -371,16 +366,86 @@ impl<D: Driver + 'static> Stack<D> {
v4_up || v6_up
}
/// Wait for the network stack to obtain a valid IP configuration.
///
/// ## Notes:
/// - Ensure [`Stack::run`] has been called before using this function.
///
/// - This function may never return (e.g. if no configuration is obtained through DHCP).
/// The caller is supposed to handle a timeout for this case.
///
/// ## Example
/// ```ignore
/// let config = embassy_net::Config::dhcpv4(Default::default());
///// Init network stack
/// let stack = &*make_static!(embassy_net::Stack::new(
/// device,
/// config,
/// make_static!(embassy_net::StackResources::<2>::new()),
/// seed
/// ));
/// // Launch network task that runs `stack.run().await`
/// spawner.spawn(net_task(stack)).unwrap();
/// // Wait for DHCP config
/// stack.wait_config_up().await;
/// // use the network stack
/// // ...
/// ```
pub async fn wait_config_up(&self) {
// If the config is up already, we can return immediately.
if self.is_config_up() {
return;
}
poll_fn(|cx| {
if self.is_config_up() {
Poll::Ready(())
} else {
// If the config is not up, we register a waker that is woken up
// when a config is applied (static or DHCP).
trace!("Waiting for config up");
self.with_mut(|_, i| {
i.config_waker.register(cx.waker());
});
Poll::Pending
}
})
.await;
}
/// Get the current IPv4 configuration.
///
/// If using DHCP, this will be None if DHCP hasn't been able to
/// acquire an IP address, or Some if it has.
#[cfg(feature = "proto-ipv4")]
pub fn config_v4(&self) -> Option<StaticConfigV4> {
self.with(|_s, i| i.static_v4.clone())
self.with(|_, i| i.static_v4.clone())
}
/// Get the current IPv6 configuration.
#[cfg(feature = "proto-ipv6")]
pub fn config_v6(&self) -> Option<StaticConfigV6> {
self.with(|_s, i| i.static_v6.clone())
self.with(|_, i| i.static_v6.clone())
}
/// Set the IPv4 configuration.
#[cfg(feature = "proto-ipv4")]
pub fn set_config_v4(&self, config: ConfigV4) {
self.with_mut(|s, i| {
i.set_config_v4(s, config);
i.apply_static_config(s);
})
}
/// Set the IPv6 configuration.
#[cfg(feature = "proto-ipv6")]
pub fn set_config_v6(&self, config: ConfigV6) {
self.with_mut(|s, i| {
i.set_config_v6(s, config);
i.apply_static_config(s);
})
}
/// Run the network stack.
@ -490,7 +555,7 @@ impl<D: Driver + 'static> Stack<D> {
}
#[cfg(feature = "igmp")]
impl<D: Driver + 'static> Stack<D> {
impl<D: Driver> Stack<D> {
/// Join a multicast group.
pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
where
@ -580,168 +645,129 @@ impl SocketStack {
}
}
impl<D: Driver + 'static> Inner<D> {
impl<D: Driver> Inner<D> {
#[cfg(feature = "proto-ipv4")]
fn apply_config_v4(&mut self, s: &mut SocketStack, config: StaticConfigV4) {
debug!("Acquired IP configuration:");
debug!(" IP address: {}", config.address);
s.iface.update_ip_addrs(|addrs| {
if let Some((index, _)) = addrs
.iter()
.enumerate()
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
{
addrs.remove(index);
}
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
});
#[cfg(feature = "medium-ip")]
let skip_gateway = self.device.capabilities().medium != Medium::Ip;
#[cfg(not(feature = "medium-ip"))]
let skip_gateway = false;
if !skip_gateway {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
} else {
debug!(" Default gateway: None");
s.iface.routes_mut().remove_default_ipv4_route();
}
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
self.static_v4 = Some(config);
#[cfg(feature = "dns")]
{
self.update_dns_servers(s)
}
}
/// Replaces the current IPv6 static configuration with a newly supplied config.
#[cfg(feature = "proto-ipv6")]
fn apply_config_v6(&mut self, s: &mut SocketStack, config: StaticConfigV6) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Acquired IPv6 configuration:");
debug!(" IP address: {}", config.address);
s.iface.update_ip_addrs(|addrs| {
if let Some((index, _)) = addrs
.iter()
.enumerate()
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv6(_)))
{
addrs.remove(index);
}
addrs.push(IpCidr::Ipv6(config.address)).unwrap();
});
#[cfg(feature = "medium-ethernet")]
if Medium::Ethernet == medium {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
s.iface.routes_mut().add_default_ipv6_route(gateway).unwrap();
} else {
debug!(" Default gateway: None");
s.iface.routes_mut().remove_default_ipv6_route();
}
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
self.static_v6 = Some(config);
#[cfg(feature = "dns")]
{
self.update_dns_servers(s)
}
}
#[cfg(feature = "dns")]
fn update_dns_servers(&mut self, s: &mut SocketStack) {
let socket = s.sockets.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket);
let servers_v4;
#[cfg(feature = "proto-ipv4")]
{
servers_v4 = self
.static_v4
.iter()
.flat_map(|cfg| cfg.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)));
pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) {
// Handle static config.
self.static_v4 = match config.clone() {
ConfigV4::None => None,
#[cfg(feature = "dhcpv4")]
ConfigV4::Dhcp(_) => None,
ConfigV4::Static(c) => Some(c),
};
#[cfg(not(feature = "proto-ipv4"))]
{
servers_v4 = core::iter::empty();
}
let servers_v6;
#[cfg(feature = "proto-ipv6")]
{
servers_v6 = self
.static_v6
.iter()
.flat_map(|cfg| cfg.dns_servers.iter().map(|c| IpAddress::Ipv6(*c)));
}
#[cfg(not(feature = "proto-ipv6"))]
{
servers_v6 = core::iter::empty();
}
// Handle DHCP config.
#[cfg(feature = "dhcpv4")]
match config {
ConfigV4::Dhcp(c) => {
// Create the socket if it doesn't exist.
if self.dhcp_socket.is_none() {
let socket = smoltcp::socket::dhcpv4::Socket::new();
let handle = _s.sockets.add(socket);
self.dhcp_socket = Some(handle);
}
// Prefer the v6 DNS servers over the v4 servers
let servers: Vec<IpAddress, 6> = servers_v6.chain(servers_v4).collect();
socket.update_servers(&servers[..]);
}
#[cfg(feature = "dhcpv4")]
fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) {
socket.set_ignore_naks(config.ignore_naks);
socket.set_max_lease_duration(config.max_lease_duration.map(crate::time::duration_to_smoltcp));
socket.set_ports(config.server_port, config.client_port);
socket.set_retry_config(config.retry_config);
}
#[cfg(feature = "dhcpv4")]
fn unapply_config_v4(&mut self, s: &mut SocketStack) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Lost IP configuration");
s.iface.update_ip_addrs(|ip_addrs| {
#[cfg(feature = "proto-ipv4")]
if let Some((index, _)) = ip_addrs
.iter()
.enumerate()
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
{
ip_addrs.remove(index);
// Configure it
let socket = _s.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
socket.set_ignore_naks(c.ignore_naks);
socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp));
socket.set_ports(c.server_port, c.client_port);
socket.set_retry_config(c.retry_config);
socket.reset();
}
});
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
#[cfg(feature = "proto-ipv4")]
{
s.iface.routes_mut().remove_default_ipv4_route();
_ => {
// Remove DHCP socket if any.
if let Some(socket) = self.dhcp_socket {
_s.sockets.remove(socket);
self.dhcp_socket = None;
}
}
}
}
#[cfg(feature = "proto-ipv6")]
pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) {
self.static_v6 = match config {
ConfigV6::None => None,
ConfigV6::Static(c) => Some(c),
};
}
fn apply_static_config(&mut self, s: &mut SocketStack) {
let mut addrs = Vec::new();
#[cfg(feature = "dns")]
let mut dns_servers: Vec<_, 6> = Vec::new();
#[cfg(feature = "proto-ipv4")]
{
self.static_v4 = None
let mut gateway_v4 = None;
#[cfg(feature = "proto-ipv6")]
let mut gateway_v6 = None;
#[cfg(feature = "proto-ipv4")]
if let Some(config) = &self.static_v4 {
debug!("IPv4: UP");
debug!(" IP address: {:?}", config.address);
debug!(" Default gateway: {:?}", config.gateway);
unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok());
gateway_v4 = config.gateway.into();
#[cfg(feature = "dns")]
for s in &config.dns_servers {
debug!(" DNS server: {:?}", s);
unwrap!(dns_servers.push(s.clone().into()).ok());
}
} else {
info!("IPv4: DOWN");
}
#[cfg(feature = "proto-ipv6")]
if let Some(config) = &self.static_v6 {
debug!("IPv6: UP");
debug!(" IP address: {:?}", config.address);
debug!(" Default gateway: {:?}", config.gateway);
unwrap!(addrs.push(IpCidr::Ipv6(config.address)).ok());
gateway_v6 = config.gateway.into();
#[cfg(feature = "dns")]
for s in &config.dns_servers {
debug!(" DNS server: {:?}", s);
unwrap!(dns_servers.push(s.clone().into()).ok());
}
} else {
info!("IPv6: DOWN");
}
// Apply addresses
s.iface.update_ip_addrs(|a| *a = addrs);
// Apply gateways
#[cfg(feature = "proto-ipv4")]
if let Some(gateway) = gateway_v4 {
unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway));
} else {
s.iface.routes_mut().remove_default_ipv4_route();
}
#[cfg(feature = "proto-ipv6")]
if let Some(gateway) = gateway_v6 {
unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway));
} else {
s.iface.routes_mut().remove_default_ipv6_route();
}
// Apply DNS servers
#[cfg(feature = "dns")]
s.sockets
.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket)
.update_servers(&dns_servers[..]);
self.config_waker.wake();
}
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
s.waker.register(cx.waker());
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
if self.device.capabilities().medium == Medium::Ethernet
|| self.device.capabilities().medium == Medium::Ieee802154
if self.device.capabilities().medium == embassy_net_driver::Medium::Ethernet
|| self.device.capabilities().medium == embassy_net_driver::Medium::Ieee802154
{
s.iface
.set_hardware_addr(to_smoltcp_hardware_address(self.device.hardware_address()));
@ -763,6 +789,9 @@ impl<D: Driver + 'static> Inner<D> {
info!("link_up = {:?}", self.link_up);
}
#[allow(unused_mut)]
let mut apply_config = false;
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
@ -770,25 +799,29 @@ impl<D: Driver + 'static> Inner<D> {
if self.link_up {
match socket.poll() {
None => {}
Some(dhcpv4::Event::Deconfigured) => self.unapply_config_v4(s),
Some(dhcpv4::Event::Deconfigured) => {
self.static_v4 = None;
apply_config = true;
}
Some(dhcpv4::Event::Configured(config)) => {
let config = StaticConfigV4 {
self.static_v4 = Some(StaticConfigV4 {
address: config.address,
gateway: config.router,
dns_servers: config.dns_servers,
};
self.apply_config_v4(s, config)
});
apply_config = true;
}
}
} else if old_link_up {
socket.reset();
self.unapply_config_v4(s);
self.static_v4 = None;
apply_config = true;
}
}
//if old_link_up || self.link_up {
// self.poll_configurator(timestamp)
//}
//
if apply_config {
self.apply_static_config(s);
}
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
let t = Timer::at(instant_from_smoltcp(poll_at));

View File

@ -440,7 +440,7 @@ impl<'d> TcpIo<'d> {
Poll::Ready(Err(Error::ConnectionReset))
}
} else {
Poll::Ready(match s.send(f.take().unwrap()) {
Poll::Ready(match s.send(unwrap!(f.take())) {
// Connection reset. TODO: this can also be timeouts etc, investigate.
Err(tcp::SendError::InvalidState) => Err(Error::ConnectionReset),
Ok(r) => Ok(r),
@ -468,7 +468,7 @@ impl<'d> TcpIo<'d> {
Poll::Ready(Err(Error::ConnectionReset))
}
} else {
Poll::Ready(match s.recv(f.take().unwrap()) {
Poll::Ready(match s.recv(unwrap!(f.take())) {
// Connection reset. TODO: this can also be timeouts etc, investigate.
Err(tcp::RecvError::Finished) | Err(tcp::RecvError::InvalidState) => {
Err(Error::ConnectionReset)

View File

@ -29,6 +29,8 @@ pub enum BindError {
pub enum Error {
/// No route to host.
NoRoute,
/// Socket not bound to an outgoing port.
SocketNotBound,
}
/// An UDP socket.
@ -155,7 +157,14 @@ impl<'a> UdpSocket<'a> {
s.register_send_waker(cx.waker());
Poll::Pending
}
Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)),
Err(udp::SendError::Unaddressable) => {
// If no sender/outgoing port is specified, there is not really "no route"
if s.endpoint().port == 0 {
Poll::Ready(Err(Error::SocketNotBound))
} else {
Poll::Ready(Err(Error::NoRoute))
}
}
})
}

View File

@ -51,7 +51,7 @@ nrf52805 = ["nrf52805-pac", "_nrf52"]
nrf52810 = ["nrf52810-pac", "_nrf52"]
nrf52811 = ["nrf52811-pac", "_nrf52"]
nrf52820 = ["nrf52820-pac", "_nrf52"]
nrf52832 = ["nrf52832-pac", "_nrf52"]
nrf52832 = ["nrf52832-pac", "_nrf52", "_nrf52832_anomaly_109"]
nrf52833 = ["nrf52833-pac", "_nrf52", "_gpio-p1"]
nrf52840 = ["nrf52840-pac", "_nrf52", "_gpio-p1"]
nrf5340-app-s = ["_nrf5340-app", "_s"]
@ -90,9 +90,12 @@ _ppi = []
_dppi = []
_gpio-p1 = []
# Errata workarounds
_nrf52832_anomaly_109 = []
[dependencies]
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true }

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -86,7 +86,6 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
unsafe { irq.enable() };
let g = regs();
g.events_port.write(|w| w);
g.intenset.write(|w| w.port().set());
}

View File

@ -102,6 +102,7 @@ mod chip;
#[macro_export]
macro_rules! bind_interrupts {
($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => {
#[derive(Copy, Clone)]
$vis struct $name;
$(

View File

@ -68,8 +68,14 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
let r = T::regs();
let s = T::state();
#[cfg(feature = "_nrf52832_anomaly_109")]
if r.events_started.read().bits() != 0 {
s.waker.wake();
r.intenclr.write(|w| w.started().clear());
}
if r.events_end.read().bits() != 0 {
s.end_waker.wake();
s.waker.wake();
r.intenclr.write(|w| w.end().clear());
}
}
@ -167,42 +173,10 @@ impl<'d, T: Instance> Spim<'d, T> {
// Enable SPIM instance.
r.enable.write(|w| w.enable().enabled());
// Configure mode.
let mode = config.mode;
r.config.write(|w| {
match mode {
MODE_0 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().leading();
}
MODE_1 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().trailing();
}
MODE_2 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().leading();
}
MODE_3 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().trailing();
}
}
let mut spim = Self { _p: spim };
w
});
// Configure frequency.
let frequency = config.frequency;
r.frequency.write(|w| w.frequency().variant(frequency));
// Set over-read character
let orc = config.orc;
r.orc.write(|w| unsafe { w.orc().bits(orc) });
// Apply runtime peripheral configuration
Self::set_config(&mut spim, &config);
// Disable all events interrupts
r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
@ -210,7 +184,7 @@ impl<'d, T: Instance> Spim<'d, T> {
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
Self { _p: spim }
spim
}
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
@ -223,14 +197,32 @@ impl<'d, T: Instance> Spim<'d, T> {
let r = T::regs();
// Set up the DMA write.
let (ptr, len) = slice_ptr_parts(tx);
let (ptr, tx_len) = slice_ptr_parts(tx);
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
// Set up the DMA read.
let (ptr, len) = slice_ptr_parts_mut(rx);
let (ptr, rx_len) = slice_ptr_parts_mut(rx);
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });
#[cfg(feature = "_nrf52832_anomaly_109")]
{
let s = T::state();
r.events_started.reset();
// Set rx/tx buffer lengths to 0...
r.txd.maxcnt.reset();
r.rxd.maxcnt.reset();
// ...and keep track of original buffer lengths...
s.tx.store(tx_len as _, Ordering::Relaxed);
s.rx.store(rx_len as _, Ordering::Relaxed);
// ...signalling the start of the fake transfer.
r.intenset.write(|w| w.started().bit(true));
}
// Reset and enable the event
r.events_end.reset();
@ -245,6 +237,9 @@ impl<'d, T: Instance> Spim<'d, T> {
fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
self.prepare(rx, tx)?;
#[cfg(feature = "_nrf52832_anomaly_109")]
while let Poll::Pending = self.nrf52832_dma_workaround_status() {}
// Wait for 'end' event.
while T::regs().events_end.read().bits() == 0 {}
@ -269,9 +264,19 @@ impl<'d, T: Instance> Spim<'d, T> {
async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
self.prepare(rx, tx)?;
#[cfg(feature = "_nrf52832_anomaly_109")]
poll_fn(|cx| {
let s = T::state();
s.waker.register(cx.waker());
self.nrf52832_dma_workaround_status()
})
.await;
// Wait for 'end' event.
poll_fn(|cx| {
T::state().end_waker.register(cx.waker());
T::state().waker.register(cx.waker());
if T::regs().events_end.read().bits() != 0 {
return Poll::Ready(());
}
@ -362,6 +367,32 @@ impl<'d, T: Instance> Spim<'d, T> {
pub async fn write_from_ram(&mut self, data: &[u8]) -> Result<(), Error> {
self.async_inner_from_ram(&mut [], data).await
}
#[cfg(feature = "_nrf52832_anomaly_109")]
fn nrf52832_dma_workaround_status(&mut self) -> Poll<()> {
let r = T::regs();
if r.events_started.read().bits() != 0 {
let s = T::state();
// Handle the first "fake" transmission
r.events_started.reset();
r.events_end.reset();
// Update DMA registers with correct rx/tx buffer sizes
r.rxd
.maxcnt
.write(|w| unsafe { w.maxcnt().bits(s.rx.load(Ordering::Relaxed)) });
r.txd
.maxcnt
.write(|w| unsafe { w.maxcnt().bits(s.tx.load(Ordering::Relaxed)) });
r.intenset.write(|w| w.end().set());
// ... and start actual, hopefully glitch-free transmission
r.tasks_start.write(|w| unsafe { w.bits(1) });
return Poll::Ready(());
}
Poll::Pending
}
}
impl<'d, T: Instance> Drop for Spim<'d, T> {
@ -386,18 +417,29 @@ impl<'d, T: Instance> Drop for Spim<'d, T> {
}
pub(crate) mod sealed {
#[cfg(feature = "_nrf52832_anomaly_109")]
use core::sync::atomic::AtomicU8;
use embassy_sync::waitqueue::AtomicWaker;
use super::*;
pub struct State {
pub end_waker: AtomicWaker,
pub waker: AtomicWaker,
#[cfg(feature = "_nrf52832_anomaly_109")]
pub rx: AtomicU8,
#[cfg(feature = "_nrf52832_anomaly_109")]
pub tx: AtomicU8,
}
impl State {
pub const fn new() -> Self {
Self {
end_waker: AtomicWaker::new(),
waker: AtomicWaker::new(),
#[cfg(feature = "_nrf52832_anomaly_109")]
rx: AtomicU8::new(0),
#[cfg(feature = "_nrf52832_anomaly_109")]
tx: AtomicU8::new(0),
}
}
}

View File

@ -169,47 +169,10 @@ impl<'d, T: Instance> Spis<'d, T> {
// Enable SPIS instance.
r.enable.write(|w| w.enable().enabled());
// Configure mode.
let mode = config.mode;
r.config.write(|w| {
match mode {
MODE_0 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().leading();
}
MODE_1 => {
w.order().msb_first();
w.cpol().active_high();
w.cpha().trailing();
}
MODE_2 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().leading();
}
MODE_3 => {
w.order().msb_first();
w.cpol().active_low();
w.cpha().trailing();
}
}
let mut spis = Self { _p: spis };
w
});
// Set over-read character.
let orc = config.orc;
r.orc.write(|w| unsafe { w.orc().bits(orc) });
// Set default character.
let def = config.def;
r.def.write(|w| unsafe { w.def().bits(def) });
// Configure auto-acquire on 'transfer end' event.
if config.auto_acquire {
r.shorts.write(|w| w.end_acquire().bit(true));
}
// Apply runtime peripheral configuration
Self::set_config(&mut spis, &config);
// Disable all events interrupts.
r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
@ -217,7 +180,7 @@ impl<'d, T: Instance> Spis<'d, T> {
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
Self { _p: spis }
spis
}
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {

View File

@ -57,7 +57,6 @@ impl<'d> Temp<'d> {
/// ```no_run
/// use embassy_nrf::{bind_interrupts, temp};
/// use embassy_nrf::temp::Temp;
/// use embassy_time::{Duration, Timer};
///
/// bind_interrupts!(struct Irqs {
/// TEMP => temp::InterruptHandler;

View File

@ -167,9 +167,10 @@ impl<'d, T: Instance> Twim<'d, T> {
// Enable TWIM instance.
r.enable.write(|w| w.enable().enabled());
// Configure frequency.
r.frequency
.write(|w| unsafe { w.frequency().bits(config.frequency as u32) });
let mut twim = Self { _p: twim };
// Apply runtime peripheral configuration
Self::set_config(&mut twim, &config);
// Disable all events interrupts
r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
@ -177,7 +178,7 @@ impl<'d, T: Instance> Twim<'d, T> {
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
Self { _p: twim }
twim
}
/// Set TX buffer, checking that it is in RAM and has suitable length.

View File

@ -59,8 +59,8 @@ nightly = ["embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "em
unstable-traits = ["embedded-hal-1", "embedded-hal-nb"]
[dependencies]
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.2", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
@ -89,7 +89,6 @@ embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional =
embedded-hal-async = { version = "=1.0.0-rc.1", optional = true}
embedded-hal-nb = { version = "=1.0.0-rc.1", optional = true}
paste = "1.0"
pio-proc = {version= "0.2" }
pio = {version= "0.2.1" }
rp2040-boot2 = "0.3"

View File

@ -94,6 +94,7 @@ impl ClockConfig {
post_div1: 6,
post_div2: 5,
}),
delay_multiplier: 128,
}),
ref_clk: RefClkConfig {
src: RefClkSrc::Xosc,
@ -203,6 +204,7 @@ pub struct XoscConfig {
pub hz: u32,
pub sys_pll: Option<PllConfig>,
pub usb_pll: Option<PllConfig>,
pub delay_multiplier: u32,
}
pub struct PllConfig {
@ -363,7 +365,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
// start XOSC
// datasheet mentions support for clock inputs into XIN, but doesn't go into
// how this is achieved. pico-sdk doesn't support this at all.
start_xosc(config.hz);
start_xosc(config.hz, config.delay_multiplier);
let pll_sys_freq = match config.sys_pll {
Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config),
@ -624,12 +626,12 @@ pub fn clk_rtc_freq() -> u16 {
CLOCKS.rtc.load(Ordering::Relaxed)
}
fn start_xosc(crystal_hz: u32) {
fn start_xosc(crystal_hz: u32, delay_multiplier: u32) {
pac::XOSC
.ctrl()
.write(|w| w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ));
let startup_delay = ((crystal_hz / 1000) + 128) / 256;
let startup_delay = (((crystal_hz / 1000) * delay_multiplier) + 128) / 256;
pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16));
pac::XOSC.ctrl().write(|w| {
w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ);

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -21,6 +21,8 @@ pub enum AbortReason {
NoAcknowledge,
/// The arbitration was lost, e.g. electrical problems with the clock signal
ArbitrationLoss,
/// Transmit ended with data still in fifo
TxNotEmpty(u16),
Other(u32),
}
@ -52,7 +54,7 @@ impl Default for Config {
}
}
const FIFO_SIZE: u8 = 16;
pub const FIFO_SIZE: u8 = 16;
pub struct I2c<'d, T: Instance, M: Mode> {
phantom: PhantomData<(&'d mut T, M)>,
@ -636,6 +638,7 @@ mod eh1 {
Self::Abort(AbortReason::NoAcknowledge) => {
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address)
}
Self::Abort(AbortReason::TxNotEmpty(_)) => embedded_hal_1::i2c::ErrorKind::Other,
Self::Abort(AbortReason::Other(_)) => embedded_hal_1::i2c::ErrorKind::Other,
Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other,
Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other,
@ -738,8 +741,8 @@ mod nightly {
}
}
fn i2c_reserved_addr(addr: u16) -> bool {
(addr & 0x78) == 0 || (addr & 0x78) == 0x78
pub fn i2c_reserved_addr(addr: u16) -> bool {
((addr & 0x78) == 0 || (addr & 0x78) == 0x78) && addr != 0
}
mod sealed {

338
embassy-rp/src/i2c_slave.rs Normal file
View File

@ -0,0 +1,338 @@
use core::future;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::into_ref;
use pac::i2c;
use crate::i2c::{i2c_reserved_addr, AbortReason, Instance, InterruptHandler, SclPin, SdaPin, FIFO_SIZE};
use crate::interrupt::typelevel::{Binding, Interrupt};
use crate::{pac, Peripheral};
/// I2C error
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// I2C abort with error
Abort(AbortReason),
/// User passed in a response buffer that was 0 length
InvalidResponseBufferLength,
}
/// Received command
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Command {
/// General Call
GeneralCall(usize),
/// Read
Read,
/// Write+read
WriteRead(usize),
/// Write
Write(usize),
}
/// Possible responses to responding to a read
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ReadStatus {
/// Transaction Complete, controller naked our last byte
Done,
/// Transaction Incomplete, controller trying to read more bytes than were provided
NeedMoreBytes,
/// Transaction Complere, but controller stopped reading bytes before we ran out
LeftoverBytes(u16),
}
/// Slave Configuration
#[non_exhaustive]
#[derive(Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Config {
/// Target Address
pub addr: u16,
}
impl Default for Config {
fn default() -> Self {
Self { addr: 0x55 }
}
}
pub struct I2cSlave<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
}
impl<'d, T: Instance> I2cSlave<'d, T> {
pub fn new(
_peri: impl Peripheral<P = T> + 'd,
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
_irq: impl Binding<T::Interrupt, InterruptHandler<T>>,
config: Config,
) -> Self {
into_ref!(_peri, scl, sda);
assert!(!i2c_reserved_addr(config.addr));
assert!(config.addr != 0);
let p = T::regs();
let reset = T::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
p.ic_enable().write(|w| w.set_enable(false));
p.ic_sar().write(|w| w.set_ic_sar(config.addr));
p.ic_con().modify(|w| {
w.set_master_mode(false);
w.set_ic_slave_disable(false);
w.set_tx_empty_ctrl(true);
});
// Set FIFO watermarks to 1 to make things simpler. This is encoded
// by a register value of 0. Rx watermark should never change, but Tx watermark will be
// adjusted in operation.
p.ic_tx_tl().write(|w| w.set_tx_tl(0));
p.ic_rx_tl().write(|w| w.set_rx_tl(0));
// Configure SCL & SDA pins
scl.gpio().ctrl().write(|w| w.set_funcsel(3));
sda.gpio().ctrl().write(|w| w.set_funcsel(3));
scl.pad_ctrl().write(|w| {
w.set_schmitt(true);
w.set_ie(true);
w.set_od(false);
w.set_pue(true);
w.set_pde(false);
});
sda.pad_ctrl().write(|w| {
w.set_schmitt(true);
w.set_ie(true);
w.set_od(false);
w.set_pue(true);
w.set_pde(false);
});
// Clear interrupts
p.ic_clr_intr().read();
// Enable I2C block
p.ic_enable().write(|w| w.set_enable(true));
// mask everything initially
p.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0));
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
Self { phantom: PhantomData }
}
/// Calls `f` to check if we are ready or not.
/// If not, `g` is called once the waker is set (to eg enable the required interrupts).
#[inline(always)]
async fn wait_on<F, U, G>(&mut self, mut f: F, mut g: G) -> U
where
F: FnMut(&mut Self) -> Poll<U>,
G: FnMut(&mut Self),
{
future::poll_fn(|cx| {
let r = f(self);
trace!("intr p: {:013b}", T::regs().ic_raw_intr_stat().read().0);
if r.is_pending() {
T::waker().register(cx.waker());
g(self);
}
r
})
.await
}
#[inline(always)]
fn drain_fifo(&mut self, buffer: &mut [u8], offset: usize) -> usize {
let p = T::regs();
let len = p.ic_rxflr().read().rxflr() as usize;
let end = offset + len;
for i in offset..end {
buffer[i] = p.ic_data_cmd().read().dat();
}
end
}
#[inline(always)]
fn write_to_fifo(&mut self, buffer: &[u8]) {
let p = T::regs();
for byte in buffer {
p.ic_data_cmd().write(|w| w.set_dat(*byte));
}
}
/// Wait asynchronously for commands from an I2C master.
/// `buffer` is provided in case master does a 'write' and is unused for 'read'.
pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> {
let p = T::regs();
p.ic_clr_intr().read();
// set rx fifo watermark to 1 byte
p.ic_rx_tl().write(|w| w.set_rx_tl(0));
let mut len = 0;
let ret = self
.wait_on(
|me| {
let stat = p.ic_raw_intr_stat().read();
if p.ic_rxflr().read().rxflr() > 0 {
len = me.drain_fifo(buffer, len);
// we're recieving data, set rx fifo watermark to 12 bytes to reduce interrupt noise
p.ic_rx_tl().write(|w| w.set_rx_tl(11));
}
if stat.restart_det() && stat.rd_req() {
Poll::Ready(Ok(Command::WriteRead(len)))
} else if stat.gen_call() && stat.stop_det() && len > 0 {
Poll::Ready(Ok(Command::GeneralCall(len)))
} else if stat.stop_det() {
Poll::Ready(Ok(Command::Write(len)))
} else if stat.rd_req() {
Poll::Ready(Ok(Command::Read))
} else {
Poll::Pending
}
},
|_me| {
p.ic_intr_mask().modify(|w| {
w.set_m_stop_det(true);
w.set_m_restart_det(true);
w.set_m_gen_call(true);
w.set_m_rd_req(true);
w.set_m_rx_full(true);
});
},
)
.await;
p.ic_clr_intr().read();
ret
}
/// Respond to an I2C master READ command, asynchronously.
pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result<ReadStatus, Error> {
let p = T::regs();
if buffer.len() == 0 {
return Err(Error::InvalidResponseBufferLength);
}
let mut chunks = buffer.chunks(FIFO_SIZE as usize);
let ret = self
.wait_on(
|me| {
if let Err(abort_reason) = me.read_and_clear_abort_reason() {
if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason {
return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes)));
} else {
return Poll::Ready(Err(abort_reason));
}
}
if let Some(chunk) = chunks.next() {
me.write_to_fifo(chunk);
Poll::Pending
} else {
let stat = p.ic_raw_intr_stat().read();
if stat.rx_done() && stat.stop_det() {
Poll::Ready(Ok(ReadStatus::Done))
} else if stat.rd_req() {
Poll::Ready(Ok(ReadStatus::NeedMoreBytes))
} else {
Poll::Pending
}
}
},
|_me| {
p.ic_intr_mask().modify(|w| {
w.set_m_stop_det(true);
w.set_m_rx_done(true);
w.set_m_tx_empty(true);
w.set_m_tx_abrt(true);
})
},
)
.await;
p.ic_clr_intr().read();
ret
}
/// Respond to reads with the fill byte until the controller stops asking
pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> {
loop {
match self.respond_to_read(&[fill]).await {
Ok(ReadStatus::NeedMoreBytes) => (),
Ok(_) => break Ok(()),
Err(e) => break Err(e),
}
}
}
/// Respond to a master read, then fill any remaining read bytes with `fill`
pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result<ReadStatus, Error> {
let resp_stat = self.respond_to_read(buffer).await?;
if resp_stat == ReadStatus::NeedMoreBytes {
self.respond_till_stop(fill).await?;
Ok(ReadStatus::Done)
} else {
Ok(resp_stat)
}
}
#[inline(always)]
fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> {
let p = T::regs();
let mut abort_reason = p.ic_tx_abrt_source().read();
// Mask off fifo flush count
let tx_flush_cnt = abort_reason.tx_flush_cnt();
abort_reason.set_tx_flush_cnt(0);
// Mask off master_dis
abort_reason.set_abrt_master_dis(false);
if abort_reason.0 != 0 {
// Note clearing the abort flag also clears the reason, and this
// instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
p.ic_clr_tx_abrt().read();
let reason = if abort_reason.abrt_7b_addr_noack()
| abort_reason.abrt_10addr1_noack()
| abort_reason.abrt_10addr2_noack()
{
AbortReason::NoAcknowledge
} else if abort_reason.arb_lost() {
AbortReason::ArbitrationLoss
} else if abort_reason.abrt_slvflush_txfifo() {
AbortReason::TxNotEmpty(tx_flush_cnt)
} else {
AbortReason::Other(abort_reason.0)
};
Err(Error::Abort(reason))
} else {
Ok(())
}
}
}

View File

@ -16,6 +16,7 @@ pub mod flash;
mod float;
pub mod gpio;
pub mod i2c;
pub mod i2c_slave;
pub mod multicore;
pub mod pwm;
mod reset;
@ -89,7 +90,8 @@ embassy_hal_internal::interrupt_mod!(
#[macro_export]
macro_rules! bind_interrupts {
($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => {
$vis struct $name;
#[derive(Copy, Clone)]
$vis struct $name;
$(
#[allow(non_snake_case)]

View File

@ -1079,18 +1079,43 @@ impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0);
pub trait PioPin: sealed::PioPin + gpio::Pin {}
macro_rules! impl_pio_pin {
($( $num:tt )*) => {
($( $pin:ident, )*) => {
$(
paste::paste!{
impl sealed::PioPin for peripherals::[< PIN_ $num >] {}
impl PioPin for peripherals::[< PIN_ $num >] {}
}
impl sealed::PioPin for peripherals::$pin {}
impl PioPin for peripherals::$pin {}
)*
};
}
impl_pio_pin! {
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
PIN_0,
PIN_1,
PIN_2,
PIN_3,
PIN_4,
PIN_5,
PIN_6,
PIN_7,
PIN_8,
PIN_9,
PIN_10,
PIN_11,
PIN_12,
PIN_13,
PIN_14,
PIN_15,
PIN_16,
PIN_17,
PIN_18,
PIN_19,
PIN_20,
PIN_21,
PIN_22,
PIN_23,
PIN_24,
PIN_25,
PIN_26,
PIN_27,
PIN_28,
PIN_29,
}

View File

@ -12,8 +12,8 @@ features = ["stm32wb55rg"]
[dependencies]
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = { version = "0.1.0", path = "../embassy-hal-internal" }
embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -1,4 +1,3 @@
use core::marker::PhantomData;
use core::ptr;
use embassy_stm32::ipcc::Ipcc;
@ -13,7 +12,7 @@ use crate::unsafe_linked_list::LinkedListNode;
use crate::{channels, evt};
pub struct Ble {
phantom: PhantomData<Ble>,
_private: (),
}
impl Ble {
@ -29,7 +28,7 @@ impl Ble {
});
}
Self { phantom: PhantomData }
Self { _private: () }
}
/// `HW_IPCC_BLE_EvtNot`
pub async fn tl_read(&self) -> EvtBox<Self> {

View File

@ -1,5 +1,4 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::ptr;
use core::sync::atomic::{AtomicBool, Ordering};
use core::task::Poll;
@ -21,12 +20,12 @@ static MAC_WAKER: AtomicWaker = AtomicWaker::new();
static MAC_EVT_OUT: AtomicBool = AtomicBool::new(false);
pub struct Mac {
phantom: PhantomData<Mac>,
_private: (),
}
impl Mac {
pub(crate) fn new() -> Self {
Self { phantom: PhantomData }
Self { _private: () }
}
/// `HW_IPCC_MAC_802_15_4_EvtNot`

View File

@ -1,6 +1,5 @@
//! Memory manager routines
use core::future::poll_fn;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::task::Poll;
@ -21,7 +20,7 @@ static MM_WAKER: AtomicWaker = AtomicWaker::new();
static mut LOCAL_FREE_BUF_QUEUE: Aligned<A4, MaybeUninit<LinkedListNode>> = Aligned(MaybeUninit::uninit());
pub struct MemoryManager {
phantom: PhantomData<MemoryManager>,
_private: (),
}
impl MemoryManager {
@ -44,7 +43,7 @@ impl MemoryManager {
});
}
Self { phantom: PhantomData }
Self { _private: () }
}
pub async fn run_queue(&self) {

View File

@ -1,4 +1,3 @@
use core::marker::PhantomData;
use core::ptr;
use crate::cmd::CmdPacket;
@ -12,7 +11,7 @@ use crate::unsafe_linked_list::LinkedListNode;
use crate::{channels, Ipcc, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_DEVICE_INFO_TABLE, TL_SYS_TABLE};
pub struct Sys {
phantom: PhantomData<Sys>,
_private: (),
}
impl Sys {
@ -27,7 +26,7 @@ impl Sys {
});
}
Self { phantom: PhantomData }
Self { _private: () }
}
/// Returns CPU2 wireless firmware information (if present).

View File

@ -8,31 +8,32 @@ license = "MIT OR Apache-2.0"
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-v$VERSION/embassy-stm32/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32/src/"
features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time", "low-power"]
features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time"]
flavors = [
{ regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" },
{ regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" },
{ regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi", features = ["low-power"] },
{ regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf" },
{ regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] },
{ regex_feature = "stm32l1.*", target = "thumbv7m-none-eabi" },
{ regex_feature = "stm32l4.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32l5.*", target = "thumbv8m.main-none-eabihf" },
{ regex_feature = "stm32u5.*", target = "thumbv8m.main-none-eabihf" },
{ regex_feature = "stm32wb.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32wba.*", target = "thumbv8m.main-none-eabihf" },
{ regex_feature = "stm32wl.*", target = "thumbv7em-none-eabi" },
]
[dependencies]
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] }
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
@ -62,7 +63,7 @@ stm32-metapac = "12"
vcell = "0.1.3"
bxcan = "0.7.0"
nb = "1.0.0"
stm32-fmc = "0.2.4"
stm32-fmc = "0.3.0"
seq-macro = "0.3.0"
cfg-if = "1.0.0"
embedded-io = { version = "0.5.0" }
@ -77,7 +78,7 @@ critical-section = { version = "1.1", features = ["std"] }
[build-dependencies]
proc-macro2 = "1.0.36"
quote = "1.0.15"
stm32-metapac = { path = "../../stm32-data-generated/stm32-metapac", default-features = false, features = ["metadata"]}
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-735cab337aad6161f3d6bcf3e49cd1f034bc3130", default-features = false, features = ["metadata"]}
[features]
default = ["rt"]
@ -1454,6 +1455,10 @@ stm32wb55vc = [ "stm32-metapac/stm32wb55vc" ]
stm32wb55ve = [ "stm32-metapac/stm32wb55ve" ]
stm32wb55vg = [ "stm32-metapac/stm32wb55vg" ]
stm32wb55vy = [ "stm32-metapac/stm32wb55vy" ]
stm32wba52ce = [ "stm32-metapac/stm32wba52ce" ]
stm32wba52cg = [ "stm32-metapac/stm32wba52cg" ]
stm32wba52ke = [ "stm32-metapac/stm32wba52ke" ]
stm32wba52kg = [ "stm32-metapac/stm32wba52kg" ]
stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4" ]
stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p" ]
stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4" ]

View File

@ -308,13 +308,11 @@ fn main() {
// ========
// Generate RccPeripheral impls
let refcounted_peripherals = HashSet::from(["usart", "adc"]);
let mut refcount_statics = HashSet::new();
for p in METADATA.peripherals {
// generating RccPeripheral impl for H7 ADC3 would result in bad frequency
if !singletons.contains(&p.name.to_string())
|| (p.name == "ADC3" && METADATA.line.starts_with("STM32H7"))
|| (p.name.starts_with("ADC") && p.registers.as_ref().map_or(false, |r| r.version == "f3"))
|| (p.name.starts_with("ADC") && p.registers.as_ref().map_or(false, |r| r.version == "v4"))
{
if !singletons.contains(&p.name.to_string()) {
continue;
}
@ -344,11 +342,36 @@ fn main() {
TokenStream::new()
};
let ptype = if let Some(reg) = &p.registers { reg.kind } else { "" };
let pname = format_ident!("{}", p.name);
let clk = format_ident!("{}", rcc.clock.to_ascii_lowercase());
let en_reg = format_ident!("{}", en.register.to_ascii_lowercase());
let set_en_field = format_ident!("set_{}", en.field.to_ascii_lowercase());
let (before_enable, before_disable) = if refcounted_peripherals.contains(ptype) {
let refcount_static =
format_ident!("{}_{}", en.register.to_ascii_uppercase(), en.field.to_ascii_uppercase());
refcount_statics.insert(refcount_static.clone());
(
quote! {
unsafe { refcount_statics::#refcount_static += 1 };
if unsafe { refcount_statics::#refcount_static } > 1 {
return;
}
},
quote! {
unsafe { refcount_statics::#refcount_static -= 1 };
if unsafe { refcount_statics::#refcount_static } > 0 {
return;
}
},
)
} else {
(TokenStream::new(), TokenStream::new())
};
g.extend(quote! {
impl crate::rcc::sealed::RccPeripheral for peripherals::#pname {
fn frequency() -> crate::time::Hertz {
@ -356,6 +379,7 @@ fn main() {
}
fn enable() {
critical_section::with(|_| {
#before_enable
#[cfg(feature = "low-power")]
crate::rcc::clock_refcount_add();
crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true));
@ -364,6 +388,7 @@ fn main() {
}
fn disable() {
critical_section::with(|_| {
#before_disable
crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false));
#[cfg(feature = "low-power")]
crate::rcc::clock_refcount_sub();
@ -379,6 +404,19 @@ fn main() {
}
}
let mut refcount_mod = TokenStream::new();
for refcount_static in refcount_statics {
refcount_mod.extend(quote! {
pub(crate) static mut #refcount_static: u8 = 0;
});
}
g.extend(quote! {
mod refcount_statics {
#refcount_mod
}
});
// ========
// Generate fns to enable GPIO, DMA in RCC
@ -673,6 +711,10 @@ fn main() {
// ADC is special
if regs.kind == "adc" {
if p.rcc.is_none() {
continue;
}
let peri = format_ident!("{}", p.name);
let pin_name = format_ident!("{}", pin.pin);
@ -786,6 +828,17 @@ fn main() {
let mut peripherals_table: Vec<Vec<String>> = Vec::new();
let mut pins_table: Vec<Vec<String>> = Vec::new();
let mut dma_channels_table: Vec<Vec<String>> = Vec::new();
let mut adc_common_table: Vec<Vec<String>> = Vec::new();
/*
If ADC3_COMMON exists, ADC3 and higher are assigned to it
All other ADCs are assigned to ADC_COMMON
ADC3 and higher are assigned to the adc34 clock in the table
The adc3_common cfg directive is added if ADC3_COMMON exists
*/
let has_adc3 = METADATA.peripherals.iter().find(|p| p.name == "ADC3_COMMON").is_some();
let set_adc345 = HashSet::from(["ADC3", "ADC4", "ADC5"]);
for m in METADATA
.memory
@ -823,6 +876,17 @@ fn main() {
}
}
if regs.kind == "adc" {
let (adc_common, adc_clock) = if set_adc345.contains(p.name) && has_adc3 {
("ADC3_COMMON", "adc34")
} else {
("ADC_COMMON", "adc")
};
let row = vec![p.name.to_string(), adc_common.to_string(), adc_clock.to_string()];
adc_common_table.push(row);
}
for irq in p.interrupts {
let row = vec![
p.name.to_string(),
@ -901,6 +965,7 @@ fn main() {
make_table(&mut m, "foreach_peripheral", &peripherals_table);
make_table(&mut m, "foreach_pin", &pins_table);
make_table(&mut m, "foreach_dma_channel", &dma_channels_table);
make_table(&mut m, "foreach_adc", &adc_common_table);
let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_file = out_dir.join("_macros.rs").to_string_lossy().to_string();
@ -942,13 +1007,24 @@ fn main() {
println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
}
// =======
// ADC3_COMMON is present
if has_adc3 {
println!("cargo:rustc-cfg={}", "adc3_common");
}
// =======
// Features for targeting groups of chips
println!("cargo:rustc-cfg={}", &chip_name[..7]); // stm32f4
println!("cargo:rustc-cfg={}", &chip_name[..9]); // stm32f429
println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x
println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9
if &chip_name[..8] == "stm32wba" {
println!("cargo:rustc-cfg={}", &chip_name[..8]); // stm32wba
println!("cargo:rustc-cfg={}", &chip_name[..10]); // stm32wba52
} else {
println!("cargo:rustc-cfg={}", &chip_name[..7]); // stm32f4
println!("cargo:rustc-cfg={}", &chip_name[..9]); // stm32f429
println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x
println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9
}
// Handle time-driver-XXXX features.
if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {}

View File

@ -1,16 +1,37 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::into_ref;
use embedded_hal_02::blocking::delay::DelayUs;
use crate::adc::{Adc, AdcPin, Instance, SampleTime};
use crate::rcc::get_freqs;
use crate::time::Hertz;
use crate::Peripheral;
use crate::{interrupt, Peripheral};
pub const VDDA_CALIB_MV: u32 = 3300;
pub const ADC_MAX: u32 = (1 << 12) - 1;
// No calibration data for F103, voltage should be 1.2v
pub const VREF_INT: u32 = 1200;
/// Interrupt handler.
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
if T::regs().sr().read().eoc() {
T::regs().cr1().modify(|w| w.set_eocie(false));
} else {
return;
}
T::state().waker.wake();
}
}
pub struct Vref;
impl<T: Instance> AdcPin<T> for Vref {}
impl<T: Instance> super::sealed::AdcPin<T> for Vref {
@ -60,7 +81,7 @@ impl<'d, T: Instance> Adc<'d, T> {
}
fn freq() -> Hertz {
unsafe { get_freqs() }.adc
unsafe { get_freqs() }.adc.unwrap()
}
pub fn sample_time_for_us(&self, us: u32) -> SampleTime {
@ -95,18 +116,28 @@ impl<'d, T: Instance> Adc<'d, T> {
}
/// Perform a single conversion.
fn convert(&mut self) -> u16 {
async fn convert(&mut self) -> u16 {
T::regs().cr2().modify(|reg| {
reg.set_adon(true);
reg.set_swstart(true);
});
while T::regs().cr2().read().swstart() {}
while !T::regs().sr().read().eoc() {}
T::regs().cr1().modify(|w| w.set_eocie(true));
poll_fn(|cx| {
T::state().waker.register(cx.waker());
if !T::regs().cr2().read().swstart() && T::regs().sr().read().eoc() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
T::regs().dr().read().0 as u16
}
pub fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 {
pub async fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 {
Self::set_channel_sample_time(pin.channel(), self.sample_time);
T::regs().cr1().modify(|reg| {
reg.set_scan(false);
@ -123,7 +154,7 @@ impl<'d, T: Instance> Adc<'d, T> {
// Configure the channel to sample
T::regs().sqr3().write(|reg| reg.set_sq(0, pin.channel()));
self.convert()
self.convert().await
}
fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
@ -135,3 +166,11 @@ impl<'d, T: Instance> Adc<'d, T> {
}
}
}
impl<'d, T: Instance> Drop for Adc<'d, T> {
fn drop(&mut self) {
T::regs().cr2().modify(|reg| reg.set_adon(false));
T::disable();
}
}

195
embassy-stm32/src/adc/f3.rs Normal file
View File

@ -0,0 +1,195 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::into_ref;
use embedded_hal_02::blocking::delay::DelayUs;
use crate::adc::{Adc, AdcPin, Instance, SampleTime};
use crate::interrupt::typelevel::Interrupt;
use crate::time::Hertz;
use crate::{interrupt, Peripheral};
pub const VDDA_CALIB_MV: u32 = 3300;
pub const ADC_MAX: u32 = (1 << 12) - 1;
pub const VREF_INT: u32 = 1230;
/// Interrupt handler.
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
if T::regs().isr().read().eoc() {
T::regs().ier().modify(|w| w.set_eocie(false));
} else {
return;
}
T::state().waker.wake();
}
}
pub struct Vref;
impl<T: Instance> AdcPin<T> for Vref {}
impl<T: Instance> super::sealed::AdcPin<T> for Vref {
fn channel(&self) -> u8 {
18
}
}
impl Vref {
/// The value that vref would be if vdda was at 3300mv
pub fn value(&self) -> u16 {
crate::pac::VREFINTCAL.data().read().value()
}
}
pub struct Temperature;
impl<T: Instance> AdcPin<T> for Temperature {}
impl<T: Instance> super::sealed::AdcPin<T> for Temperature {
fn channel(&self) -> u8 {
16
}
}
impl<'d, T: Instance> Adc<'d, T> {
pub fn new(
adc: impl Peripheral<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
delay: &mut impl DelayUs<u32>,
) -> Self {
use crate::pac::adc::vals;
into_ref!(adc);
T::enable();
T::reset();
// Enable the adc regulator
T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::INTERMEDIATE));
T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::ENABLED));
// Wait for the regulator to stabilize
delay.delay_us(10);
assert!(!T::regs().cr().read().aden());
// Begin calibration
T::regs().cr().modify(|w| w.set_adcaldif(false));
T::regs().cr().modify(|w| w.set_adcal(true));
while T::regs().cr().read().adcal() {}
// Wait more than 4 clock cycles after adcal is cleared (RM0364 p. 223)
delay.delay_us(1 + (6 * 1_000_000 / Self::freq().0));
// Enable the adc
T::regs().cr().modify(|w| w.set_aden(true));
// Wait until the adc is ready
while !T::regs().isr().read().adrdy() {}
T::Interrupt::unpend();
unsafe {
T::Interrupt::enable();
}
Self {
adc,
sample_time: Default::default(),
}
}
fn freq() -> Hertz {
<T as crate::adc::sealed::Instance>::frequency()
}
pub fn sample_time_for_us(&self, us: u32) -> SampleTime {
match us * Self::freq().0 / 1_000_000 {
0..=1 => SampleTime::Cycles1_5,
2..=4 => SampleTime::Cycles4_5,
5..=7 => SampleTime::Cycles7_5,
8..=19 => SampleTime::Cycles19_5,
20..=61 => SampleTime::Cycles61_5,
62..=181 => SampleTime::Cycles181_5,
_ => SampleTime::Cycles601_5,
}
}
pub fn enable_vref(&self, _delay: &mut impl DelayUs<u32>) -> Vref {
T::common_regs().ccr().modify(|w| w.set_vrefen(true));
Vref {}
}
pub fn enable_temperature(&self) -> Temperature {
T::common_regs().ccr().modify(|w| w.set_tsen(true));
Temperature {}
}
pub fn set_sample_time(&mut self, sample_time: SampleTime) {
self.sample_time = sample_time;
}
/// Perform a single conversion.
async fn convert(&mut self) -> u16 {
T::regs().isr().write(|_| {});
T::regs().ier().modify(|w| w.set_eocie(true));
T::regs().cr().modify(|w| w.set_adstart(true));
poll_fn(|cx| {
T::state().waker.register(cx.waker());
if T::regs().isr().read().eoc() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
T::regs().isr().write(|_| {});
T::regs().dr().read().rdata()
}
pub async fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 {
Self::set_channel_sample_time(pin.channel(), self.sample_time);
// Configure the channel to sample
T::regs().sqr1().write(|w| w.set_sq(0, pin.channel()));
self.convert().await
}
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));
}
}
}
impl<'d, T: Instance> Drop for Adc<'d, T> {
fn drop(&mut self) {
use crate::pac::adc::vals;
T::regs().cr().modify(|w| w.set_adstp(true));
while T::regs().cr().read().adstp() {}
T::regs().cr().modify(|w| w.set_addis(true));
while T::regs().cr().read().aden() {}
// Disable the adc regulator
T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::INTERMEDIATE));
T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::DISABLED));
T::disable();
}
}

View File

@ -1,23 +1,24 @@
#![macro_use]
#[cfg(not(any(adc_f3, adc_f3_v2)))]
#[cfg(not(adc_f3_v2))]
#[cfg_attr(adc_f1, path = "f1.rs")]
#[cfg_attr(adc_f3, path = "f3.rs")]
#[cfg_attr(adc_v1, path = "v1.rs")]
#[cfg_attr(adc_v2, path = "v2.rs")]
#[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")]
#[cfg_attr(adc_v4, path = "v4.rs")]
mod _version;
#[cfg(not(any(adc_f1, adc_f3, adc_f3_v2)))]
#[cfg(not(any(adc_f1, adc_f3_v2)))]
mod resolution;
mod sample_time;
#[cfg(not(any(adc_f3, adc_f3_v2)))]
#[allow(unused)]
#[cfg(not(adc_f3_v2))]
pub use _version::*;
#[cfg(not(any(adc_f1, adc_f3, adc_f3_v2)))]
pub use resolution::Resolution;
#[cfg(not(any(adc_f3, adc_f3_v2)))]
#[cfg(not(adc_f3_v2))]
pub use sample_time::SampleTime;
use crate::peripherals;
@ -25,18 +26,46 @@ use crate::peripherals;
pub struct Adc<'d, T: Instance> {
#[allow(unused)]
adc: crate::PeripheralRef<'d, T>,
#[cfg(not(any(adc_f3, adc_f3_v2)))]
#[cfg(not(adc_f3_v2))]
sample_time: SampleTime,
}
pub(crate) mod sealed {
pub trait Instance {
#[cfg(any(adc_f1, adc_f3, adc_v1))]
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(any(adc_f1, adc_f3, adc_v1))]
pub struct State {
pub waker: AtomicWaker,
}
#[cfg(any(adc_f1, adc_f3, adc_v1))]
impl State {
pub const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
}
pub trait InterruptableInstance {
type Interrupt: crate::interrupt::typelevel::Interrupt;
}
pub trait Instance: InterruptableInstance {
fn regs() -> crate::pac::adc::Adc;
#[cfg(not(any(adc_f1, adc_v1, adc_f3, adc_f3_v2)))]
#[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_g0)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon;
#[cfg(adc_f3)]
fn frequency() -> crate::time::Hertz;
#[cfg(any(adc_f1, adc_f3, adc_v1))]
fn state() -> &'static State;
}
pub trait AdcPin<T: Instance> {
#[cfg(any(adc_v1, adc_v2))]
fn set_as_analog(&mut self) {}
fn channel(&self) -> u8;
}
@ -45,68 +74,45 @@ pub(crate) mod sealed {
}
}
#[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v4)))]
#[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v4, adc_f3)))]
pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> {}
#[cfg(any(adc_f1, adc_v1, adc_v2, adc_v4))]
#[cfg(any(adc_f1, adc_v1, adc_v2, adc_v4, adc_f3))]
pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> + crate::rcc::RccPeripheral {}
pub trait AdcPin<T: Instance>: sealed::AdcPin<T> {}
pub trait InternalChannel<T>: sealed::InternalChannel<T> {}
#[cfg(not(stm32h7))]
foreach_peripheral!(
(adc, $inst:ident) => {
foreach_adc!(
($inst:ident, $common_inst:ident, $clock:ident) => {
impl crate::adc::sealed::Instance for peripherals::$inst {
fn regs() -> crate::pac::adc::Adc {
crate::pac::$inst
}
#[cfg(not(any(adc_f1, adc_v1, adc_f3, adc_f3_v2)))]
#[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_g0)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon {
foreach_peripheral!{
(adccommon, $common_inst:ident) => {
return crate::pac::$common_inst
};
}
return crate::pac::$common_inst
}
#[cfg(adc_f3)]
fn frequency() -> crate::time::Hertz {
unsafe { crate::rcc::get_freqs() }.$clock.unwrap()
}
#[cfg(any(adc_f1, adc_f3, adc_v1))]
fn state() -> &'static sealed::State {
static STATE: sealed::State = sealed::State::new();
&STATE
}
}
impl crate::adc::Instance for peripherals::$inst {}
};
);
#[cfg(stm32h7)]
foreach_peripheral!(
(adc, ADC3) => {
impl crate::adc::sealed::Instance for peripherals::ADC3 {
fn regs() -> crate::pac::adc::Adc {
crate::pac::ADC3
}
#[cfg(all(not(adc_f1), not(adc_v1)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon {
foreach_peripheral!{
(adccommon, ADC3_COMMON) => {
return crate::pac::ADC3_COMMON
};
foreach_interrupt!(
($inst,adc,ADC,GLOBAL,$irq:ident) => {
impl sealed::InterruptableInstance for peripherals::$inst {
type Interrupt = crate::interrupt::typelevel::$irq;
}
}
}
impl crate::adc::Instance for peripherals::ADC3 {}
};
(adc, $inst:ident) => {
impl crate::adc::sealed::Instance for peripherals::$inst {
fn regs() -> crate::pac::adc::Adc {
crate::pac::$inst
}
#[cfg(all(not(adc_f1), not(adc_v1)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon {
foreach_peripheral!{
(adccommon, ADC_COMMON) => {
return crate::pac::ADC_COMMON
};
}
}
}
};
);
impl crate::adc::Instance for peripherals::$inst {}
};
@ -117,6 +123,11 @@ macro_rules! impl_adc_pin {
impl crate::adc::AdcPin<peripherals::$inst> for crate::peripherals::$pin {}
impl crate::adc::sealed::AdcPin<peripherals::$inst> for crate::peripherals::$pin {
#[cfg(any(adc_v1, adc_v2))]
fn set_as_analog(&mut self) {
<Self as crate::gpio::sealed::Pin>::set_as_analog(self);
}
fn channel(&self) -> u8 {
$ch
}

View File

@ -1,4 +1,4 @@
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Resolution {
TwelveBit,
@ -19,7 +19,7 @@ pub enum Resolution {
impl Default for Resolution {
fn default() -> Self {
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))]
{
Self::TwelveBit
}
@ -40,7 +40,7 @@ impl From<Resolution> for crate::pac::adc::vals::Res {
Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT,
Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT,
Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT,
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))]
Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT,
}
}
@ -56,7 +56,7 @@ impl Resolution {
Resolution::TwelveBit => (1 << 12) - 1,
Resolution::TenBit => (1 << 10) - 1,
Resolution::EightBit => (1 << 8) - 1,
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))]
Resolution::SixBit => (1 << 6) - 1,
}
}

View File

@ -1,4 +1,4 @@
#[cfg(not(any(adc_f3, adc_f3_v2)))]
#[cfg(not(adc_f3_v2))]
macro_rules! impl_sample_time {
($default_doc:expr, $default:ident, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => {
#[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")]
@ -105,3 +105,19 @@ impl_sample_time!(
("810.5", Cycles810_5, CYCLES810_5)
)
);
#[cfg(adc_f3)]
impl_sample_time!(
"1.5",
Cycles1_5,
(
("1.5", Cycles1_5, CYCLES1_5),
("2.5", Cycles2_5, CYCLES2_5),
("4.5", Cycles4_5, CYCLES4_5),
("7.5", Cycles7_5, CYCLES7_5),
("19.5", Cycles19_5, CYCLES19_5),
("61.5", Cycles61_5, CYCLES61_5),
("181.5", Cycles181_5, CYCLES181_5),
("601.5", Cycles601_5, CYCLES601_5)
)
);

View File

@ -1,39 +1,65 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::into_ref;
use embedded_hal_02::blocking::delay::DelayUs;
use crate::adc::{Adc, AdcPin, Instance, InternalChannel, Resolution, SampleTime};
use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime};
use crate::interrupt::typelevel::Interrupt;
use crate::peripherals::ADC;
use crate::Peripheral;
use crate::{interrupt, Peripheral};
pub const VDDA_CALIB_MV: u32 = 3300;
pub const VREF_INT: u32 = 1230;
/// Interrupt handler.
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
if T::regs().isr().read().eoc() {
T::regs().ier().modify(|w| w.set_eocie(false));
} else {
return;
}
T::state().waker.wake();
}
}
pub struct Vbat;
impl InternalChannel<ADC> for Vbat {}
impl super::sealed::InternalChannel<ADC> for Vbat {
impl AdcPin<ADC> for Vbat {}
impl super::sealed::AdcPin<ADC> for Vbat {
fn channel(&self) -> u8 {
18
}
}
pub struct Vref;
impl InternalChannel<ADC> for Vref {}
impl super::sealed::InternalChannel<ADC> for Vref {
impl AdcPin<ADC> for Vref {}
impl super::sealed::AdcPin<ADC> for Vref {
fn channel(&self) -> u8 {
17
}
}
pub struct Temperature;
impl InternalChannel<ADC> for Temperature {}
impl super::sealed::InternalChannel<ADC> for Temperature {
impl AdcPin<ADC> for Temperature {}
impl super::sealed::AdcPin<ADC> for Temperature {
fn channel(&self) -> u8 {
16
}
}
impl<'d, T: Instance> Adc<'d, T> {
pub fn new(adc: impl Peripheral<P = T> + 'd, delay: &mut impl DelayUs<u32>) -> Self {
pub fn new(
adc: impl Peripheral<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
delay: &mut impl DelayUs<u32>,
) -> Self {
into_ref!(adc);
T::enable();
T::reset();
@ -44,12 +70,32 @@ impl<'d, T: Instance> Adc<'d, T> {
// tstab = 14 * 1/fadc
delay.delay_us(1);
let s = Self {
// A.7.1 ADC calibration code example
T::regs().cfgr1().modify(|reg| reg.set_dmaen(false));
T::regs().cr().modify(|reg| reg.set_adcal(true));
while T::regs().cr().read().adcal() {}
// A.7.2 ADC enable sequence code example
if T::regs().isr().read().adrdy() {
T::regs().isr().modify(|reg| reg.set_adrdy(true));
}
T::regs().cr().modify(|reg| reg.set_aden(true));
while !T::regs().isr().read().adrdy() {
// ES0233, 2.4.3 ADEN bit cannot be set immediately after the ADC calibration
// Workaround: When the ADC calibration is complete (ADCAL = 0), keep setting the
// ADEN bit until the ADRDY flag goes high.
T::regs().cr().modify(|reg| reg.set_aden(true));
}
T::Interrupt::unpend();
unsafe {
T::Interrupt::enable();
}
Self {
adc,
sample_time: Default::default(),
};
s.calibrate();
s
}
}
pub fn enable_vbat(&self, _delay: &mut impl DelayUs<u32>) -> Vbat {
@ -80,21 +126,6 @@ impl<'d, T: Instance> Adc<'d, T> {
Temperature
}
fn calibrate(&self) {
// A.7.1 ADC calibration code example
if T::regs().cr().read().aden() {
T::regs().cr().modify(|reg| reg.set_addis(true));
}
while T::regs().cr().read().aden() {
// spin
}
T::regs().cfgr1().modify(|reg| reg.set_dmaen(false));
T::regs().cr().modify(|reg| reg.set_adcal(true));
while T::regs().cr().read().adcal() {
// spin
}
}
pub fn set_sample_time(&mut self, sample_time: SampleTime) {
self.sample_time = sample_time;
}
@ -103,57 +134,50 @@ impl<'d, T: Instance> Adc<'d, T> {
T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into()));
}
pub fn read<P>(&mut self, pin: &mut P) -> u16
where
P: AdcPin<T> + crate::gpio::sealed::Pin,
{
pub async fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 {
let channel = pin.channel();
pin.set_as_analog();
self.read_channel(channel)
// A.7.5 Single conversion sequence code example - Software trigger
T::regs().chselr().write(|reg| reg.set_chselx(channel as usize, true));
self.convert().await
}
pub fn read_internal(&mut self, channel: &mut impl InternalChannel<T>) -> u16 {
let channel = channel.channel();
self.read_channel(channel)
}
fn read_channel(&mut self, channel: u8) -> u16 {
// A.7.2 ADC enable sequence code example
if T::regs().isr().read().adrdy() {
T::regs().isr().modify(|reg| reg.set_adrdy(true));
}
T::regs().cr().modify(|reg| reg.set_aden(true));
while !T::regs().isr().read().adrdy() {
// ES0233, 2.4.3 ADEN bit cannot be set immediately after the ADC calibration
// Workaround: When the ADC calibration is complete (ADCAL = 0), keep setting the
// ADEN bit until the ADRDY flag goes high.
T::regs().cr().modify(|reg| reg.set_aden(true));
}
async fn convert(&mut self) -> u16 {
T::regs().isr().modify(|reg| {
reg.set_eoc(true);
reg.set_eosmp(true);
});
// A.7.5 Single conversion sequence code example - Software trigger
T::regs().chselr().write(|reg| reg.set_chselx(channel as usize, true));
T::regs().smpr().modify(|reg| reg.set_smp(self.sample_time.into()));
T::regs().ier().modify(|w| w.set_eocie(true));
T::regs().cr().modify(|reg| reg.set_adstart(true));
while !T::regs().isr().read().eoc() {
// spin
}
let value = T::regs().dr().read().0 as u16;
// A.7.3 ADC disable code example
T::regs().cr().modify(|reg| reg.set_adstp(true));
while T::regs().cr().read().adstp() {
// spin
}
T::regs().cr().modify(|reg| reg.set_addis(true));
while T::regs().cr().read().aden() {
// spin
}
poll_fn(|cx| {
T::state().waker.register(cx.waker());
value
if T::regs().isr().read().eoc() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
T::regs().dr().read().data()
}
}
impl<'d, T: Instance> Drop for Adc<'d, T> {
fn drop(&mut self) {
// A.7.3 ADC disable code example
T::regs().cr().modify(|reg| reg.set_adstp(true));
while T::regs().cr().read().adstp() {}
T::regs().cr().modify(|reg| reg.set_addis(true));
while T::regs().cr().read().aden() {}
T::disable();
}
}

View File

@ -1,7 +1,6 @@
use embassy_hal_internal::into_ref;
use embedded_hal_02::blocking::delay::DelayUs;
use super::InternalChannel;
use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime};
use crate::peripherals::ADC1;
use crate::time::Hertz;
@ -16,8 +15,8 @@ pub const VREF_CALIB_MV: u32 = 3300;
pub const ADC_POWERUP_TIME_US: u32 = 3;
pub struct VrefInt;
impl InternalChannel<ADC1> for VrefInt {}
impl super::sealed::InternalChannel<ADC1> for VrefInt {
impl AdcPin<ADC1> for VrefInt {}
impl super::sealed::AdcPin<ADC1> for VrefInt {
fn channel(&self) -> u8 {
17
}
@ -31,8 +30,8 @@ impl VrefInt {
}
pub struct Temperature;
impl InternalChannel<ADC1> for Temperature {}
impl super::sealed::InternalChannel<ADC1> for Temperature {
impl AdcPin<ADC1> for Temperature {}
impl super::sealed::AdcPin<ADC1> for Temperature {
fn channel(&self) -> u8 {
cfg_if::cfg_if! {
if #[cfg(any(stm32f40, stm32f41))] {
@ -52,8 +51,8 @@ impl Temperature {
}
pub struct Vbat;
impl InternalChannel<ADC1> for Vbat {}
impl super::sealed::InternalChannel<ADC1> for Vbat {
impl AdcPin<ADC1> for Vbat {}
impl super::sealed::AdcPin<ADC1> for Vbat {
fn channel(&self) -> u8 {
18
}
@ -102,7 +101,7 @@ where
let presc = Prescaler::from_pclk2(T::frequency());
T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre()));
T::regs().cr2().modify(|reg| {
reg.set_adon(crate::pac::adc::vals::Adon::ENABLED);
reg.set_adon(true);
});
delay.delay_us(ADC_POWERUP_TIME_US);
@ -125,7 +124,7 @@ where
/// [Adc::read_internal()] to perform conversion.
pub fn enable_vrefint(&self) -> VrefInt {
T::common_regs().ccr().modify(|reg| {
reg.set_tsvrefe(crate::pac::adccommon::vals::Tsvrefe::ENABLED);
reg.set_tsvrefe(true);
});
VrefInt {}
@ -138,7 +137,7 @@ where
/// temperature sensor will return vbat value.
pub fn enable_temperature(&self) -> Temperature {
T::common_regs().ccr().modify(|reg| {
reg.set_tsvrefe(crate::pac::adccommon::vals::Tsvrefe::ENABLED);
reg.set_tsvrefe(true);
});
Temperature {}
@ -148,7 +147,7 @@ where
/// [Adc::read_internal()] to perform conversion.
pub fn enable_vbat(&self) -> Vbat {
T::common_regs().ccr().modify(|reg| {
reg.set_vbate(crate::pac::adccommon::vals::Vbate::ENABLED);
reg.set_vbate(true);
});
Vbat {}
@ -176,22 +175,11 @@ where
T::regs().dr().read().0 as u16
}
pub fn read<P>(&mut self, pin: &mut P) -> u16
where
P: AdcPin<T>,
P: crate::gpio::sealed::Pin,
{
pub fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 {
pin.set_as_analog();
self.read_channel(pin.channel())
}
pub fn read_internal(&mut self, channel: &mut impl InternalChannel<T>) -> u16 {
self.read_channel(channel.channel())
}
fn read_channel(&mut self, channel: u8) -> u16 {
// Configure ADC
let channel = pin.channel();
// Select channel
T::regs().sqr3().write(|reg| reg.set_sq(0, channel));
@ -199,9 +187,7 @@ where
// Configure channel
Self::set_channel_sample_time(channel, self.sample_time);
let val = self.convert();
val
self.convert()
}
fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
@ -216,6 +202,10 @@ where
impl<'d, T: Instance> Drop for Adc<'d, T> {
fn drop(&mut self) {
T::regs().cr2().modify(|reg| {
reg.set_adon(false);
});
T::disable();
}
}

View File

@ -13,7 +13,7 @@ pub const VREF_CALIB_MV: u32 = 3000;
/// configuration.
fn enable() {
critical_section::with(|_| {
#[cfg(stm32h7)]
#[cfg(any(stm32h7, stm32wl))]
crate::pac::RCC.apb2enr().modify(|w| w.set_adcen(true));
#[cfg(stm32g0)]
crate::pac::RCC.apbenr2().modify(|w| w.set_adcen(true));
@ -26,9 +26,9 @@ pub struct VrefInt;
impl<T: Instance> AdcPin<T> for VrefInt {}
impl<T: Instance> super::sealed::AdcPin<T> for VrefInt {
fn channel(&self) -> u8 {
#[cfg(not(stm32g0))]
#[cfg(not(adc_g0))]
let val = 0;
#[cfg(stm32g0)]
#[cfg(adc_g0)]
let val = 13;
val
}
@ -38,9 +38,9 @@ pub struct Temperature;
impl<T: Instance> AdcPin<T> for Temperature {}
impl<T: Instance> super::sealed::AdcPin<T> for Temperature {
fn channel(&self) -> u8 {
#[cfg(not(stm32g0))]
#[cfg(not(adc_g0))]
let val = 17;
#[cfg(stm32g0)]
#[cfg(adc_g0)]
let val = 12;
val
}
@ -50,9 +50,9 @@ pub struct Vbat;
impl<T: Instance> AdcPin<T> for Vbat {}
impl<T: Instance> super::sealed::AdcPin<T> for Vbat {
fn channel(&self) -> u8 {
#[cfg(not(stm32g0))]
#[cfg(not(adc_g0))]
let val = 18;
#[cfg(stm32g0)]
#[cfg(adc_g0)]
let val = 14;
val
}
@ -92,9 +92,14 @@ impl<'d, T: Instance> Adc<'d, T> {
}
pub fn enable_vrefint(&self, delay: &mut impl DelayUs<u32>) -> VrefInt {
#[cfg(not(adc_g0))]
T::common_regs().ccr().modify(|reg| {
reg.set_vrefen(true);
});
#[cfg(adc_g0)]
T::regs().ccr().modify(|reg| {
reg.set_vrefen(true);
});
// "Table 24. Embedded internal voltage reference" states that it takes a maximum of 12 us
// to stabilize the internal voltage reference, we wait a little more.
@ -106,17 +111,27 @@ impl<'d, T: Instance> Adc<'d, T> {
}
pub fn enable_temperature(&self) -> Temperature {
#[cfg(not(adc_g0))]
T::common_regs().ccr().modify(|reg| {
reg.set_ch17sel(true);
});
#[cfg(adc_g0)]
T::regs().ccr().modify(|reg| {
reg.set_tsen(true);
});
Temperature {}
}
pub fn enable_vbat(&self) -> Vbat {
#[cfg(not(adc_g0))]
T::common_regs().ccr().modify(|reg| {
reg.set_ch18sel(true);
});
#[cfg(adc_g0)]
T::regs().ccr().modify(|reg| {
reg.set_vbaten(true);
});
Vbat {}
}
@ -126,9 +141,9 @@ impl<'d, T: Instance> Adc<'d, T> {
}
pub fn set_resolution(&mut self, resolution: Resolution) {
#[cfg(not(stm32g0))]
#[cfg(not(adc_g0))]
T::regs().cfgr().modify(|reg| reg.set_res(resolution.into()));
#[cfg(stm32g0)]
#[cfg(adc_g0)]
T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into()));
}
@ -182,9 +197,9 @@ impl<'d, T: Instance> Adc<'d, T> {
Self::set_channel_sample_time(pin.channel(), self.sample_time);
// Select channel
#[cfg(not(stm32g0))]
#[cfg(not(adc_g0))]
T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel()));
#[cfg(stm32g0)]
#[cfg(adc_g0)]
T::regs().chselr().write(|reg| reg.set_chsel(1 << pin.channel()));
// Some models are affected by an erratum:
@ -203,12 +218,12 @@ impl<'d, T: Instance> Adc<'d, T> {
val
}
#[cfg(stm32g0)]
#[cfg(adc_g0)]
fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) {
T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into()));
}
#[cfg(not(stm32g0))]
#[cfg(not(adc_g0))]
fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
let sample_time = sample_time.into();
T::regs()

View File

@ -1,6 +1,5 @@
use core::sync::atomic::{AtomicU8, Ordering};
use embedded_hal_02::blocking::delay::DelayUs;
#[allow(unused)]
use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel};
use pac::adccommon::vals::Presc;
@ -13,12 +12,31 @@ pub const VREF_DEFAULT_MV: u32 = 3300;
/// VREF voltage used for factory calibration of VREFINTCAL register.
pub const VREF_CALIB_MV: u32 = 3300;
// NOTE: Vrefint/Temperature/Vbat are only available on ADC3 on H7, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs
/// Max single ADC operation clock frequency
#[cfg(stm32g4)]
const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(60);
#[cfg(stm32h7)]
const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(50);
#[cfg(stm32g4)]
const VREF_CHANNEL: u8 = 18;
#[cfg(stm32g4)]
const TEMP_CHANNEL: u8 = 16;
#[cfg(stm32h7)]
const VREF_CHANNEL: u8 = 19;
#[cfg(stm32h7)]
const TEMP_CHANNEL: u8 = 18;
// TODO this should be 14 for H7a/b/35
const VBAT_CHANNEL: u8 = 17;
// NOTE: Vrefint/Temperature/Vbat are not available on all ADCs, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs
pub struct VrefInt;
impl<T: Instance> InternalChannel<T> for VrefInt {}
impl<T: Instance> super::sealed::InternalChannel<T> for VrefInt {
fn channel(&self) -> u8 {
19
VREF_CHANNEL
}
}
@ -26,7 +44,7 @@ pub struct Temperature;
impl<T: Instance> InternalChannel<T> for Temperature {}
impl<T: Instance> super::sealed::InternalChannel<T> for Temperature {
fn channel(&self) -> u8 {
18
TEMP_CHANNEL
}
}
@ -34,128 +52,10 @@ pub struct Vbat;
impl<T: Instance> InternalChannel<T> for Vbat {}
impl<T: Instance> super::sealed::InternalChannel<T> for Vbat {
fn channel(&self) -> u8 {
// TODO this should be 14 for H7a/b/35
17
VBAT_CHANNEL
}
}
static ADC12_ENABLE_COUNTER: AtomicU8 = AtomicU8::new(0);
#[cfg(stm32h7)]
foreach_peripheral!(
(adc, ADC1) => {
impl crate::rcc::sealed::RccPeripheral for crate::peripherals::ADC1 {
fn frequency() -> crate::time::Hertz {
critical_section::with(|_| {
match unsafe { crate::rcc::get_freqs() }.adc {
Some(ck) => ck,
None => panic!("Invalid ADC clock configuration, AdcClockSource was likely not properly configured.")
}
})
}
fn enable() {
critical_section::with(|_| {
crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(true))
});
ADC12_ENABLE_COUNTER.fetch_add(1, Ordering::SeqCst);
}
fn disable() {
if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 {
critical_section::with(|_| {
crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(false));
})
}
ADC12_ENABLE_COUNTER.fetch_sub(1, Ordering::SeqCst);
}
fn reset() {
if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 {
critical_section::with(|_| {
crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(true));
crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(false));
});
}
}
}
impl crate::rcc::RccPeripheral for crate::peripherals::ADC1 {}
};
(adc, ADC2) => {
impl crate::rcc::sealed::RccPeripheral for crate::peripherals::ADC2 {
fn frequency() -> crate::time::Hertz {
critical_section::with(|_| {
match unsafe { crate::rcc::get_freqs() }.adc {
Some(ck) => ck,
None => panic!("Invalid ADC clock configuration, AdcClockSource was likely not properly configured.")
}
})
}
fn enable() {
critical_section::with(|_| {
crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(true))
});
ADC12_ENABLE_COUNTER.fetch_add(1, Ordering::SeqCst);
}
fn disable() {
if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 {
critical_section::with(|_| {
crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(false));
})
}
ADC12_ENABLE_COUNTER.fetch_sub(1, Ordering::SeqCst);
}
fn reset() {
if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 {
critical_section::with(|_| {
crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(true));
crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(false));
});
}
}
}
impl crate::rcc::RccPeripheral for crate::peripherals::ADC2 {}
};
(adc, ADC3) => {
impl crate::rcc::sealed::RccPeripheral for crate::peripherals::ADC3 {
fn frequency() -> crate::time::Hertz {
critical_section::with(|_| {
match unsafe { crate::rcc::get_freqs() }.adc {
Some(ck) => ck,
None => panic!("Invalid ADC clock configuration, AdcClockSource was likely not properly configured.")
}
})
}
fn enable() {
critical_section::with(|_| {
crate::pac::RCC.ahb4enr().modify(|w| w.set_adc3en(true))
});
}
fn disable() {
critical_section::with(|_| {
crate::pac::RCC.ahb4enr().modify(|w| w.set_adc3en(false));
})
}
fn reset() {
critical_section::with(|_| {
crate::pac::RCC.ahb4rstr().modify(|w| w.set_adc3rst(true));
crate::pac::RCC.ahb4rstr().modify(|w| w.set_adc3rst(false));
});
}
}
impl crate::rcc::RccPeripheral for crate::peripherals::ADC3 {}
};
);
// NOTE (unused): The prescaler enum closely copies the hardware capabilities,
// but high prescaling doesn't make a lot of sense in the current implementation and is ommited.
#[allow(unused)]
@ -176,7 +76,7 @@ enum Prescaler {
impl Prescaler {
fn from_ker_ck(frequency: Hertz) -> Self {
let raw_prescaler = frequency.0 / 50_000_000;
let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0;
match raw_prescaler {
0 => Self::NotDivided,
1 => Self::DividedBy2,
@ -237,20 +137,23 @@ impl<'d, T: Instance> Adc<'d, T> {
let frequency = Hertz(T::frequency().0 / prescaler.divisor());
info!("ADC frequency set to {} Hz", frequency.0);
if frequency > Hertz::mhz(50) {
panic!("Maximal allowed frequency for the ADC is 50 MHz and it varies with different packages, refer to ST docs for more information.");
if frequency > MAX_ADC_CLK_FREQ {
panic!("Maximal allowed frequency for the ADC is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 );
}
let boost = if frequency < Hertz::khz(6_250) {
Boost::LT6_25
} else if frequency < Hertz::khz(12_500) {
Boost::LT12_5
} else if frequency < Hertz::mhz(25) {
Boost::LT25
} else {
Boost::LT50
};
T::regs().cr().modify(|w| w.set_boost(boost));
#[cfg(stm32h7)]
{
let boost = if frequency < Hertz::khz(6_250) {
Boost::LT6_25
} else if frequency < Hertz::khz(12_500) {
Boost::LT12_5
} else if frequency < Hertz::mhz(25) {
Boost::LT25
} else {
Boost::LT50
};
T::regs().cr().modify(|w| w.set_boost(boost));
}
let mut s = Self {
adc,
sample_time: Default::default(),
@ -379,10 +282,14 @@ impl<'d, T: Instance> Adc<'d, T> {
// Configure channel
Self::set_channel_sample_time(channel, self.sample_time);
T::regs().cfgr2().modify(|w| w.set_lshift(0));
T::regs()
.pcsel()
.write(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED));
#[cfg(stm32h7)]
{
T::regs().cfgr2().modify(|w| w.set_lshift(0));
T::regs()
.pcsel()
.write(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED));
}
T::regs().sqr1().write(|reg| {
reg.set_sq(0, channel);
reg.set_l(0);

View File

@ -129,7 +129,6 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
#[cfg(any(eth_v1b, eth_v1c))]
critical_section::with(|_| {
RCC.apb2enr().modify(|w| w.set_syscfgen(true));
RCC.ahb1enr().modify(|w| {
w.set_ethen(true);
w.set_ethtxen(true);

View File

@ -34,8 +34,6 @@ impl interrupt::typelevel::Handler<interrupt::typelevel::ETH> for InterruptHandl
}
}
const MTU: usize = 1514; // 14 Ethernet header + 1500 IP packet
pub struct Ethernet<'d, T: Instance, P: PHY> {
_peri: PeripheralRef<'d, T>,
pub(crate) tx: TDesRing<'d>,
@ -80,7 +78,6 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
// Enable the necessary Clocks
#[cfg(not(rcc_h5))]
critical_section::with(|_| {
crate::pac::RCC.apb4enr().modify(|w| w.set_syscfgen(true));
crate::pac::RCC.ahb1enr().modify(|w| {
w.set_eth1macen(true);
w.set_eth1txen(true);
@ -102,9 +99,9 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
});
// RMII
crate::pac::SBS
crate::pac::SYSCFG
.pmcr()
.modify(|w| w.set_eth_sel_phy(crate::pac::sbs::vals::EthSelPhy::B_0X4));
.modify(|w| w.set_eth_sel_phy(crate::pac::syscfg::vals::EthSelPhy::B_0X4));
});
config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
@ -164,7 +161,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ?
dma.dmacrx_cr().modify(|w| {
w.set_rxpbl(1); // 32 ?
w.set_rbsz(MTU as u16);
w.set_rbsz(RX_BUFFER_SIZE as u16);
});
// NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called

View File

@ -6,7 +6,7 @@ use core::task::{Context, Poll};
use embassy_hal_internal::impl_peripheral;
use embassy_sync::waitqueue::AtomicWaker;
use crate::gpio::{AnyPin, Input, Pin as GpioPin};
use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin};
use crate::pac::exti::regs::Lines;
use crate::pac::EXTI;
use crate::{interrupt, pac, peripherals, Peripheral};
@ -39,6 +39,9 @@ fn exticr_regs() -> pac::afio::Afio {
}
pub unsafe fn on_irq() {
#[cfg(feature = "low-power")]
crate::low_power::on_wakeup_irq();
#[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50)))]
let bits = EXTI.pr(0).read().0;
#[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50))]
@ -98,6 +101,10 @@ impl<'d, T: GpioPin> ExtiInput<'d, T> {
self.pin.is_low()
}
pub fn get_level(&self) -> Level {
self.pin.get_level()
}
pub async fn wait_for_high<'a>(&'a mut self) {
let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false);
if self.is_high() {
@ -364,9 +371,4 @@ pub(crate) unsafe fn init() {
use crate::interrupt::typelevel::Interrupt;
foreach_exti_irq!(enable_irq);
#[cfg(not(any(rcc_wb, rcc_wl5, rcc_wle, stm32f1, exti_h5, exti_h50)))]
<crate::peripherals::SYSCFG as crate::rcc::sealed::RccPeripheral>::enable();
#[cfg(stm32f1)]
<crate::peripherals::AFIO as crate::rcc::sealed::RccPeripheral>::enable();
}

View File

@ -1,6 +1,8 @@
#![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.");
@ -81,14 +83,17 @@ macro_rules! todo {
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> {
self
}
}
#[allow(unused)]
pub(crate) 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

@ -758,6 +758,9 @@ foreach_pin!(
);
pub(crate) unsafe fn init() {
#[cfg(afio)]
<crate::peripherals::AFIO as crate::rcc::sealed::RccPeripheral>::enable();
crate::_generated::init_gpio();
}

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