diff --git a/.cargo/config.toml b/.cargo/config.toml
index 35049cbcb13..9f81e304413 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,2 +1,3 @@
[alias]
xtask = "run --package xtask --"
+xfmt = "xtask fmt-packages"
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index 4a7d24d845d..2f59525cc2f 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -2,16 +2,13 @@ name: Changelog check
on:
pull_request:
- # We will not track changes for the following packages.
+ # We will not track changes for the following packages/directories.
paths-ignore:
- - "/xtask/"
- - "/esp-build/"
- - "/esp-hal-procmacros/"
- - "/esp-metadata/"
- "/examples/"
- - "/hil-tests/"
- "/extras/"
+ - "/hil-tests/"
- "/resources/"
+ - "/xtask/"
# Run on labeled/unlabeled in addition to defaults to detect
# adding/removing skip-changelog labels.
types: [opened, reopened, labeled, unlabeled, synchronize]
@@ -33,24 +30,34 @@ jobs:
- 'esp-alloc/**'
esp-backtrace:
- 'esp-backtrace/**'
+ esp-build:
+ - 'esp-build/**'
+ esp-config:
+ - 'esp-config/**'
esp-hal:
- 'esp-hal/**'
esp-hal-embassy:
- 'esp-hal-embassy/**'
+ esp-hal-procmacros:
+ - 'esp-hal-procmacros/**'
esp-ieee802154:
- 'esp-ieee802154/**'
esp-lp-hal:
- 'esp-lp-hal/**'
+ esp-metadata:
+ - 'esp-metadata/**'
esp-println:
- 'esp-println/**'
esp-riscv-rt:
- 'esp-riscv-rt/**'
- xtensa-lx-rt:
- - 'xtensa-lx-rt/**'
esp-storage:
- 'esp-storage/**'
esp-wifi:
- 'esp-wifi/**'
+ xtensa-lx:
+ - 'xtensa-lx/**'
+ xtensa-lx-rt:
+ - 'xtensa-lx-rt/**'
- name: Check that changelog updated (esp-alloc)
if: steps.changes.outputs.esp-alloc == 'true'
@@ -68,6 +75,22 @@ jobs:
skipLabels: "skip-changelog"
missingUpdateErrorMessage: "Please add a changelog entry in the esp-backtrace/CHANGELOG.md file."
+ - name: Check that changelog updated (esp-build)
+ if: steps.changes.outputs.esp-build == 'true'
+ uses: dangoslen/changelog-enforcer@v3
+ with:
+ changeLogPath: esp-build/CHANGELOG.md
+ skipLabels: "skip-changelog"
+ missingUpdateErrorMessage: "Please add a changelog entry in the esp-build/CHANGELOG.md file."
+
+ - name: Check that changelog updated (esp-config)
+ if: steps.changes.outputs.esp-config == 'true'
+ uses: dangoslen/changelog-enforcer@v3
+ with:
+ changeLogPath: esp-config/CHANGELOG.md
+ skipLabels: "skip-changelog"
+ missingUpdateErrorMessage: "Please add a changelog entry in the esp-config/CHANGELOG.md file."
+
- name: Check that changelog updated (esp-hal)
if: steps.changes.outputs.esp-hal == 'true'
uses: dangoslen/changelog-enforcer@v3
@@ -84,6 +107,14 @@ jobs:
skipLabels: "skip-changelog"
missingUpdateErrorMessage: "Please add a changelog entry in the esp-hal-embassy/CHANGELOG.md file."
+ - name: Check that changelog updated (esp-hal-procmacros)
+ if: steps.changes.outputs.esp-hal-procmacros == 'true'
+ uses: dangoslen/changelog-enforcer@v3
+ with:
+ changeLogPath: esp-hal-procmacros/CHANGELOG.md
+ skipLabels: "skip-changelog"
+ missingUpdateErrorMessage: "Please add a changelog entry in the esp-hal-procmacros/CHANGELOG.md file."
+
- name: Check that changelog updated (esp-ieee802154)
if: steps.changes.outputs.esp-ieee802154 == 'true'
uses: dangoslen/changelog-enforcer@v3
@@ -131,3 +162,19 @@ jobs:
changeLogPath: esp-wifi/CHANGELOG.md
skipLabels: "skip-changelog"
missingUpdateErrorMessage: "Please add a changelog entry in the esp-wifi/CHANGELOG.md file."
+
+ - name: Check that changelog updated (xtensa-lx)
+ if: steps.changes.outputs.xtensa-lx == 'true'
+ uses: dangoslen/changelog-enforcer@v3
+ with:
+ changeLogPath: xtensa-lx/CHANGELOG.md
+ skipLabels: "skip-changelog"
+ missingUpdateErrorMessage: "Please add a changelog entry in the xtensa-lx/CHANGELOG.md file."
+
+ - name: Check that changelog updated (xtensa-lx-rt)
+ if: steps.changes.outputs.xtensa-lx-rt == 'true'
+ uses: dangoslen/changelog-enforcer@v3
+ with:
+ changeLogPath: xtensa-lx-rt/CHANGELOG.md
+ skipLabels: "skip-changelog"
+ missingUpdateErrorMessage: "Please add a changelog entry in the xtensa-lx-rt/CHANGELOG.md file."
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 79f6b994eb0..e8bef8ccac6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -137,10 +137,10 @@ jobs:
- name: msrv RISCV (esp-wifi)
run: |
- cargo xtask build-package --features=esp32c2,wifi,ble,async --target=riscv32imc-unknown-none-elf esp-wifi
- cargo xtask build-package --features=esp32c3,wifi,ble,async --target=riscv32imc-unknown-none-elf esp-wifi
- cargo xtask build-package --features=esp32c6,wifi,ble,async --target=riscv32imac-unknown-none-elf esp-wifi
- cargo xtask build-package --features=esp32h2,ble,async --target=riscv32imac-unknown-none-elf esp-wifi
+ cargo xtask build-package --features=esp32c2,wifi,ble --target=riscv32imc-unknown-none-elf esp-wifi
+ cargo xtask build-package --features=esp32c3,wifi,ble --target=riscv32imc-unknown-none-elf esp-wifi
+ cargo xtask build-package --features=esp32c6,wifi,ble --target=riscv32imac-unknown-none-elf esp-wifi
+ cargo xtask build-package --features=esp32h2,ble --target=riscv32imac-unknown-none-elf esp-wifi
# Verify the MSRV for all Xtensa chips:
- name: msrv Xtensa (esp-hal)
@@ -151,9 +151,9 @@ jobs:
- name: msrv Xtensa (esp-wifi)
run: |
- cargo xtask build-package --toolchain=esp --features=esp32,wifi,ble,async --target=xtensa-esp32-none-elf esp-wifi
- cargo xtask build-package --toolchain=esp --features=esp32s2,wifi,async --target=xtensa-esp32s2-none-elf esp-wifi
- cargo xtask build-package --toolchain=esp --features=esp32s3,wifi,ble,async --target=xtensa-esp32s3-none-elf esp-wifi
+ cargo xtask build-package --toolchain=esp --features=esp32,wifi,ble --target=xtensa-esp32-none-elf esp-wifi
+ cargo xtask build-package --toolchain=esp --features=esp32s2,wifi --target=xtensa-esp32s2-none-elf esp-wifi
+ cargo xtask build-package --toolchain=esp --features=esp32s3,wifi,ble --target=xtensa-esp32s3-none-elf esp-wifi
- name: msrv (esp-lp-hal)
run: |
diff --git a/documentation/API-GUIDELINES.md b/documentation/API-GUIDELINES.md
index b2e18e13eda..c0336070789 100644
--- a/documentation/API-GUIDELINES.md
+++ b/documentation/API-GUIDELINES.md
@@ -15,9 +15,21 @@ The following paragraphs contain additional recommendations.
## Construction and Destruction of Drivers
-- Drivers take peripherals and pins via the `PeripheralRef` pattern - they don't consume peripherals/pins.
+- Drivers should take peripherals via the `PeripheralRef` pattern - they don't consume peripherals directly.
+- If a driver requires pins, those pins should be configured using `fn with_signal_name(self, pin: impl Peripheral
+ 'd) -> Self)` or `fn with_signal_name(self, pin: impl Peripheral
+ 'd) -> Self)`
+- If a driver supports multiple peripheral instances (for example, I2C0 is one such instance):
+ - The peripheral instance type should be positioned as the last type parameter of the driver type.
+ - The peripheral instance type should default to a type that supports any of the peripheral instances.
+ - The author is encouraged to use `crate::any_peripheral` to define the "any" peripheral instance type.
+ - The driver should implement a `new` constructor that automatically converts the peripheral instance into the any type, and a `new_typed` that preserves the peripheral type.
+- If a driver only supports a single peripheral instance, no instance type parameter is necessary.
+- If a driver implements both blocking and async operations, or only implements blocking operations, but may support asynchronous ones in the future, the driver's type signature should include a `crate::Mode` type parameter.
+- By default, constructors should configure the driver for blocking mode. The driver should implement `into_async` (and a matching `into_blocking`) function that reconfigures the driver.
+ - `into_async` should configure the driver and/or the associated DMA channels. This most often means enabling an interrupt handler.
+ - `into_blocking` should undo the configuration done by `into_async`.
+- The asynchronous driver implemntation should also expose the blocking methods (except for interrupt related functions).
- Consider adding a `Drop` implementation resetting the peripheral to idle state.
-- Consider using a builder-like pattern for configuration which must be done during initialization.
+- Consider using a builder-like pattern for driver construction.
## Interoperability
@@ -25,6 +37,7 @@ The following paragraphs contain additional recommendations.
- see [this example](https://github.com/esp-rs/esp-hal/blob/df2b7bd8472cc1d18db0d9441156575570f59bb3/esp-hal/src/spi/mod.rs#L15)
- e.g. `#[cfg_attr(feature = "defmt", derive(defmt::Format))]`
- Don't use `log::XXX!` macros directly - use the wrappers in `fmt.rs` (e.g. just `info!` instead of `log::info!` or importing `log::*`)!
+- Consider implementing common ecosystem traits, like the ones in `embedded-hal` or `embassy-embedded-hal`.
## API Surface
@@ -45,6 +58,9 @@ The following paragraphs contain additional recommendations.
- For example starting a timer is fine for `&self`, worst case a timer will be started twice if two parts of the program call it. You can see a real example of this [here](https://github.com/esp-rs/esp-hal/pull/1500#pullrequestreview-2015911974)
- Maintain order consistency in the API, such as in the case of pairs like RX/TX.
- If your driver provides a way to listen for interrupts, the interrupts should be listed in a `derive(EnumSetType)` enum as opposed to one function per interrupt flag.
+- If a driver only implements a subset of a peripheral's capabilities, it should be placed in the `peripheral::subcategory` module.
+ - For example, if a driver implements the slave-mode I2C driver, it should be placed into `i2c::slave`.
+ - This helps us reducing the need of introducing breaking changes if we implement additional functionalities.
## Maintainability
@@ -56,6 +72,15 @@ The following paragraphs contain additional recommendations.
- All `Future` objects (public or private) must be marked with ``#[must_use = "futures do nothing unless you `.await` or poll them"]``.
- Prefer `cfg_if!` over multiple exclusive `#[cfg]` attributes. `cfg_if!` visually divides the options, often results in simpler conditions and simplifies adding new branches in the future.
+## Driver implementation
+
+- If a common `Instance` trait is used for multiple peripherals, those traits should not have any logic implemented in them.
+- The `Instance` traits should only be used to access information about a peripheral instance.
+- The internal implementation of the driver should be non-generic over the peripheral instance. This helps the compiler produce smaller code.
+- The author is encouraged to return a static shared reference to an `Info` and a `State` structure from the `Instance` trait.
+ - The `Info` struct should describe the peripheral. Do not use any interior mutability.
+ - The `State` struct should contain counters, wakers and other, mutable state. As this is accessed via a shared reference, interior mutability and atomic variables are preferred.
+
## Modules Documentation
Modules should have the following documentation format:
@@ -88,4 +113,5 @@ Modules should have the following documentation format:
```
#![doc = concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", crate::soc::chip!(), "/api-reference/peripherals/etm.html)")]
```
-- Documentation examples should be short and basic, for more complex scenarios, create an example.
+ - In case of referencing a TRM chapter, use the `crate::trm_markdown_link!()` macro. If you are referring to a particular chapter, you may use `crate::trm_markdown_link!("#chapter_anchor")`.
+- Documentation examples should be short and basic. For more complex scenarios, create an example.
diff --git a/esp-build/CHANGELOG.md b/esp-build/CHANGELOG.md
new file mode 100644
index 00000000000..50b75908901
--- /dev/null
+++ b/esp-build/CHANGELOG.md
@@ -0,0 +1,24 @@
+# Changelog
+
+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.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+### Fixed
+
+### Changed
+
+- Use `panic` instead of `process::exit` in esp-build (#2402 )
+
+### Removed
+
+## [0.1.0] - 2024-04-17
+
+- Initial release
+
+[Unreleased]: https://github.com/esp-rs/esp-hal/commits/main/esp-build?since=2024-04-17
diff --git a/esp-config/CHANGELOG.md b/esp-config/CHANGELOG.md
new file mode 100644
index 00000000000..4066e9b0323
--- /dev/null
+++ b/esp-config/CHANGELOG.md
@@ -0,0 +1,22 @@
+# Changelog
+
+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.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+### Fixed
+
+### Changed
+
+### Removed
+
+## [0.1.0] - 2024-10-10
+
+- Initial release
+
+[Unreleased]: https://github.com/esp-rs/esp-hal/commits/main/esp-config?since=2024-10-10
diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs
index 1afa1c92591..ede216f5d27 100644
--- a/esp-config/src/generate.rs
+++ b/esp-config/src/generate.rs
@@ -1,77 +1,246 @@
-use core::fmt::Write;
-use std::{collections::HashMap, env, fs, path::PathBuf};
+use std::{
+ collections::HashMap,
+ env,
+ fmt::{self, Write as _},
+ fs,
+ ops::Range,
+ path::PathBuf,
+};
const DOC_TABLE_HEADER: &str = r#"
| Name | Description | Default value |
|------|-------------|---------------|
"#;
-const CHOSEN_TABLE_HEADER: &str = r#"
+
+const SELECTED_TABLE_HEADER: &str = r#"
| Name | Selected value |
|------|----------------|
"#;
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct ParseError(String);
+/// Configuration errors.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Error {
+ /// Parse errors.
+ Parse(String),
+ /// Validation errors.
+ Validation(String),
+}
+
+impl Error {
+ /// Convenience function for creating parse errors.
+ pub fn parse(message: S) -> Self
+ where
+ S: Into,
+ {
+ Self::Parse(message.into())
+ }
+
+ /// Convenience function for creating validation errors.
+ pub fn validation(message: S) -> Self
+ where
+ S: Into,
+ {
+ Self::Validation(message.into())
+ }
+}
-#[derive(Clone, Debug, PartialEq, Eq)]
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::Parse(message) => write!(f, "{message}"),
+ Error::Validation(message) => write!(f, "{message}"),
+ }
+ }
+}
+
+/// Supported configuration value types.
+#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
- SignedInteger(isize),
- UnsignedInteger(usize),
+ /// Booleans.
Bool(bool),
+ /// Integers.
+ Integer(i128),
+ /// Strings.
String(String),
}
+// TODO: Do we want to handle negative values for non-decimal values?
impl Value {
- fn parse_in_place(&mut self, s: &str) -> Result<(), ParseError> {
+ fn parse_in_place(&mut self, s: &str) -> Result<(), Error> {
*self = match self {
- Value::Bool(_) => match s.to_lowercase().as_ref() {
- "false" | "no" | "n" | "f" => Value::Bool(false),
- "true" | "yes" | "y" | "t" => Value::Bool(true),
- _ => return Err(ParseError(format!("Invalid boolean value: {}", s))),
- },
- Value::SignedInteger(_) => Value::SignedInteger(
- match s.as_bytes() {
- [b'0', b'x', ..] => isize::from_str_radix(&s[2..], 16),
- [b'0', b'o', ..] => isize::from_str_radix(&s[2..], 8),
- [b'0', b'b', ..] => isize::from_str_radix(&s[2..], 2),
- _ => isize::from_str_radix(&s, 10),
+ Value::Bool(_) => match s {
+ "true" => Value::Bool(true),
+ "false" => Value::Bool(false),
+ _ => {
+ return Err(Error::parse(format!(
+ "Expected 'true' or 'false', found: '{s}'"
+ )))
}
- .map_err(|_| ParseError(format!("Invalid signed numerical value: {}", s)))?,
- ),
- Value::UnsignedInteger(_) => Value::UnsignedInteger(
- match s.as_bytes() {
- [b'0', b'x', ..] => usize::from_str_radix(&s[2..], 16),
- [b'0', b'o', ..] => usize::from_str_radix(&s[2..], 8),
- [b'0', b'b', ..] => usize::from_str_radix(&s[2..], 2),
- _ => usize::from_str_radix(&s, 10),
+ },
+ Value::Integer(_) => {
+ let inner = match s.as_bytes() {
+ [b'0', b'x', ..] => i128::from_str_radix(&s[2..], 16),
+ [b'0', b'o', ..] => i128::from_str_radix(&s[2..], 8),
+ [b'0', b'b', ..] => i128::from_str_radix(&s[2..], 2),
+ _ => i128::from_str_radix(&s, 10),
}
- .map_err(|_| ParseError(format!("Invalid unsigned numerical value: {}", s)))?,
- ),
- Value::String(_) => Value::String(String::from(s)),
+ .map_err(|_| Error::parse(format!("Expected valid intger value, found: '{s}'")))?;
+
+ Value::Integer(inner)
+ }
+ Value::String(_) => Value::String(s.into()),
};
+
Ok(())
}
- fn as_string(&self) -> String {
+ /// Convert the value to a [bool].
+ pub fn as_bool(&self) -> bool {
+ match self {
+ Value::Bool(value) => *value,
+ _ => panic!("attempted to convert non-bool value to a bool"),
+ }
+ }
+
+ /// Convert the value to an [i128].
+ pub fn as_integer(&self) -> i128 {
+ match self {
+ Value::Integer(value) => *value,
+ _ => panic!("attempted to convert non-integer value to an integer"),
+ }
+ }
+
+ /// Convert the value to a [String].
+ pub fn as_string(&self) -> String {
+ match self {
+ Value::String(value) => value.to_owned(),
+ _ => panic!("attempted to convert non-string value to a string"),
+ }
+ }
+
+ /// Is the value a bool?
+ pub fn is_bool(&self) -> bool {
+ matches!(self, Value::Bool(_))
+ }
+
+ /// Is the value an integer?
+ pub fn is_integer(&self) -> bool {
+ matches!(self, Value::Integer(_))
+ }
+
+ /// Is the value a string?
+ pub fn is_string(&self) -> bool {
+ matches!(self, Value::String(_))
+ }
+}
+
+impl fmt::Display for Value {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Value::Bool(b) => write!(f, "{b}"),
+ Value::Integer(i) => write!(f, "{i}"),
+ Value::String(s) => write!(f, "{s}"),
+ }
+ }
+}
+
+/// Configuration value validation functions.
+pub enum Validator {
+ /// Only allow negative integers, i.e. any values less than 0.
+ NegativeInteger,
+ /// Only allow non-negative integers, i.e. any values greater than or equal
+ /// to 0.
+ NonNegativeInteger,
+ /// Only allow positive integers, i.e. any values greater than to 0.
+ PositiveInteger,
+ /// Ensure that an integer value falls within the specified range.
+ IntegerInRange(Range),
+ /// A custom validation function to run against any supported value type.
+ Custom(Box Result<(), Error>>),
+}
+
+impl Validator {
+ fn validate(&self, value: &Value) -> Result<(), Error> {
match self {
- Value::Bool(value) => String::from(if *value { "true" } else { "false" }),
- Value::SignedInteger(value) => format!("{}", value),
- Value::UnsignedInteger(value) => format!("{}", value),
- Value::String(value) => value.clone(),
+ Validator::NegativeInteger => negative_integer(value)?,
+ Validator::NonNegativeInteger => non_negative_integer(value)?,
+ Validator::PositiveInteger => positive_integer(value)?,
+ Validator::IntegerInRange(range) => integer_in_range(range, value)?,
+ Validator::Custom(validator_fn) => validator_fn(value)?,
}
+
+ Ok(())
}
}
-/// Generate and parse config from a prefix, and array of key, default,
-/// description tuples.
+fn negative_integer(value: &Value) -> Result<(), Error> {
+ if !value.is_integer() {
+ return Err(Error::validation(
+ "Validator::NegativeInteger can only be used with integer values",
+ ));
+ } else if value.as_integer() >= 0 {
+ return Err(Error::validation(format!(
+ "Expected negative integer, found '{}'",
+ value.as_integer()
+ )));
+ }
+
+ Ok(())
+}
+
+fn non_negative_integer(value: &Value) -> Result<(), Error> {
+ if !value.is_integer() {
+ return Err(Error::validation(
+ "Validator::NonNegativeInteger can only be used with integer values",
+ ));
+ } else if value.as_integer() < 0 {
+ return Err(Error::validation(format!(
+ "Expected non-negative integer, found '{}'",
+ value.as_integer()
+ )));
+ }
+
+ Ok(())
+}
+
+fn positive_integer(value: &Value) -> Result<(), Error> {
+ if !value.is_integer() {
+ return Err(Error::validation(
+ "Validator::PositiveInteger can only be used with integer values",
+ ));
+ } else if value.as_integer() <= 0 {
+ return Err(Error::validation(format!(
+ "Expected positive integer, found '{}'",
+ value.as_integer()
+ )));
+ }
+
+ Ok(())
+}
+
+fn integer_in_range(range: &Range, value: &Value) -> Result<(), Error> {
+ if !value.is_integer() || !range.contains(&value.as_integer()) {
+ Err(Error::validation(format!(
+ "Value '{}' does not fall within range '{:?}'",
+ value, range
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+/// Generate and parse config from a prefix, and an array tuples containing the
+/// name, description, default value, and an optional validator.
///
/// This function will parse any `SCREAMING_SNAKE_CASE` environment variables
-/// that match the given prefix. It will then attempt to parse the [`Value`].
+/// that match the given prefix. It will then attempt to parse the [`Value`] and
+/// run any validators which have been specified.
+///
/// Once the config has been parsed, this function will emit `snake_case` cfg's
/// _without_ the prefix which can be used in the dependant crate. After that,
/// it will create a markdown table in the `OUT_DIR` under the name
-/// `{prefix}_config_table.md` where prefix has also been converted
-/// to `snake_case`. This can be included in crate documentation to outline the
+/// `{prefix}_config_table.md` where prefix has also been converted to
+/// `snake_case`. This can be included in crate documentation to outline the
/// available configuration options for the crate.
///
/// Passing a value of true for the `emit_md_tables` argument will create and
@@ -81,22 +250,46 @@ impl Value {
/// Unknown keys with the supplied prefix will cause this function to panic.
pub fn generate_config(
prefix: &str,
- config: &[(&str, Value, &str)],
+ config: &[(&str, &str, Value, Option)],
emit_md_tables: bool,
) -> HashMap {
- // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
+ // Only rebuild if `build.rs` changed. Otherwise, Cargo will rebuild if any
// other file changed.
println!("cargo:rerun-if-changed=build.rs");
+
#[cfg(not(test))]
env_change_work_around();
- // ensure that the prefix is `SCREAMING_SNAKE_CASE`
- let prefix = format!("{}_", screaming_snake_case(prefix));
let mut doc_table = String::from(DOC_TABLE_HEADER);
- let mut selected_config = String::from(CHOSEN_TABLE_HEADER);
+ let mut selected_config = String::from(SELECTED_TABLE_HEADER);
+
+ // Ensure that the prefix is `SCREAMING_SNAKE_CASE`:
+ let prefix = screaming_snake_case(prefix);
+
+ // Build a lookup table for any provided validators; we must prefix the
+ // name of the config and transform it to SCREAMING_SNAKE_CASE so that
+ // it matches the keys in the hash table produced by `create_config`.
+ let config_validators = config
+ .iter()
+ .flat_map(|(name, _description, _default, validator)| {
+ if let Some(validator) = validator {
+ let name = format!("{prefix}_{}", screaming_snake_case(name));
+ Some((name, validator))
+ } else {
+ None
+ }
+ })
+ .collect::>();
let mut configs = create_config(&prefix, config, &mut doc_table);
capture_from_env(&prefix, &mut configs);
+
+ for (name, value) in configs.iter() {
+ if let Some(validator) = config_validators.get(name) {
+ validator.validate(value).unwrap();
+ }
+ }
+
emit_configuration(&prefix, &configs, &mut selected_config);
if emit_md_tables {
@@ -118,8 +311,7 @@ fn env_change_work_around() {
// target
while !out_dir.ends_with("target") {
if !out_dir.pop() {
- // We ran out of directories...
- return;
+ return; // We ran out of directories...
}
}
out_dir.pop();
@@ -141,44 +333,42 @@ fn env_change_work_around() {
}
}
-fn emit_configuration(
+fn create_config(
prefix: &str,
- configs: &HashMap,
- selected_config: &mut String,
-) {
- // emit cfgs and set envs
- for (name, value) in configs.into_iter() {
- let cfg_name = snake_case(name.trim_start_matches(prefix));
- println!("cargo:rustc-check-cfg=cfg({cfg_name})");
- match value {
- Value::Bool(true) => {
- println!("cargo:rustc-cfg={cfg_name}")
- }
- _ => {}
- }
+ config: &[(&str, &str, Value, Option)],
+ doc_table: &mut String,
+) -> HashMap {
+ let mut configs = HashMap::new();
- let value = value.as_string();
- // values that haven't been seen will be output here with the default value
- println!("cargo:rustc-env={}={}", name, value);
+ for (name, description, default, _validator) in config {
+ let name = format!("{prefix}_{}", screaming_snake_case(name));
+ configs.insert(name.clone(), default.clone());
- writeln!(selected_config, "|**{name}**|{value}|").unwrap();
+ // Write documentation table line:
+ let default = default.to_string();
+ writeln!(doc_table, "|**{name}**|{description}|{default}|").unwrap();
+
+ // Rebuild if config environment variable changed:
+ println!("cargo:rerun-if-env-changed={name}");
}
+
+ configs
}
fn capture_from_env(prefix: &str, configs: &mut HashMap) {
let mut unknown = Vec::new();
let mut failed = Vec::new();
- // Try and capture input from the environment
+ // Try and capture input from the environment:
for (var, value) in env::vars() {
- if let Some(_) = var.strip_prefix(prefix) {
+ if var.strip_prefix(prefix).is_some() {
let Some(cfg) = configs.get_mut(&var) else {
unknown.push(var);
continue;
};
if let Err(e) = cfg.parse_in_place(&value) {
- failed.push(format!("{}: {e:?}", var));
+ failed.push(format!("{var}: {e}"));
}
}
}
@@ -192,61 +382,54 @@ fn capture_from_env(prefix: &str, configs: &mut HashMap) {
}
}
-fn create_config(
+fn emit_configuration(
prefix: &str,
- config: &[(&str, Value, &str)],
- doc_table: &mut String,
-) -> HashMap {
- // Generate the template for the config
- let mut configs = HashMap::new();
- for (name, default, desc) in config {
- let name = format!("{prefix}{}", screaming_snake_case(&name));
- configs.insert(name.clone(), default.clone());
+ configs: &HashMap,
+ selected_config: &mut String,
+) {
+ for (name, value) in configs.iter() {
+ let cfg_name = snake_case(name.trim_start_matches(&format!("{prefix}_")));
+ println!("cargo:rustc-check-cfg=cfg({cfg_name})");
- // write doc table line
- let default = default.as_string();
- writeln!(doc_table, "|**{name}**|{desc}|{default}|").unwrap();
+ if let Value::Bool(true) = value {
+ println!("cargo:rustc-cfg={cfg_name}");
+ }
- // Rebuild if config envvar changed.
- println!("cargo:rerun-if-env-changed={name}");
- }
+ let value = value.to_string();
- configs
+ // Values that haven't been seen will be output here with the default value:
+ println!("cargo:rustc-env={}={}", name, value);
+ writeln!(selected_config, "|**{name}**|{value}|").unwrap();
+ }
}
fn write_config_tables(prefix: &str, doc_table: String, selected_config: String) {
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+
let out_file = out_dir
- .join(format!("{prefix}config_table.md"))
- .to_string_lossy()
+ .join(format!("{prefix}_config_table.md"))
+ .display()
.to_string();
fs::write(out_file, doc_table).unwrap();
- let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_file = out_dir
- .join(format!("{prefix}selected_config.md"))
- .to_string_lossy()
+ .join(format!("{prefix}_selected_config.md"))
+ .display()
.to_string();
fs::write(out_file, selected_config).unwrap();
}
-// Converts a symbol name like
-// "PLACE-spi_DRIVER-IN_ram"
-// to
-// "place_spi_driver_in_ram"
fn snake_case(name: &str) -> String {
let mut name = name.replace("-", "_");
name.make_ascii_lowercase();
+
name
}
-// Converts a symbol name like
-// "PLACE-spi_DRIVER-IN_ram"
-// to
-// "PLACE_SPI_DRIVER_IN_RAM"
fn screaming_snake_case(name: &str) -> String {
let mut name = name.replace("-", "_");
name.make_ascii_uppercase();
+
name
}
@@ -257,32 +440,24 @@ mod test {
#[test]
fn value_number_formats() {
const INPUTS: &[&str] = &["0xAA", "0o252", "0b0000000010101010", "170"];
- let mut v = Value::SignedInteger(0);
+ let mut v = Value::Integer(0);
for input in INPUTS {
v.parse_in_place(input).unwrap();
// no matter the input format, the output format should be decimal
- assert_eq!(v.as_string(), "170");
+ assert_eq!(format!("{v}"), "170");
}
}
#[test]
fn value_bool_inputs() {
- const TRUE_INPUTS: &[&str] = &["true", "y", "yes"];
- const FALSE_INPUTS: &[&str] = &["false", "n", "no"];
let mut v = Value::Bool(false);
- for input in TRUE_INPUTS {
- v.parse_in_place(input).unwrap();
- // no matter the input variant, the output format should be "true"
- assert_eq!(v.as_string(), "true");
- }
+ v.parse_in_place("true").unwrap();
+ assert_eq!(format!("{v}"), "true");
- for input in FALSE_INPUTS {
- v.parse_in_place(input).unwrap();
- // no matter the input variant, the output format should be "false"
- assert_eq!(v.as_string(), "false");
- }
+ v.parse_in_place("false").unwrap();
+ assert_eq!(format!("{v}"), "false");
}
#[test]
@@ -298,13 +473,18 @@ mod test {
let configs = generate_config(
"esp-test",
&[
- ("number", Value::UnsignedInteger(999), "NA"),
- ("number_signed", Value::SignedInteger(-777), "NA"),
- ("string", Value::String("Demo".to_owned()), "NA"),
- ("bool", Value::Bool(false), "NA"),
- ("number_default", Value::UnsignedInteger(999), "NA"),
- ("string_default", Value::String("Demo".to_owned()), "NA"),
- ("bool_default", Value::Bool(false), "NA"),
+ ("number", "NA", Value::Integer(999), None),
+ ("number_signed", "NA", Value::Integer(-777), None),
+ ("string", "NA", Value::String("Demo".to_owned()), None),
+ ("bool", "NA", Value::Bool(false), None),
+ ("number_default", "NA", Value::Integer(999), None),
+ (
+ "string_default",
+ "NA",
+ Value::String("Demo".to_owned()),
+ None,
+ ),
+ ("bool_default", "NA", Value::Bool(false), None),
],
false,
);
@@ -312,14 +492,14 @@ mod test {
// some values have changed
assert_eq!(
match configs.get("ESP_TEST_NUMBER").unwrap() {
- Value::UnsignedInteger(num) => *num,
+ Value::Integer(num) => *num,
_ => unreachable!(),
},
0xaa
);
assert_eq!(
match configs.get("ESP_TEST_NUMBER_SIGNED").unwrap() {
- Value::SignedInteger(num) => *num,
+ Value::Integer(num) => *num,
_ => unreachable!(),
},
-999
@@ -342,7 +522,7 @@ mod test {
// the rest are the defaults
assert_eq!(
match configs.get("ESP_TEST_NUMBER_DEFAULT").unwrap() {
- Value::UnsignedInteger(num) => *num,
+ Value::Integer(num) => *num,
_ => unreachable!(),
},
999
@@ -365,6 +545,114 @@ mod test {
)
}
+ #[test]
+ fn builtin_validation_passes() {
+ temp_env::with_vars(
+ [
+ ("ESP_TEST_POSITIVE_NUMBER", Some("7")),
+ ("ESP_TEST_NEGATIVE_NUMBER", Some("-1")),
+ ("ESP_TEST_NON_NEGATIVE_NUMBER", Some("0")),
+ ("ESP_TEST_RANGE", Some("9")),
+ ],
+ || {
+ generate_config(
+ "esp-test",
+ &[
+ (
+ "positive_number",
+ "NA",
+ Value::Integer(-1),
+ Some(Validator::PositiveInteger),
+ ),
+ (
+ "negative_number",
+ "NA",
+ Value::Integer(1),
+ Some(Validator::NegativeInteger),
+ ),
+ (
+ "non_negative_number",
+ "NA",
+ Value::Integer(-1),
+ Some(Validator::NonNegativeInteger),
+ ),
+ (
+ "range",
+ "NA",
+ Value::Integer(0),
+ Some(Validator::IntegerInRange(5..10)),
+ ),
+ ],
+ false,
+ )
+ },
+ );
+ }
+
+ #[test]
+ fn custom_validation_passes() {
+ temp_env::with_vars([("ESP_TEST_NUMBER", Some("13"))], || {
+ generate_config(
+ "esp-test",
+ &[(
+ "number",
+ "NA",
+ Value::Integer(-1),
+ Some(Validator::Custom(Box::new(|value| {
+ let range = 10..20;
+ if !value.is_integer() || !range.contains(&value.as_integer()) {
+ Err(Error::validation("value does not fall within range"))
+ } else {
+ Ok(())
+ }
+ }))),
+ )],
+ false,
+ )
+ });
+ }
+
+ #[test]
+ #[should_panic]
+ fn builtin_validation_bails() {
+ temp_env::with_vars([("ESP_TEST_POSITIVE_NUMBER", Some("-99"))], || {
+ generate_config(
+ "esp-test",
+ &[(
+ "positive_number",
+ "NA",
+ Value::Integer(-1),
+ Some(Validator::PositiveInteger),
+ )],
+ false,
+ )
+ });
+ }
+
+ #[test]
+ #[should_panic]
+ fn custom_validation_bails() {
+ temp_env::with_vars([("ESP_TEST_NUMBER", Some("37"))], || {
+ generate_config(
+ "esp-test",
+ &[(
+ "number",
+ "NA",
+ Value::Integer(-1),
+ Some(Validator::Custom(Box::new(|value| {
+ let range = 10..20;
+ if !value.is_integer() || !range.contains(&value.as_integer()) {
+ Err(Error::validation("value does not fall within range"))
+ } else {
+ Ok(())
+ }
+ }))),
+ )],
+ false,
+ )
+ });
+ }
+
#[test]
#[should_panic]
fn env_unknown_bails() {
@@ -376,7 +664,7 @@ mod test {
|| {
generate_config(
"esp-test",
- &[("number", Value::UnsignedInteger(999), "NA")],
+ &[("number", "NA", Value::Integer(999), None)],
false,
);
},
@@ -389,7 +677,7 @@ mod test {
temp_env::with_vars([("ESP_TEST_NUMBER", Some("Hello world"))], || {
generate_config(
"esp-test",
- &[("number", Value::UnsignedInteger(999), "NA")],
+ &[("number", "NA", Value::Integer(999), None)],
false,
);
});
diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs
index 941c420692e..2ef0c18f9a9 100644
--- a/esp-config/src/lib.rs
+++ b/esp-config/src/lib.rs
@@ -3,17 +3,30 @@
#![doc = document_features::document_features!(feature_label = r#"{feature}
"#)]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![cfg_attr(not(feature = "build"), no_std)]
+#![deny(missing_docs, rust_2018_idioms)]
#[cfg(feature = "build")]
mod generate;
#[cfg(feature = "build")]
-pub use generate::*;
+pub use generate::{generate_config, Error, Validator, Value};
+/// Parse the value of an environment variable as a [bool] at compile time.
+#[macro_export]
+macro_rules! esp_config_bool {
+ ( $var:expr ) => {
+ match env!($var).as_bytes() {
+ b"true" => true,
+ b"false" => false,
+ _ => ::core::panic!("boolean value must be either 'true' or 'false'"),
+ }
+ };
+}
+
+// TODO: From 1.82 on, we can use `<$ty>::from_str_radix(env!($var), 10)`
+/// Parse the value of an environment variable as an integer at compile time.
#[macro_export]
-// TODO from 1.82 we can use <$ty>::from_str_radix(env!($var), 10) instead
-/// Parse the value of a `env` variable as an integer at compile time
macro_rules! esp_config_int {
- ($ty:ty, $var:expr) => {
+ ( $ty:ty, $var:expr ) => {
const {
const BYTES: &[u8] = env!($var).as_bytes();
esp_config_int_parse!($ty, BYTES)
@@ -21,63 +34,53 @@ macro_rules! esp_config_int {
};
}
+/// Get the string value of an environment variable at compile time.
#[macro_export]
-/// Get the string value of an `env` variable at compile time
macro_rules! esp_config_str {
- ($var:expr) => {
+ ( $var:expr ) => {
env!($var)
};
}
-#[macro_export]
-/// Parse the value of a `env` variable as an bool at compile time
-macro_rules! esp_config_bool {
- ($var:expr) => {
- match env!($var).as_bytes() {
- b"false" => false,
- _ => true,
- }
- };
-}
-
-#[macro_export]
-#[doc(hidden)] // to avoid confusion with esp_config_int, let's hide this
/// Parse a string like "777" into an integer, which _can_ be used in a `const`
/// context
+#[doc(hidden)] // To avoid confusion with `esp_config_int`, hide this in the docs
+#[macro_export]
macro_rules! esp_config_int_parse {
- ($ty:ty, $bytes:expr) => {{
+ ( $ty:ty, $bytes:expr ) => {{
let mut bytes = $bytes;
let mut val: $ty = 0;
let mut sign_seen = false;
let mut is_negative = false;
+
while let [byte, rest @ ..] = bytes {
match *byte {
b'0'..=b'9' => {
val = val * 10 + (*byte - b'0') as $ty;
}
b'-' | b'+' if !sign_seen => {
- if *byte == b'-' {
- is_negative = true;
- }
+ is_negative = *byte == b'-';
sign_seen = true;
}
- _ => ::core::panic!("invalid digit"),
+ _ => ::core::panic!("invalid character encountered while parsing integer"),
}
+
bytes = rest;
}
+
if is_negative {
let original = val;
- // subtract twice to get the negative
+ // Subtract the value twice to get a negative:
val -= original;
val -= original;
}
+
val
}};
}
#[cfg(test)]
mod test {
-
// We can only test success in the const context
const _: () = {
core::assert!(esp_config_int_parse!(i64, "-77777".as_bytes()) == -77777);
@@ -98,4 +101,10 @@ mod test {
fn test_expect_positive() {
esp_config_int_parse!(u8, "-5".as_bytes());
}
+
+ #[test]
+ #[should_panic]
+ fn test_invalid_digit() {
+ esp_config_int_parse!(u32, "a".as_bytes());
+ }
}
diff --git a/esp-hal-embassy/CHANGELOG.md b/esp-hal-embassy/CHANGELOG.md
index c3cad863938..3449b640ed2 100644
--- a/esp-hal-embassy/CHANGELOG.md
+++ b/esp-hal-embassy/CHANGELOG.md
@@ -9,12 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- `ESP_HAL_EMBASSY_LOW_POWER_WAIT` configuration option. (#2329)
+
### Changed
- Reduce memory footprint by 4 bytes on multi-core MCUs.
### Fixed
+- Alarm interrupts are now handled on the core that allocated them. (For executors created on the second core after calling `esp_hal_embassy::init`) (#2451)
+
### Removed
## 0.4.0 - 2024-10-10
diff --git a/esp-hal-embassy/Cargo.toml b/esp-hal-embassy/Cargo.toml
index ffac4445831..d4827afca27 100644
--- a/esp-hal-embassy/Cargo.toml
+++ b/esp-hal-embassy/Cargo.toml
@@ -25,6 +25,7 @@ static_cell = "2.1.0"
[build-dependencies]
esp-build = { version = "0.1.0", path = "../esp-build" }
+esp-config = { version = "0.1.0", path = "../esp-config", features = ["build"] }
esp-metadata = { version = "0.4.0", path = "../esp-metadata" }
[features]
diff --git a/esp-hal-embassy/build.rs b/esp-hal-embassy/build.rs
index 1c7ecfc2ff6..5a4cf918864 100644
--- a/esp-hal-embassy/build.rs
+++ b/esp-hal-embassy/build.rs
@@ -1,6 +1,7 @@
use std::{error::Error, str::FromStr};
use esp_build::assert_unique_used_features;
+use esp_config::{generate_config, Value};
use esp_metadata::{Chip, Config};
fn main() -> Result<(), Box> {
@@ -37,5 +38,17 @@ fn main() -> Result<(), Box> {
// Define all necessary configuration symbols for the configured device:
config.define_symbols();
+ // emit config
+ generate_config(
+ "esp_hal_embassy",
+ &[(
+ "low-power-wait",
+ "Enables the lower-power wait if no tasks are ready to run on the thread-mode executor. This allows the MCU to use less power if the workload allows. Recommended for battery-powered systems. May impact analog performance.",
+ Value::Bool(true),
+ None
+ )],
+ true,
+ );
+
Ok(())
}
diff --git a/esp-hal-embassy/src/executor/interrupt.rs b/esp-hal-embassy/src/executor/interrupt.rs
index 28ce6ccd574..7f3599127c2 100644
--- a/esp-hal-embassy/src/executor/interrupt.rs
+++ b/esp-hal-embassy/src/executor/interrupt.rs
@@ -4,8 +4,8 @@ use core::{cell::UnsafeCell, mem::MaybeUninit};
use embassy_executor::{raw, SendSpawner};
use esp_hal::{
- get_core,
interrupt::{self, software::SoftwareInterrupt, InterruptHandler},
+ Cpu,
};
use portable_atomic::{AtomicUsize, Ordering};
@@ -87,7 +87,7 @@ impl InterruptExecutor {
.core
.compare_exchange(
usize::MAX,
- get_core() as usize,
+ Cpu::current() as usize,
Ordering::Acquire,
Ordering::Relaxed,
)
diff --git a/esp-hal-embassy/src/executor/thread.rs b/esp-hal-embassy/src/executor/thread.rs
index 4af62ba3ecb..3539a36d204 100644
--- a/esp-hal-embassy/src/executor/thread.rs
+++ b/esp-hal-embassy/src/executor/thread.rs
@@ -3,9 +3,10 @@
use core::marker::PhantomData;
use embassy_executor::{raw, Spawner};
-use esp_hal::{get_core, Cpu};
+use esp_hal::Cpu;
#[cfg(multi_core)]
use esp_hal::{interrupt::software::SoftwareInterrupt, macros::handler};
+#[cfg(low_power_wait)]
use portable_atomic::{AtomicBool, Ordering};
pub(crate) const THREAD_MODE_CONTEXT: usize = 16;
@@ -15,7 +16,7 @@ pub(crate) const THREAD_MODE_CONTEXT: usize = 16;
static SIGNAL_WORK_THREAD_MODE: [AtomicBool; Cpu::COUNT] =
[const { AtomicBool::new(false) }; Cpu::COUNT];
-#[cfg(multi_core)]
+#[cfg(all(multi_core, low_power_wait))]
#[handler]
fn software3_interrupt() {
// This interrupt is fired when the thread-mode executor's core needs to be
@@ -25,30 +26,33 @@ fn software3_interrupt() {
unsafe { SoftwareInterrupt::<3>::steal().reset() };
}
-pub(crate) fn pend_thread_mode(core: usize) {
- // Signal that there is work to be done.
- SIGNAL_WORK_THREAD_MODE[core].store(true, Ordering::SeqCst);
-
- // If we are pending a task on the current core, we're done. Otherwise, we
- // need to make sure the other core wakes up.
- #[cfg(multi_core)]
- if core != get_core() as usize {
- // We need to clear the interrupt from software. We don't actually
- // need it to trigger and run the interrupt handler, we just need to
- // kick waiti to return.
- unsafe { SoftwareInterrupt::<3>::steal().raise() };
+pub(crate) fn pend_thread_mode(_core: usize) {
+ #[cfg(low_power_wait)]
+ {
+ // Signal that there is work to be done.
+ SIGNAL_WORK_THREAD_MODE[_core].store(true, Ordering::SeqCst);
+
+ // If we are pending a task on the current core, we're done. Otherwise, we
+ // need to make sure the other core wakes up.
+ #[cfg(multi_core)]
+ if _core != Cpu::current() as usize {
+ // We need to clear the interrupt from software. We don't actually
+ // need it to trigger and run the interrupt handler, we just need to
+ // kick waiti to return.
+ unsafe { SoftwareInterrupt::<3>::steal().raise() };
+ }
}
}
-/// A thread aware Executor
+/// Thread mode executor.
+///
+/// This is the simplest and most common kind of executor. It runs on thread
+/// mode (at the lowest priority level).
+#[cfg_attr(multi_core, doc = "")]
#[cfg_attr(
multi_core,
- doc = r#"
-This executor is capable of waking an
-executor running on another core if work
-needs to be completed there for a task to
-progress on this core.
-"#
+ doc = "This executor is safe to use on multiple cores. You need to
+create one instance per core. The executors don't steal tasks from each other."
)]
pub struct Executor {
inner: raw::Executor,
@@ -64,13 +68,13 @@ impl Executor {
This will use software-interrupt 3 which isn't available for anything else to wake the other core(s)."#
)]
pub fn new() -> Self {
- #[cfg(multi_core)]
+ #[cfg(all(multi_core, low_power_wait))]
unsafe {
SoftwareInterrupt::<3>::steal().set_interrupt_handler(software3_interrupt);
}
Self {
- inner: raw::Executor::new((THREAD_MODE_CONTEXT + get_core() as usize) as *mut ()),
+ inner: raw::Executor::new((THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut ()),
not_send: PhantomData,
}
}
@@ -90,7 +94,7 @@ This will use software-interrupt 3 which isn't available for anything else to wa
/// you mutable access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
- /// - a `static mut` (unsafe)
+ /// - a `static mut` (unsafe, not recommended)
/// - a local variable in a function you know never returns (like `fn main()
/// -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
@@ -98,20 +102,19 @@ This will use software-interrupt 3 which isn't available for anything else to wa
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
- let cpu = get_core() as usize;
+ #[cfg(low_power_wait)]
+ let cpu = Cpu::current() as usize;
loop {
- unsafe {
- self.inner.poll();
+ unsafe { self.inner.poll() };
- Self::wait_impl(cpu);
- }
+ #[cfg(low_power_wait)]
+ Self::wait_impl(cpu);
}
}
- #[doc(hidden)]
- #[cfg(xtensa)]
- pub fn wait_impl(cpu: usize) {
+ #[cfg(all(xtensa, low_power_wait))]
+ fn wait_impl(cpu: usize) {
// Manual critical section implementation that only masks interrupts handlers.
// We must not acquire the cross-core on dual-core systems because that would
// prevent the other core from doing useful work while this core is sleeping.
@@ -140,9 +143,8 @@ This will use software-interrupt 3 which isn't available for anything else to wa
}
}
- #[doc(hidden)]
- #[cfg(riscv)]
- pub fn wait_impl(cpu: usize) {
+ #[cfg(all(riscv, low_power_wait))]
+ fn wait_impl(cpu: usize) {
// we do not care about race conditions between the load and store operations,
// interrupts will only set this value to true.
critical_section::with(|_| {
diff --git a/esp-hal-embassy/src/lib.rs b/esp-hal-embassy/src/lib.rs
index 9b8a8980a0c..795f4cf13b3 100644
--- a/esp-hal-embassy/src/lib.rs
+++ b/esp-hal-embassy/src/lib.rs
@@ -149,6 +149,9 @@ impl_array!(4);
/// - A mutable static array of `OneShotTimer` instances
/// - A 2, 3, 4 element array of `AnyTimer` instances
///
+/// Note that if you use the `integrated-timers` feature,
+/// you need to pass as many timers as you start executors.
+///
/// # Examples
///
/// ```rust, no_run
diff --git a/esp-hal-embassy/src/time_driver.rs b/esp-hal-embassy/src/time_driver.rs
index 99708077812..6b73f800104 100644
--- a/esp-hal-embassy/src/time_driver.rs
+++ b/esp-hal-embassy/src/time_driver.rs
@@ -49,48 +49,20 @@ impl EmbassyTimer {
);
}
- static HANDLERS: [InterruptHandler; MAX_SUPPORTED_ALARM_COUNT] = [
- handler0, handler1, handler2, handler3, handler4, handler5, handler6,
- ];
-
critical_section::with(|cs| {
timers.iter_mut().enumerate().for_each(|(n, timer)| {
timer.enable_interrupt(false);
timer.stop();
- timer.set_interrupt_handler(HANDLERS[n]);
+
+ if DRIVER.alarms.borrow(cs)[n].allocated.get() {
+ // FIXME: we should track which core allocated an alarm and bind the interrupt
+ // to that core.
+ timer.set_interrupt_handler(HANDLERS[n]);
+ }
});
TIMERS.replace(cs, Some(timers));
});
-
- #[handler(priority = Priority::max())]
- fn handler0() {
- DRIVER.on_interrupt(0);
- }
- #[handler(priority = Priority::max())]
- fn handler1() {
- DRIVER.on_interrupt(1);
- }
- #[handler(priority = Priority::max())]
- fn handler2() {
- DRIVER.on_interrupt(2);
- }
- #[handler(priority = Priority::max())]
- fn handler3() {
- DRIVER.on_interrupt(3);
- }
- #[handler(priority = Priority::max())]
- fn handler4() {
- DRIVER.on_interrupt(4);
- }
- #[handler(priority = Priority::max())]
- fn handler5() {
- DRIVER.on_interrupt(5);
- }
- #[handler(priority = Priority::max())]
- fn handler6() {
- DRIVER.on_interrupt(6);
- }
}
fn on_interrupt(&self, id: usize) {
@@ -128,11 +100,26 @@ impl Driver for EmbassyTimer {
unsafe fn allocate_alarm(&self) -> Option {
critical_section::with(|cs| {
for (i, alarm) in self.alarms.borrow(cs).iter().enumerate() {
- if !alarm.allocated.get() {
- // set alarm so it is not overwritten
- alarm.allocated.set(true);
- return Some(AlarmHandle::new(i as u8));
+ if alarm.allocated.get() {
+ continue;
}
+ let mut timer = TIMERS.borrow_ref_mut(cs);
+ // `allocate_alarm` may be called before `esp_hal_embassy::init()`, so
+ // we need to check if we have timers.
+ if let Some(timer) = &mut *timer {
+ // If we do, bind the interrupt handler to the timer.
+ // This ensures that alarms allocated after init are correctly bound to the
+ // core that created the executor.
+ let timer = unwrap!(
+ timer.get_mut(i),
+ "There are not enough timers to allocate a new alarm. Call `esp_hal_embassy::init()` with the correct number of timers."
+ );
+ timer.set_interrupt_handler(HANDLERS[i]);
+ }
+
+ // set alarm so it is not overwritten
+ alarm.allocated.set(true);
+ return Some(AlarmHandle::new(i as u8));
}
None
})
@@ -172,3 +159,36 @@ impl Driver for EmbassyTimer {
true
}
}
+
+static HANDLERS: [InterruptHandler; MAX_SUPPORTED_ALARM_COUNT] = [
+ handler0, handler1, handler2, handler3, handler4, handler5, handler6,
+];
+
+#[handler(priority = Priority::max())]
+fn handler0() {
+ DRIVER.on_interrupt(0);
+}
+#[handler(priority = Priority::max())]
+fn handler1() {
+ DRIVER.on_interrupt(1);
+}
+#[handler(priority = Priority::max())]
+fn handler2() {
+ DRIVER.on_interrupt(2);
+}
+#[handler(priority = Priority::max())]
+fn handler3() {
+ DRIVER.on_interrupt(3);
+}
+#[handler(priority = Priority::max())]
+fn handler4() {
+ DRIVER.on_interrupt(4);
+}
+#[handler(priority = Priority::max())]
+fn handler5() {
+ DRIVER.on_interrupt(5);
+}
+#[handler(priority = Priority::max())]
+fn handler6() {
+ DRIVER.on_interrupt(6);
+}
diff --git a/esp-hal-procmacros/CHANGELOG.md b/esp-hal-procmacros/CHANGELOG.md
new file mode 100644
index 00000000000..84d7f1c0ed0
--- /dev/null
+++ b/esp-hal-procmacros/CHANGELOG.md
@@ -0,0 +1,48 @@
+# Changelog
+
+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.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+### Fixed
+
+### Changed
+
+### Removed
+
+## [0.14.0] - 2024-10-10
+
+## [0.13.0] - 2024-08-29
+
+## [0.12.0] - 2024-07-15
+
+## [0.11.0] - 2024-06-04
+
+## [0.10.0] - 2024-04-18
+
+## [0.9.0] - 2024-03-18
+
+## [0.8.0] - 2023-12-12
+
+## [0.7.0] - 2023-10-31
+
+## [0.6.1] - 2023-09-05
+
+## [0.6.0] - 2023-07-04
+
+## [0.5.0] - 2023-03-27
+
+## [0.4.0] - 2023-02-21
+
+## [0.2.0] - 2023-01-26
+
+## [0.1.0] - 2022-08-25
+
+- Initial release
+
+[Unreleased]: https://github.com/esp-rs/esp-hal/commits/main/esp-hal-procmacros?since=2024-10-10
diff --git a/esp-hal-procmacros/src/lp_core.rs b/esp-hal-procmacros/src/lp_core.rs
index 5fa639d7e92..60a3b351cc1 100644
--- a/esp-hal-procmacros/src/lp_core.rs
+++ b/esp-hal-procmacros/src/lp_core.rs
@@ -31,7 +31,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
res
}
- pub(crate) fn get_simplename(t: &Type) -> String {
+ pub(crate) fn simplename(t: &Type) -> String {
match t {
Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
_ => String::new(),
@@ -125,7 +125,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
.into();
}
FnArg::Typed(t) => {
- match get_simplename(&t.ty).as_str() {
+ match simplename(&t.ty).as_str() {
"Output" => {
let pin = extract_pin(&t.ty);
if used_pins.contains(&pin) {
diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md
index 4690eb3b58d..90ea7f5edb3 100644
--- a/esp-hal/CHANGELOG.md
+++ b/esp-hal/CHANGELOG.md
@@ -22,6 +22,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Spi::half_duplex_read` and `Spi::half_duplex_write` (#2373)
- `Cpu::COUNT` and `Cpu::current()` (#2411)
- `UartInterrupt` and related functions (#2406)
+- I2S Parallel output driver for ESP32. (#2348, #2436, #2472)
+- Add an option to configure `WDT` action (#2330)
+- `DmaDescriptor` is now `Send` (#2456)
+- `into_async` and `into_blocking` functions for most peripherals (#2430, #2461)
+- API mode type parameter (currently always `Blocking`) to `master::Spi` and `slave::Spi` (#2430)
+- `gpio::{GpioPin, AnyPin, Flex, Output, OutputOpenDrain}::split()` to obtain peripheral interconnect signals. (#2418)
+- `gpio::Input::{split(), into_peripheral_output()}` when used with output pins. (#2418)
+- `gpio::Output::peripheral_input()` (#2418)
+- `{Uart, UartRx, UartTx}::apply_config()` (#2449)
+- `{Uart, UartRx, UartTx}` now implement `embassy_embedded_hal::SetConfig` (#2449)
+- GPIO ETM tasks and events now accept `InputSignal` and `OutputSignal` (#2427)
+- `spi::master::Config` and `{Spi, SpiDma, SpiDmaBus}::apply_config` (#2448)
+- `embassy_embedded_hal::SetConfig` is now implemented for `spi::master::{Spi, SpiDma, SpiDmaBus}`, `i2c::master::I2c` (#2448, #2477)
+- `slave::Spi::{with_mosi(), with_miso(), with_sclk(), with_cs()}` functions (#2485)
+- I8080: Added `set_8bits_order()` to set the byte order in 8-bit mode (#2487)
+- `I2c::{apply_config(), with_sda(), with_scl()}` (#2477)
+- ESP32-S2: Added missing GPIO alternate functions (#2512)
### Changed
@@ -33,17 +50,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Renamed `SpiDma` functions: `dma_transfer` to `transfer`, `dma_write` to `write`, `dma_read` to `read`. (#2373)
- Peripheral type erasure for UART (#2381)
- Changed listening for UART events (#2406)
+- Circular DMA transfers now correctly error, `available` returns `Result` now (#2409)
+- Interrupt listen/unlisten/clear functions now accept any type that converts into `EnumSet` (i.e. single interrupt flags). (#2442)
+- SPI interrupt listening is now only available in Blocking mode. The `set_interrupt_handler` is available via `InterruptConfigurable` (#2442)
+- Allow users to create DMA `Preparation`s (#2455)
+- The `rmt::asynch::RxChannelAsync` and `rmt::asynch::TxChannelAsync` traits have been moved to `rmt` (#2430)
+- Calling `AnyPin::output_signals` on an input-only pin (ESP32 GPIO 34-39) will now result in a panic. (#2418)
+- UART configuration types have been moved to `esp_hal::uart` (#2449)
+- `spi::master::Spi::new()` no longer takes `frequency` and `mode` as a parameter. (#2448)
+- Peripheral interconnections via GPIO pins now use the GPIO matrix. (#2419)
+- The I2S driver has been moved to `i2s::master` (#2472)
+- `slave::Spi` constructors no longer take pins (#2485)
+- The `I2c` master driver has been moved from `esp_hal::i2c` to `esp_hal::i2c::master`. (#2476)
+- `I2c` SCL timeout is now defined in bus clock cycles. (#2477)
+- Trying to send a single-shot RMT transmission will result in an error now, `RMT` deals with `u32` now, `PulseCode` is a convenience trait now (#2463)
+- Removed `get_` prefixes from functions (#2528)
### Fixed
- Fix conflict between `RtcClock::get_xtal_freq` and `Rtc::disable_rom_message_printing` (#2360)
- Fixed an issue where interrupts enabled before `esp_hal::init` were disabled. This issue caused the executor created by `#[esp_hal_embassy::main]` to behave incorrectly in multi-core applications. (#2377)
+- Fixed `TWAI::transmit_async`: bus-off state is not reached when CANH and CANL are shorted. (#2421)
+- ESP32: added UART-specific workaround for https://docs.espressif.com/projects/esp-chip-errata/en/latest/esp32/03-errata-description/esp32/cpu-subsequent-access-halted-when-get-interrupted.html (#2441)
+- Fixed some SysTimer race conditions and panics (#2451)
+- TWAI: accept all messages by default (#2467)
+- I8080: `set_byte_order()` now works correctly in 16-bit mode (#2487)
+- ESP32-C6/ESP32-H2: Make higher LEDC frequencies work (#2520)
### Removed
- The `i2s::{I2sWrite, I2sWriteDma, I2sRead, I2sReadDma, I2sWriteDmaAsync, I2sReadDmaAsync}` traits have been removed. (#2316)
- The `ledc::ChannelHW` trait is no longer generic. (#2387)
- The `I2c::new_with_timeout` constructors have been removed (#2361)
+- `I2c::new()` no longer takes `frequency` and pins as parameters. (#2477)
- The `spi::master::HalfDuplexReadWrite` trait has been removed. (#2373)
- The `Spi::with_pins` methods have been removed. (#2373)
- The `Spi::new_half_duplex` constructor have been removed. (#2373)
@@ -56,12 +95,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed the pin type parameters from `parl_io::{RxOneBit, RxTwoBits, RxFourBits, RxEightBits, RxSixteenBits}` (#2388)
- Removed the pin type parameters from `lcd_cam::lcd::i8080::{TxEightBits, TxSixteenBits}` (#2388)
- Removed the pin type parameters from `lcd_cam::cam::{RxEightBits, RxSixteenBits}` (#2388)
+- Most of the async-specific constructors (`new_async`, `new_async_no_transceiver`) have been removed. (#2430)
+- The `configure_for_async` DMA functions have been removed (#2430)
+- The `Uart::{change_baud, change_stop_bits}` functions have been removed (#2449)
+- `gpio::{Input, Output, OutputOpenDrain, Flex, GpioPin}::{peripheral_input, into_peripheral_output}` have been removed. (#2418)
+- The `GpioEtm` prefix has been removed from `gpio::etm` types (#2427)
+- The `TimerEtm` prefix has been removed from `timer::timg::etm` types (#2427)
+- The `SysTimerEtm` prefix has been removed from `timer::systimer::etm` types (#2427)
+- The `GpioEtmEventRising`, `GpioEtmEventFalling`, `GpioEtmEventAny` types have been replaced with `Event` (#2427)
+- The `TaskSet`, `TaskClear`, `TaskToggle` types have been replaced with `Task` (#2427)
+- `{Spi, SpiDma, SpiDmaBus}` configuration methods (#2448)
+- `Io::new_with_priority` and `Io::new_no_bind_interrupt`. (#2486)
+- `parl_io::{no_clk_pin(), NoClkPin}` (#2531)
+- Removed `get_core` function in favour of `Cpu::current` (#2533)
## [0.21.1]
### Fixed
- Restored blocking `embedded_hal` compatibility for async I2C driver (#2343)
+- I2c::transaction is now able to transmit data of arbitrary length (#2481)
## [0.21.0]
@@ -126,6 +179,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- SPI transactions are now cancelled if the transfer object (or async Future) is dropped. (#2216)
- The DMA channel types have been removed from peripherals (#2261)
- `I2C` driver renamed to `I2c` (#2320)
+- The GPIO pins are now accessible via `Peripherals` and are no longer part of the `Io` struct (#2508)
### Fixed
@@ -150,6 +204,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TWAI should no longer panic when receiving a non-compliant frame (#2255)
- OneShotTimer: fixed `delay_nanos` behaviour (#2256)
- Fixed unsoundness around `Efuse` (#2259)
+- Empty I2C writes to unknown addresses now correctly fail with `AckCheckFailed`. (#2506)
### Removed
@@ -170,6 +225,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed `esp_hal::spi::slave::WithDmaSpiN` traits (#2260)
- The `WithDmaAes` trait has been removed (#2261)
- The `I2s::new_i2s1` constructor has been removed (#2261)
+- `Peripherals.GPIO` has been removed (#2508)
## [0.20.1] - 2024-08-30
diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml
index 399c4e7222b..a92c64af117 100644
--- a/esp-hal/Cargo.toml
+++ b/esp-hal/Cargo.toml
@@ -25,6 +25,7 @@ defmt = { version = "0.3.8", optional = true }
delegate = "0.12.0"
digest = { version = "0.10.7", default-features = false, optional = true }
document-features = "0.2.10"
+embassy-embedded-hal = "0.2.0"
embassy-futures = "0.1.1"
embassy-sync = "0.6.0"
embassy-usb-driver = { version = "0.1.0", optional = true }
@@ -55,13 +56,13 @@ xtensa-lx = { version = "0.9.0", optional = true }
# IMPORTANT:
# Each supported device MUST have its PAC included below along with a
# corresponding feature.
-esp32 = { version = "0.33.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
-esp32c2 = { version = "0.22.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
-esp32c3 = { version = "0.25.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
-esp32c6 = { version = "0.16.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
-esp32h2 = { version = "0.12.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
-esp32s2 = { version = "0.24.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
-esp32s3 = { version = "0.28.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "48fd400", features = ["critical-section", "rt"], optional = true }
+esp32 = { version = "0.34.0", features = ["critical-section", "rt"], optional = true }
+esp32c2 = { version = "0.23.0", features = ["critical-section", "rt"], optional = true }
+esp32c3 = { version = "0.26.0", features = ["critical-section", "rt"], optional = true }
+esp32c6 = { version = "0.17.0", features = ["critical-section", "rt"], optional = true }
+esp32h2 = { version = "0.13.0", features = ["critical-section", "rt"], optional = true }
+esp32s2 = { version = "0.25.0", features = ["critical-section", "rt"], optional = true }
+esp32s3 = { version = "0.29.0", features = ["critical-section", "rt"], optional = true }
[target.'cfg(target_arch = "riscv32")'.dependencies]
esp-riscv-rt = { version = "0.9.0", path = "../esp-riscv-rt" }
diff --git a/esp-hal/MIGRATING-0.20.md b/esp-hal/MIGRATING-0.20.md
index 543cb03185a..0064d8a1d45 100644
--- a/esp-hal/MIGRATING-0.20.md
+++ b/esp-hal/MIGRATING-0.20.md
@@ -230,7 +230,7 @@ We've replaced some usage of features with [esp-config](https://docs.rs/esp-conf
# feature in Cargo.toml
- esp-hal = { version = "0.20", features = ["place-spi-driver-in-ram"] }
# key in .cargo/config.toml [env] section
-+ ESP_HAL_PLACE_SPI_DRIVER_IN_RAM=true
++ ESP_HAL_PLACE_SPI_DRIVER_IN_RAM="true"
```
## `Camera` driver now uses `DmaRxBuffer` and moves the driver into the transfer object.
diff --git a/esp-hal/MIGRATING-0.21.md b/esp-hal/MIGRATING-0.21.md
index a693ab966f2..6211533dba6 100644
--- a/esp-hal/MIGRATING-0.21.md
+++ b/esp-hal/MIGRATING-0.21.md
@@ -1,28 +1,58 @@
# Migration Guide from 0.21.x to v0.22.x
-## Removed `i2s` traits
+## IO changes
-The following traits have been removed:
+### GPIO pins are now accessible via `Peripherals`
-- `I2sWrite`
-- `I2sWriteDma`
-- `I2sRead`
-- `I2sReadDma`
-- `I2sWriteDmaAsync`
-- `I2sReadDmaAsync`
+```diff
+ let peripherals = esp_hal::init(Default::default());
+-let io = Io::new(peripherals.GPIO, peripherals.IOMUX);
+-let pin = io.pins.gpio5;
++let pin = peripherals.GPIO5;
+```
-You no longer have to import these to access their respective APIs. If you used these traits
-in your functions as generic parameters, you can use the `I2s` type directly instead.
+### `Io` constructor changes
-For example:
+- `new_with_priority` and `new_no_bind_interrupts` have been removed.
+ Use `set_priority` to configure the GPIO interrupt priority.
+ We no longer overwrite interrupt handlers set by user code during initialization.
+- `new` no longer takes `peripherals.GPIO`
+
+## Removed `async`-specific constructors
+
+The following async-specific constuctors have been removed:
+
+- `configure_for_async` DMA channel constructors
+- `TwaiConfiguration::new_async` and `TwaiConfiguration::new_async_no_transceiver`
+- `I2c::new_async`
+- `LcdCam::new_async`
+- `UsbSerialJtag::new_async`
+- `Rsa::new_async`
+- `Rmt::new_async`
+- `Uart::new_async`, `Uart::new_async_with_config`
+- `UartRx::new_async`, `UartRx::new_async_with_config`
+- `UartTx::new_async`, `UartTx::new_async_with_config`
+
+You can use the blocking counterparts, then call `into_async` on the returned peripheral instead.
```diff
--fn foo(i2s: &mut impl I2sWrite) {
-+fn foo(i2s: &mut I2s<'_, I2S0, Blocking>) {
- // ...
- }
+-let mut config = twai::TwaiConfiguration::new_async(
++let mut config = twai::TwaiConfiguration::new(
+ peripherals.TWAI0,
+ loopback_pin.peripheral_input(),
+ loopback_pin,
+ twai::BaudRate::B1000K,
+ TwaiMode::SelfTest,
+-);
++).into_async();
```
+Some drivers were implicitly configured to the asyncness of the DMA channel used to construct them.
+This is no longer the case, and the following drivers will always be created in blocking mode:
+
+- `i2s::master::I2s`
+- `spi::master::SpiDma` and `spi::master::SpiDmaBus`
+
## Peripheral types are now optional
You no longer have to specify the peripheral instance in the driver's type for the following
@@ -53,15 +83,46 @@ the peripheral instance has been moved to the last generic parameter position.
let spi: Spi<'static, FullDuplexMode, SPI2> = Spi::new_typed(peripherals.SPI2, 1.MHz(), SpiMode::Mode0);
```
-## I2C constructor changes
+## I2C changes
+
+The I2C master driver and related types have been moved to `esp_hal::i2c::master`.
-The `with_timeout` constructors have been removed in favour of `set_timeout` or `with_timeout`.
+The `with_timeout` constructors have been removed. `new` and `new_typed` now take a `Config` struct
+with the available configuration options.
+
+- The default configuration is now:
+ - bus frequency: 100 kHz
+ - timeout: about 10 bus clock cycles
+
+The constructors no longer take pins. Use `with_sda` and `with_scl` instead.
```diff
+-use esp_hal::i2c::I2c;
++use esp_hal::i2c::{Config, I2c};
-let i2c = I2c::new_with_timeout(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 100.kHz(), timeout);
-+let i2c = I2c::new(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 100.kHz()).with_timeout(timeout);
++I2c::new_with_config(
++ peripherals.I2C0,
++ {
++ let mut config = Config::default();
++ config.frequency = 100.kHz();
++ config.timeout = timeout;
++ config
++ },
++)
++.with_sda(io.pins.gpio4)
++.with_scl(io.pins.gpio5);
```
+### The calculation of I2C timeout has changed
+
+Previously, I2C timeouts were counted in increments of I2C peripheral clock cycles. This meant that
+the configure value meant different lengths of time depending on the device. With this update, the
+I2C configuration now expects the timeout value in number of bus clock cycles, which is consistent
+between devices.
+
+ESP32 and ESP32-S2 use an exact number of clock cycles for its timeout. Other MCUs, however, use
+the `2^timeout` value internally, and the HAL rounds up the timeout to the next appropriate value.
+
## Changes to half-duplex SPI
The `HalfDuplexMode` and `FullDuplexMode` type parameters have been removed from SPI master and slave
@@ -72,7 +133,6 @@ drivers. It is now possible to execute half-duplex and full-duplex operations on
- The `Spi::new_half_duplex` constructor has been removed. Use `new` (or `new_typed`) instead.
- The `with_pins` methods have been removed. Use the individual `with_*` functions instead.
- The `with_mosi` and `with_miso` functions now take input-output peripheral signals to support half-duplex mode.
- > TODO(danielb): this means they are currently only usable with GPIO pins, but upcoming GPIO changes should allow using any output signal.
```diff
- let mut spi = Spi::new_half_duplex(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
@@ -119,6 +179,28 @@ The `Spi<'_, SPI, HalfDuplexMode>::read` and `Spi<'_, SPI, HalfDuplexMode>::writ
.unwrap();
```
+## Slave-mode SPI
+
+### Driver construction
+
+The constructors no longer accept pins. Use the `with_pin_name` setters instead.
+
+```diff
+ let mut spi = Spi::new(
+ peripherals.SPI2,
+- sclk,
+- mosi,
+- miso,
+- cs,
+ SpiMode::Mode0,
+-);
++)
++.with_sclk(sclk)
++.with_mosi(mosi)
++.with_miso(miso)
++.with_cs(cs);
+```
+
## UART event listening
The following functions have been removed:
@@ -141,6 +223,7 @@ You can now use the `UartInterrupt` enum and the corresponding `listen`, `unlist
Use `interrupts` in place of `_interrupt_set` and `clear_interrupts` in place of the old `reset_` functions.
`UartInterrupt`:
+
- `AtCmd`
- `TxDone`
- `RxFifoFull`
@@ -159,4 +242,187 @@ You can now listen/unlisten multiple interrupt bits at once:
-uart0.listen_at_cmd();
-uart0.listen_rx_fifo_full();
+uart0.listen(UartInterrupt::AtCmd | UartConterrupt::RxFifoFull);
-```Ë›
+```
+
+## I2S changes
+
+### The I2S driver has been moved to `i2s::master`
+
+```diff
+-use esp_hal::i2s::{DataFormat, I2s, Standard};
++use esp_hal::i2s::master::{DataFormat, I2s, Standard};
+```
+
+### Removed `i2s` traits
+
+The following traits have been removed:
+
+- `I2sWrite`
+- `I2sWriteDma`
+- `I2sRead`
+- `I2sReadDma`
+- `I2sWriteDmaAsync`
+- `I2sReadDmaAsync`
+
+You no longer have to import these to access their respective APIs. If you used these traits
+in your functions as generic parameters, you can use the `I2s` type directly instead.
+
+For example:
+
+```diff
+-fn foo(i2s: &mut impl I2sWrite) {
++fn foo(i2s: &mut I2s<'_, I2S0, Blocking>) {
+ // ...
+ }
+```
+
+## Circular DMA transfer's `available` returns `Result` now
+
+In case of any error you should drop the transfer and restart it.
+
+```diff
+ loop {
+- let avail = transfer.available();
++ let avail = match transfer.available() {
++ Ok(avail) => avail,
++ Err(_) => {
++ core::mem::drop(transfer);
++ transfer = i2s_tx.write_dma_circular(&tx_buffer).unwrap();
++ continue;
++ },
++ };
+```
+
+## Removed `peripheral_input` and `into_peripheral_output` from GPIO pin types
+
+Creating peripheral interconnect signals now consume the GPIO pin used for the connection.
+
+The previous signal function have been replaced by `split`. This change affects the following APIs:
+
+- `GpioPin`
+- `AnyPin`
+
+```diff
+-let input_signal = gpioN.peripheral_input();
+-let output_signal = gpioN.into_peripheral_output();
++let (input_signal, output_signal) = gpioN.split();
+```
+
+`into_peripheral_output`, `split` (for output pins only) and `peripheral_input` have been added to
+the GPIO drivers (`Input`, `Output`, `OutputOpenDrain` and `Flex`) instead.
+
+## ETM changes
+
+- The types are no longer prefixed with `GpioEtm`, `TimerEtm` or `SysTimerEtm`. You can still use
+ import aliasses in case you need to differentiate due to name collisions
+ (e.g. `use esp_hal::gpio::etm::Event as GpioEtmEvent`).
+- The old task and event types have been replaced by `Task` and `Event`.
+- GPIO tasks and events are no longer generic.
+
+## Changes to peripheral configuration
+
+### The `uart::config` module has been removed
+
+The module's contents have been moved into `uart`.
+
+```diff
+-use esp_hal::uart::config::Config;
++use esp_hal::uart::Config;
+```
+
+If you work with multiple configurable peripherals, you may want to import the `uart` module and
+refer to the `Config` struct as `uart::Config`.
+
+### SPI drivers can now be configured using `spi::master::Config`
+
+- The old methods to change configuration have been removed.
+- The `new` and `new_typed` constructor no longer takes `frequency` and `mode`.
+- The default configuration is now:
+ - bus frequency: 1 MHz
+ - bit order: MSB first
+ - mode: SPI mode 0
+- There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration.
+
+```diff
+-use esp_hal::spi::{master::Spi, SpiMode};
++use esp_hal::spi::{master::{Config, Spi}, SpiMode};
+-Spi::new(SPI2, 100.kHz(), SpiMode::Mode1);
++Spi::new_with_config(
++ SPI2,
++ Config {
++ frequency: 100.kHz(),
++ mode: SpiMode::Mode0,
++ ..Config::default()
++ },
++)
+```
+
+## I8080 driver split `set_byte_order()` into `set_8bits_order()` and `set_byte_order()`.
+
+If you were using an 8-bit bus.
+
+```diff
+- i8080.set_byte_order(ByteOrder::default());
++ i8080.set_8bits_order(ByteOrder::default());
+```
+
+If you were using an 16-bit bus, you don't need to change anything, `set_byte_order()` now works correctly.
+
+If you were sharing the bus between an 8-bit and 16-bit device, you will have to call the corresponding method when
+you switch between devices. Be sure to read the documentation of the new methods.
+
+## `rmt::Channel::transmit` now returns `Result`, `PulseCode` is now `u32`
+
+When trying to send a one-shot transmission will fail if it doesn't end with an end-marker.
+
+```diff
+- let mut data = [PulseCode {
+- level1: true,
+- length1: 200,
+- level2: false,
+- length2: 50,
+- }; 20];
+-
+- data[data.len() - 2] = PulseCode {
+- level1: true,
+- length1: 3000,
+- level2: false,
+- length2: 500,
+- };
+- data[data.len() - 1] = PulseCode::default();
++ let mut data = [PulseCode::new(true, 200, false, 50); 20];
++ data[data.len() - 2] = PulseCode::new(true, 3000, false, 500);
++ data[data.len() - 1] = PulseCode::empty();
+
+- let transaction = channel.transmit(&data);
++ let transaction = channel.transmit(&data).unwrap();
+```
+
+
+## The `parl_io::NoClkPin` and `no_clk_pin()` have been removed
+
+You can use `gpio::NoPin` instead.
+
+```diff
+ use esp_hal:: {
+- parl_io::no_clk_pin,
++ gpio::NoPin,
+ }
+
+-parl_io.rx.with_config(&mut rx_pins, no_clk_pin(), BitPackOrder::Msb, Some(0xfff))
++let mut rx_clk_pin = NoPin;
++parl_io.rx.with_config(&mut rx_pins, &mut rx_clk_pin, BitPackOrder::Msb, Some(0xfff))
+```
+
+## `get_` prefixes have been removed from functions
+
+In order to better comply with the Rust API Guidelines [getter names convention], we have removed the `get_` prefixes from all functions which previously had it. Due to the number of changes it's not practical to list all changes here, however if a function previous began with `get_`, you can simply remove this prefix.
+
+[getter names convention]: https://rust-lang.github.io/api-guidelines/naming.html#c-getter
+
+## The `get_core()` function has been removed in favour of `Cpu::current()`
+
+```diff
+- let core = esp_hal::get_core();
++ let core = esp_hal::Cpu::current();
+```
diff --git a/esp-hal/build.rs b/esp-hal/build.rs
index c507a97bd13..9e0f49684c0 100644
--- a/esp-hal/build.rs
+++ b/esp-hal/build.rs
@@ -80,23 +80,27 @@ fn main() -> Result<(), Box> {
&[
(
"place-spi-driver-in-ram",
- Value::Bool(false),
"Places the SPI driver in RAM for better performance",
+ Value::Bool(false),
+ None
),
(
"spi-address-workaround",
- Value::Bool(true),
"(ESP32 only) Enables a workaround for the issue where SPI in half-duplex mode incorrectly transmits the address on a single line if the data buffer is empty.",
+ Value::Bool(true),
+ None
),
(
"place-switch-tables-in-ram",
- Value::Bool(true),
"Places switch-tables, some lookup tables and constants related to interrupt handling into RAM - resulting in better performance but slightly more RAM consumption.",
+ Value::Bool(true),
+ None
),
(
"place-anon-in-ram",
- Value::Bool(false),
"Places anonymous symbols into RAM - resulting in better performance at the cost of significant more RAM consumption. Best to be combined with `place-switch-tables-in-ram`.",
+ Value::Bool(false),
+ None
),
],
true,
diff --git a/esp-hal/ld/esp32/rom/additional.ld b/esp-hal/ld/esp32/rom/additional.ld
index ee815fb3970..d8afbed1267 100644
--- a/esp-hal/ld/esp32/rom/additional.ld
+++ b/esp-hal/ld/esp32/rom/additional.ld
@@ -9,3 +9,12 @@ PROVIDE ( strncpy = 0x400015d4 );
PROVIDE ( strncmp = 0x4000c5f4 );
PROVIDE ( bzero = 0x4000c1f4 );
+
+PROVIDE ( strcat = 0x4000c518 );
+PROVIDE ( strcmp = 0x40001274 );
+PROVIDE ( strchr = 0x4000c53c );
+PROVIDE ( strlcpy = 0x4000c584 );
+PROVIDE ( strstr = 0x4000c674 );
+PROVIDE ( strcasecmp = 0x400011cc );
+PROVIDE ( strdup = 0x4000143c );
+PROVIDE ( atoi = 0x400566c4 );
\ No newline at end of file
diff --git a/esp-hal/ld/esp32c2/rom/additional.ld b/esp-hal/ld/esp32c2/rom/additional.ld
index 15f88e99bdc..46ef79d8f36 100644
--- a/esp-hal/ld/esp32c2/rom/additional.ld
+++ b/esp-hal/ld/esp32c2/rom/additional.ld
@@ -6,3 +6,12 @@ memcmp = 0x40000494;
strcpy = 0x40000498;
strncpy = 0x4000049c;
strncmp = 0x400004a4;
+
+PROVIDE ( strcat = 0x4000050c );
+PROVIDE ( strcmp = 0x400004a0 );
+PROVIDE ( strchr = 0x40000514 );
+PROVIDE ( strlcpy = 0x40000524 );
+PROVIDE ( strstr = 0x400004ac );
+PROVIDE ( strcasecmp = 0x40000504 );
+PROVIDE ( strdup = 0x40000510 );
+PROVIDE ( atoi = 0x40000580 );
diff --git a/esp-hal/ld/esp32c3/rom/additional.ld b/esp-hal/ld/esp32c3/rom/additional.ld
index 70032e9e921..f45bec9b54e 100644
--- a/esp-hal/ld/esp32c3/rom/additional.ld
+++ b/esp-hal/ld/esp32c3/rom/additional.ld
@@ -10,3 +10,12 @@ strcpy = 0x40000364;
abs = 0x40000424;
PROVIDE(cache_dbus_mmu_set = 0x40000564);
+
+PROVIDE( strcat = 0x400003d8 );
+PROVIDE( strcmp = 0x4000036c );
+PROVIDE( strchr = 0x400003e0 );
+PROVIDE( strlcpy = 0x400003f0 );
+PROVIDE( strstr = 0x40000378 );
+PROVIDE( strcasecmp = 0x400003d0 );
+PROVIDE( strdup = 0x400003dc );
+PROVIDE( atoi = 0x4000044c );
\ No newline at end of file
diff --git a/esp-hal/ld/esp32c6/rom/additional.ld b/esp-hal/ld/esp32c6/rom/additional.ld
index 315c01f769a..a9c1801e3a8 100644
--- a/esp-hal/ld/esp32c6/rom/additional.ld
+++ b/esp-hal/ld/esp32c6/rom/additional.ld
@@ -8,3 +8,12 @@ strncpy = 0x400004bc;
strcpy = 0x400004b8;
abs = 0x40000578;
+
+PROVIDE(strcat = 0x4000052c);
+PROVIDE(strcmp = 0x400004c0);
+PROVIDE(strchr = 0x40000534);
+PROVIDE(strlcpy = 0x40000544);
+PROVIDE(strstr = 0x400004cc);
+PROVIDE(strcasecmp = 0x40000524);
+PROVIDE(strdup = 0x40000530);
+PROVIDE(atoi = 0x400005a0);
diff --git a/esp-hal/ld/esp32h2/rom/additional.ld b/esp-hal/ld/esp32h2/rom/additional.ld
index ae1a956ca95..7c1c27ace8e 100644
--- a/esp-hal/ld/esp32h2/rom/additional.ld
+++ b/esp-hal/ld/esp32h2/rom/additional.ld
@@ -8,3 +8,12 @@ strncpy = 0x400004b4;
strcpy = 0x400004b0;
abs = 0x40000570;
+
+PROVIDE( strcat = 0x40000524 );
+PROVIDE( strcmp = 0x400004b8 );
+PROVIDE( strchr = 0x4000052c );
+PROVIDE( strlcpy = 0x4000053c );
+PROVIDE( strstr = 0x400004c4 );
+PROVIDE( strcasecmp = 0x4000051c );
+PROVIDE( strdup = 0x40000528 );
+PROVIDE( atoi = 0x40000598 );
diff --git a/esp-hal/ld/esp32s2/rom/additional.ld b/esp-hal/ld/esp32s2/rom/additional.ld
index 41f18abbe25..ea535add6f0 100644
--- a/esp-hal/ld/esp32s2/rom/additional.ld
+++ b/esp-hal/ld/esp32s2/rom/additional.ld
@@ -10,3 +10,11 @@ strncmp = 0x4001ae64;
bzero = 0x400078c8;
PROVIDE ( cache_dbus_mmu_set = 0x40018eb0 );
+
+PROVIDE ( strcat = 0x4001ad90 );
+PROVIDE ( strcmp = 0x40007be4 );
+PROVIDE ( strchr = 0x4001adb0 );
+PROVIDE ( strlcpy = 0x4001adf8 );
+PROVIDE ( strstr = 0x4001aee8 );
+PROVIDE ( strcasecmp = 0x40007b38 );
+PROVIDE ( strdup = 0x40007d84 );
diff --git a/esp-hal/ld/esp32s3/rom/additional.ld b/esp-hal/ld/esp32s3/rom/additional.ld
index 4d8c27b5974..4e926c8adde 100644
--- a/esp-hal/ld/esp32s3/rom/additional.ld
+++ b/esp-hal/ld/esp32s3/rom/additional.ld
@@ -12,3 +12,12 @@ bzero = 0x40001260;
PROVIDE(cache_dbus_mmu_set = 0x400019b0);
PROVIDE( Cache_Suspend_DCache_Autoload = 0x40001734 );
PROVIDE( Cache_Suspend_DCache = 0x400018b4 );
+
+PROVIDE( strcat = 0x40001374 );
+PROVIDE( strcmp = 0x40001230 );
+PROVIDE( strchr = 0x4000138c );
+PROVIDE( strlcpy = 0x400013bc );
+PROVIDE( strstr = 0x40001254 );
+PROVIDE( strcasecmp = 0x4000135c );
+PROVIDE( strdup = 0x40001380 );
+PROVIDE( atoi = 0x400014d0 );
diff --git a/esp-hal/src/aes/esp32.rs b/esp-hal/src/aes/esp32.rs
index f9920a9a4d3..dd69d4c5439 100644
--- a/esp-hal/src/aes/esp32.rs
+++ b/esp-hal/src/aes/esp32.rs
@@ -56,7 +56,7 @@ impl<'d> Aes<'d> {
}
pub(super) fn write_start(&mut self) {
- self.aes.start().write(|w| w.start().set_bit())
+ self.aes.start().write(|w| w.start().set_bit());
}
pub(super) fn read_idle(&mut self) -> bool {
diff --git a/esp-hal/src/aes/esp32cX.rs b/esp-hal/src/aes/esp32cX.rs
index bd653da4a84..9477b17579b 100644
--- a/esp-hal/src/aes/esp32cX.rs
+++ b/esp-hal/src/aes/esp32cX.rs
@@ -3,7 +3,7 @@ use crate::{
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
};
-impl<'d> Aes<'d> {
+impl Aes<'_> {
pub(super) fn init(&mut self) {
PeripheralClockControl::enable(PeripheralEnable::Aes);
self.write_dma(false);
@@ -13,7 +13,7 @@ impl<'d> Aes<'d> {
match enable_dma {
true => self.aes.dma_enable().write(|w| w.dma_enable().set_bit()),
false => self.aes.dma_enable().write(|w| w.dma_enable().clear_bit()),
- }
+ };
}
pub(super) fn write_key(&mut self, key: &[u8]) {
@@ -34,7 +34,7 @@ impl<'d> Aes<'d> {
}
pub(super) fn write_start(&mut self) {
- self.aes.trigger().write(|w| w.trigger().set_bit())
+ self.aes.trigger().write(|w| w.trigger().set_bit());
}
pub(super) fn read_idle(&mut self) -> bool {
diff --git a/esp-hal/src/aes/esp32s2.rs b/esp-hal/src/aes/esp32s2.rs
index 926612821c4..d00c34d20e8 100644
--- a/esp-hal/src/aes/esp32s2.rs
+++ b/esp-hal/src/aes/esp32s2.rs
@@ -21,7 +21,7 @@ impl<'d> Aes<'d> {
match enable_dma {
true => self.aes.dma_enable().write(|w| w.dma_enable().set_bit()),
false => self.aes.dma_enable().write(|w| w.dma_enable().clear_bit()),
- }
+ };
}
pub(super) fn write_key(&mut self, key: &[u8]) {
@@ -67,7 +67,7 @@ impl<'d> Aes<'d> {
}
pub(super) fn write_start(&mut self) {
- self.aes.trigger().write(|w| w.trigger().set_bit())
+ self.aes.trigger().write(|w| w.trigger().set_bit());
}
pub(super) fn read_idle(&mut self) -> bool {
diff --git a/esp-hal/src/aes/esp32s3.rs b/esp-hal/src/aes/esp32s3.rs
index 6adc108449f..3e8085348a8 100644
--- a/esp-hal/src/aes/esp32s3.rs
+++ b/esp-hal/src/aes/esp32s3.rs
@@ -13,7 +13,7 @@ impl<'d> Aes<'d> {
match enable_dma {
true => self.aes.dma_enable().write(|w| w.dma_enable().set_bit()),
false => self.aes.dma_enable().write(|w| w.dma_enable().clear_bit()),
- }
+ };
}
pub(super) fn write_key(&mut self, key: &[u8]) {
@@ -39,7 +39,7 @@ impl<'d> Aes<'d> {
}
pub(super) fn write_start(&mut self) {
- self.aes.trigger().write(|w| w.trigger().set_bit())
+ self.aes.trigger().write(|w| w.trigger().set_bit());
}
pub(super) fn read_idle(&mut self) -> bool {
diff --git a/esp-hal/src/aes/mod.rs b/esp-hal/src/aes/mod.rs
index bbf18752534..e6815b8877b 100644
--- a/esp-hal/src/aes/mod.rs
+++ b/esp-hal/src/aes/mod.rs
@@ -166,7 +166,7 @@ impl<'d> Aes<'d> {
self.set_block(block);
self.start();
while !(self.is_idle()) {}
- self.get_block(block);
+ self.block(block);
}
fn set_mode(&mut self, mode: u8) {
@@ -181,7 +181,7 @@ impl<'d> Aes<'d> {
self.write_block(block);
}
- fn get_block(&self, block: &mut [u8; 16]) {
+ fn block(&self, block: &mut [u8; 16]) {
self.read_block(block);
}
@@ -250,6 +250,7 @@ pub mod dma {
WriteBuffer,
},
peripherals::AES,
+ Blocking,
};
const ALIGN_SIZE: usize = core::mem::size_of::();
@@ -275,7 +276,7 @@ pub mod dma {
/// The underlying [`Aes`](super::Aes) driver
pub aes: super::Aes<'d>,
- channel: Channel<'d, ::Dma, crate::Blocking>,
+ channel: Channel<'d, ::Dma, Blocking>,
rx_chain: DescriptorChain,
tx_chain: DescriptorChain,
}
@@ -284,12 +285,11 @@ pub mod dma {
/// Enable DMA for the current instance of the AES driver
pub fn with_dma(
self,
- channel: Channel<'d, C, crate::Blocking>,
+ channel: Channel<'d, C, Blocking>,
rx_descriptors: &'static mut [DmaDescriptor],
tx_descriptors: &'static mut [DmaDescriptor],
) -> AesDma<'d>
where
- Self: Sized,
C: DmaChannelConvert<::Dma>,
{
AesDma {
@@ -301,13 +301,13 @@ pub mod dma {
}
}
- impl<'d> core::fmt::Debug for AesDma<'d> {
+ impl core::fmt::Debug for AesDma<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AesDma").finish()
}
}
- impl<'d> DmaSupport for AesDma<'d> {
+ impl DmaSupport for AesDma<'_> {
fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
while self.aes.aes.state().read().state().bits() != 2 // DMA status DONE == 2
&& !self.channel.tx.is_done()
@@ -347,7 +347,7 @@ pub mod dma {
}
}
- impl<'d> AesDma<'d> {
+ impl AesDma<'_> {
/// Writes the encryption key to the AES hardware, checking that its
/// length matches expected constraints.
pub fn write_key(&mut self, key: K)
diff --git a/esp-hal/src/analog/adc/calibration/basic.rs b/esp-hal/src/analog/adc/calibration/basic.rs
index 4943d6648e8..643653d5178 100644
--- a/esp-hal/src/analog/adc/calibration/basic.rs
+++ b/esp-hal/src/analog/adc/calibration/basic.rs
@@ -36,7 +36,7 @@ where
fn new_cal(atten: Attenuation) -> Self {
// Try to get init code (Dout0) from efuse
// Dout0 means mean raw ADC value when zero voltage applied to input.
- let cal_val = ADCI::get_init_code(atten).unwrap_or_else(|| {
+ let cal_val = ADCI::init_code(atten).unwrap_or_else(|| {
// As a fallback try to calibrate via connecting input to ground internally.
AdcConfig::::adc_calibrate(atten, AdcCalSource::Gnd)
});
diff --git a/esp-hal/src/analog/adc/calibration/line.rs b/esp-hal/src/analog/adc/calibration/line.rs
index 64b0105bc6f..ea6bf66f3b1 100644
--- a/esp-hal/src/analog/adc/calibration/line.rs
+++ b/esp-hal/src/analog/adc/calibration/line.rs
@@ -59,8 +59,8 @@ where
// Try get the reference point (Dout, Vin) from efuse
// Dout means mean raw ADC value when specified Vin applied to input.
- let (code, mv) = ADCI::get_cal_code(atten)
- .map(|code| (code, ADCI::get_cal_mv(atten)))
+ let (code, mv) = ADCI::cal_code(atten)
+ .map(|code| (code, ADCI::cal_mv(atten)))
.unwrap_or_else(|| {
// As a fallback try to calibrate using reference voltage source.
// This method is not too good because actual reference voltage may varies
diff --git a/esp-hal/src/analog/adc/mod.rs b/esp-hal/src/analog/adc/mod.rs
index b7d4e58d2a8..a0b3193cdcc 100644
--- a/esp-hal/src/analog/adc/mod.rs
+++ b/esp-hal/src/analog/adc/mod.rs
@@ -31,13 +31,11 @@
//! # use esp_hal::analog::adc::Attenuation;
//! # use esp_hal::analog::adc::Adc;
//! # use esp_hal::delay::Delay;
-//! # use esp_hal::gpio::Io;
-//! # let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
-#![cfg_attr(esp32, doc = "let analog_pin = io.pins.gpio32;")]
-#![cfg_attr(any(esp32s2, esp32s3), doc = "let analog_pin = io.pins.gpio3;")]
+#![cfg_attr(esp32, doc = "let analog_pin = peripherals.GPIO32;")]
+#![cfg_attr(any(esp32s2, esp32s3), doc = "let analog_pin = peripherals.GPIO3;")]
#![cfg_attr(
not(any(esp32, esp32s2, esp32s3)),
- doc = "let analog_pin = io.pins.gpio2;"
+ doc = "let analog_pin = peripherals.GPIO2;"
)]
//! let mut adc1_config = AdcConfig::new();
//! let mut pin = adc1_config.enable_pin(
@@ -237,17 +235,17 @@ trait AdcCalEfuse {
/// Get ADC calibration init code
///
/// Returns digital value for zero voltage for a given attenuation
- fn get_init_code(atten: Attenuation) -> Option;
+ fn init_code(atten: Attenuation) -> Option;
/// Get ADC calibration reference point voltage
///
/// Returns reference voltage (millivolts) for a given attenuation
- fn get_cal_mv(atten: Attenuation) -> u16;
+ fn cal_mv(atten: Attenuation) -> u16;
/// Get ADC calibration reference point digital value
///
/// Returns digital value for reference voltage for a given attenuation
- fn get_cal_code(atten: Attenuation) -> Option;
+ fn cal_code(atten: Attenuation) -> Option;
}
macro_rules! impl_adc_interface {
diff --git a/esp-hal/src/analog/adc/riscv.rs b/esp-hal/src/analog/adc/riscv.rs
index b25eaa2fc86..c9c4bcdd655 100644
--- a/esp-hal/src/analog/adc/riscv.rs
+++ b/esp-hal/src/analog/adc/riscv.rs
@@ -502,36 +502,36 @@ where
#[cfg(any(esp32c2, esp32c3, esp32c6))]
impl super::AdcCalEfuse for crate::peripherals::ADC1 {
- fn get_init_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_init_code(1, atten)
+ fn init_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_init_code(1, atten)
}
- fn get_cal_mv(atten: Attenuation) -> u16 {
- Efuse::get_rtc_calib_cal_mv(1, atten)
+ fn cal_mv(atten: Attenuation) -> u16 {
+ Efuse::rtc_calib_cal_mv(1, atten)
}
- fn get_cal_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_cal_code(1, atten)
+ fn cal_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_cal_code(1, atten)
}
}
#[cfg(esp32c3)]
impl super::AdcCalEfuse for crate::peripherals::ADC2 {
- fn get_init_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_init_code(2, atten)
+ fn init_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_init_code(2, atten)
}
- fn get_cal_mv(atten: Attenuation) -> u16 {
- Efuse::get_rtc_calib_cal_mv(2, atten)
+ fn cal_mv(atten: Attenuation) -> u16 {
+ Efuse::rtc_calib_cal_mv(2, atten)
}
- fn get_cal_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_cal_code(2, atten)
+ fn cal_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_cal_code(2, atten)
}
}
-impl<'d, ADCI, PIN, CS> embedded_hal_02::adc::OneShot>
- for Adc<'d, ADCI>
+impl embedded_hal_02::adc::OneShot>
+ for Adc<'_, ADCI>
where
PIN: embedded_hal_02::adc::Channel + super::AdcChannel,
ADCI: RegisterAccess,
diff --git a/esp-hal/src/analog/adc/xtensa.rs b/esp-hal/src/analog/adc/xtensa.rs
index bc9d6ef3202..324c79462db 100644
--- a/esp-hal/src/analog/adc/xtensa.rs
+++ b/esp-hal/src/analog/adc/xtensa.rs
@@ -562,31 +562,31 @@ where
#[cfg(esp32s3)]
impl super::AdcCalEfuse for crate::peripherals::ADC1 {
- fn get_init_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_init_code(1, atten)
+ fn init_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_init_code(1, atten)
}
- fn get_cal_mv(atten: Attenuation) -> u16 {
- Efuse::get_rtc_calib_cal_mv(1, atten)
+ fn cal_mv(atten: Attenuation) -> u16 {
+ Efuse::rtc_calib_cal_mv(1, atten)
}
- fn get_cal_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_cal_code(1, atten)
+ fn cal_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_cal_code(1, atten)
}
}
#[cfg(esp32s3)]
impl super::AdcCalEfuse for crate::peripherals::ADC2 {
- fn get_init_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_init_code(2, atten)
+ fn init_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_init_code(2, atten)
}
- fn get_cal_mv(atten: Attenuation) -> u16 {
- Efuse::get_rtc_calib_cal_mv(2, atten)
+ fn cal_mv(atten: Attenuation) -> u16 {
+ Efuse::rtc_calib_cal_mv(2, atten)
}
- fn get_cal_code(atten: Attenuation) -> Option {
- Efuse::get_rtc_calib_cal_code(2, atten)
+ fn cal_code(atten: Attenuation) -> Option {
+ Efuse::rtc_calib_cal_code(2, atten)
}
}
diff --git a/esp-hal/src/analog/dac.rs b/esp-hal/src/analog/dac.rs
index 8cb1e859db3..246dfa796c0 100644
--- a/esp-hal/src/analog/dac.rs
+++ b/esp-hal/src/analog/dac.rs
@@ -17,14 +17,11 @@
//! ### Write a value to a DAC channel
//! ```rust, no_run
#![doc = crate::before_snippet!()]
-//! # use esp_hal::gpio::Io;
//! # use esp_hal::analog::dac::Dac;
//! # use esp_hal::delay::Delay;
//! # use embedded_hal::delay::DelayNs;
-//!
-//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
-#![cfg_attr(esp32, doc = "let dac1_pin = io.pins.gpio25;")]
-#![cfg_attr(esp32s2, doc = "let dac1_pin = io.pins.gpio17;")]
+#![cfg_attr(esp32, doc = "let dac1_pin = peripherals.GPIO25;")]
+#![cfg_attr(esp32s2, doc = "let dac1_pin = peripherals.GPIO17;")]
//! let mut dac1 = Dac::new(peripherals.DAC1, dac1_pin);
//!
//! let mut delay = Delay::new();
diff --git a/esp-hal/src/assist_debug.rs b/esp-hal/src/assist_debug.rs
index 4e5fe1e8c90..d99a0334613 100644
--- a/esp-hal/src/assist_debug.rs
+++ b/esp-hal/src/assist_debug.rs
@@ -26,7 +26,7 @@
use crate::{
interrupt::InterruptHandler,
peripheral::{Peripheral, PeripheralRef},
- peripherals::ASSIST_DEBUG,
+ peripherals::{Interrupt, ASSIST_DEBUG},
InterruptConfigurable,
};
@@ -47,26 +47,23 @@ impl<'d> DebugAssist<'d> {
}
}
-impl<'d> crate::private::Sealed for DebugAssist<'d> {}
+impl crate::private::Sealed for DebugAssist<'_> {}
-impl<'d> InterruptConfigurable for DebugAssist<'d> {
+impl InterruptConfigurable for DebugAssist<'_> {
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
- unsafe {
- crate::interrupt::bind_interrupt(
- crate::peripherals::Interrupt::ASSIST_DEBUG,
- handler.handler(),
- );
- crate::interrupt::enable(
- crate::peripherals::Interrupt::ASSIST_DEBUG,
- handler.priority(),
- )
- .unwrap();
+ for core in crate::Cpu::other() {
+ crate::interrupt::disable(core, Interrupt::ASSIST_DEBUG);
}
+ unsafe { crate::interrupt::bind_interrupt(Interrupt::ASSIST_DEBUG, handler.handler()) };
+ unwrap!(crate::interrupt::enable(
+ Interrupt::ASSIST_DEBUG,
+ handler.priority()
+ ));
}
}
#[cfg(assist_debug_sp_monitor)]
-impl<'d> DebugAssist<'d> {
+impl DebugAssist<'_> {
/// Enable SP monitoring on main core. When the SP exceeds the
/// `lower_bound` or `upper_bound` threshold, the module will record the PC
/// pointer and generate an interrupt.
@@ -139,7 +136,7 @@ impl<'d> DebugAssist<'d> {
}
/// Get SP monitoring PC value on main core.
- pub fn get_sp_monitor_pc(&self) -> u32 {
+ pub fn sp_monitor_pc(&self) -> u32 {
self.debug_assist
.core_0_sp_pc()
.read()
@@ -222,13 +219,13 @@ impl<'d> DebugAssist<'d> {
}
/// Get SP monitoring PC value on secondary core.
- pub fn get_core1_sp_monitor_pc(&self) -> u32 {
+ pub fn core1_sp_monitor_pc(&self) -> u32 {
self.debug_assist.core_1_sp_pc.read().core_1_sp_pc().bits()
}
}
#[cfg(assist_debug_region_monitor)]
-impl<'d> DebugAssist<'d> {
+impl DebugAssist<'_> {
/// Enable region monitoring of read/write performed by the main CPU in a
/// certain memory region0. Whenever the bus reads or writes in the
/// specified memory region, an interrupt will be triggered. Two memory
@@ -385,7 +382,7 @@ impl<'d> DebugAssist<'d> {
}
/// Get region monitoring PC value on main core.
- pub fn get_region_monitor_pc(&self) -> u32 {
+ pub fn region_monitor_pc(&self) -> u32 {
self.debug_assist
.core_0_area_pc()
.read()
@@ -551,7 +548,7 @@ impl<'d> DebugAssist<'d> {
}
/// Get region monitoring PC value on secondary core.
- pub fn get_core1_region_monitor_pc(&self) -> u32 {
+ pub fn core1_region_monitor_pc(&self) -> u32 {
self.debug_assist
.core_1_area_pc()
.read()
diff --git a/esp-hal/src/clock/clocks_ll/esp32c6.rs b/esp-hal/src/clock/clocks_ll/esp32c6.rs
index ef349384ba7..71cec2c84f9 100644
--- a/esp-hal/src/clock/clocks_ll/esp32c6.rs
+++ b/esp-hal/src/clock/clocks_ll/esp32c6.rs
@@ -244,7 +244,7 @@ fn clk_ll_mspi_fast_set_hs_divider(divider: u32) {
.mspi_clk_conf()
.modify(|_, w| w.mspi_fast_hs_div_num().bits(5)),
_ => panic!("Unsupported HS MSPI_FAST divider"),
- }
+ };
}
}
diff --git a/esp-hal/src/dma/buffers.rs b/esp-hal/src/dma/buffers.rs
index b3f2d43351f..c00b9c46912 100644
--- a/esp-hal/src/dma/buffers.rs
+++ b/esp-hal/src/dma/buffers.rs
@@ -7,11 +7,12 @@ use crate::soc::is_slice_in_psram;
/// Holds all the information needed to configure a DMA channel for a transfer.
pub struct Preparation {
- pub(super) start: *mut DmaDescriptor,
+ /// The descriptor the DMA will start from.
+ pub start: *mut DmaDescriptor,
- /// block size for PSRAM transfers
+ /// Block size for PSRAM transfers
#[cfg_attr(not(esp32s3), allow(dead_code))]
- pub(super) block_size: Option,
+ pub block_size: Option,
/// Specifies whether descriptor linked list specified in `start` conforms
/// to the alignment requirements required to enable burst transfers.
@@ -22,8 +23,35 @@ pub struct Preparation {
/// There are no additional alignment requirements for TX burst transfers,
/// but RX transfers require all descriptors to have buffer pointers and
/// sizes that are a multiple of 4 (word aligned).
- pub(super) is_burstable: bool,
- // alignment, check_owner, etc.
+ pub is_burstable: bool,
+
+ /// Configures the "check owner" feature of the DMA channel.
+ ///
+ /// Most DMA channels allow software to configure whether the hardware
+ /// checks that [DmaDescriptor::owner] is set to [Owner::Dma] before
+ /// consuming the descriptor. If this check fails, the channel stops
+ /// operating and fires
+ /// [DmaRxInterrupt::DescriptorError]/[DmaTxInterrupt::DescriptorError].
+ ///
+ /// This field allows buffer implementation to configure this behaviour.
+ /// - `Some(true)`: DMA channel must check the owner bit.
+ /// - `Some(false)`: DMA channel must NOT check the owner bit.
+ /// - `None`: DMA channel should check the owner bit if it is supported.
+ ///
+ /// Some buffer implementations may require that the DMA channel performs
+ /// this check before consuming the descriptor to ensure correct
+ /// behaviour. e.g. To prevent wrap-around in a circular transfer.
+ ///
+ /// Some buffer implementations may require that the DMA channel does NOT
+ /// perform this check as the ownership bit will not be set before the
+ /// channel tries to consume the descriptor.
+ ///
+ /// Most implementations won't have any such requirements and will work
+ /// correctly regardless of whether the DMA channel checks or not.
+ ///
+ /// Note: If the DMA channel doesn't support the provided option,
+ /// preparation will fail.
+ pub check_owner: Option,
}
/// [DmaTxBuffer] is a DMA descriptor + memory combo that can be used for
@@ -303,6 +331,7 @@ unsafe impl DmaTxBuffer for DmaTxBuf {
block_size: self.block_size,
// This is TX, the DMA channel is free to do a burst transfer.
is_burstable: true,
+ check_owner: None,
}
}
@@ -453,6 +482,7 @@ unsafe impl DmaRxBuffer for DmaRxBuf {
// In the future, it could either enforce the alignment or calculate if the alignment
// requirements happen to be met.
is_burstable: false,
+ check_owner: None,
}
}
@@ -580,6 +610,7 @@ unsafe impl DmaTxBuffer for DmaRxTxBuf {
// This is TX, the DMA channel is free to do a burst transfer.
is_burstable: true,
+ check_owner: None,
}
}
@@ -611,6 +642,7 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf {
// DmaRxTxBuf doesn't currently enforce the alignment requirements required for
// bursting.
is_burstable: false,
+ check_owner: None,
}
}
@@ -751,6 +783,12 @@ unsafe impl DmaRxBuffer for DmaRxStreamBuf {
// DmaRxStreamBuf doesn't currently enforce the alignment requirements required for
// bursting.
is_burstable: false,
+
+ // Whilst we give ownership of the descriptors the DMA, the correctness of this buffer
+ // implementation doesn't rely on the DMA checking for descriptor ownership.
+ // No descriptor is added back to the end of the stream before it's ready for the DMA
+ // to consume it.
+ check_owner: None,
}
}
@@ -958,6 +996,10 @@ unsafe impl DmaTxBuffer for EmptyBuf {
// This is TX, the DMA channel is free to do a burst transfer.
is_burstable: true,
+
+ // As we don't give ownership of the descriptor to the DMA, it's important that the DMA
+ // channel does *NOT* check for ownership, otherwise the channel will return an error.
+ check_owner: Some(false),
}
}
@@ -985,6 +1027,10 @@ unsafe impl DmaRxBuffer for EmptyBuf {
// As much as bursting is meaningless here, the descriptor does meet the requirements.
is_burstable: true,
+
+ // As we don't give ownership of the descriptor to the DMA, it's important that the DMA
+ // channel does *NOT* check for ownership, otherwise the channel will return an error.
+ check_owner: Some(false),
}
}
diff --git a/esp-hal/src/dma/gdma.rs b/esp-hal/src/dma/gdma.rs
index 59d19ecdb80..8ac8b6b4731 100644
--- a/esp-hal/src/dma/gdma.rs
+++ b/esp-hal/src/dma/gdma.rs
@@ -17,7 +17,9 @@
use crate::{
dma::*,
peripheral::PeripheralRef,
+ peripherals::Interrupt,
system::{Peripheral, PeripheralClockControl},
+ Blocking,
};
#[doc(hidden)]
@@ -33,6 +35,36 @@ impl crate::private::Sealed for AnyGdmaChannel {}
impl DmaChannel for AnyGdmaChannel {
type Rx = ChannelRxImpl;
type Tx = ChannelTxImpl;
+
+ fn async_handler(ch: &Channel<'_, Self, M>) -> InterruptHandler {
+ match ch.tx.tx_impl.0.number() {
+ 0 => DmaChannel0::handler(),
+ #[cfg(not(esp32c2))]
+ 1 => DmaChannel1::handler(),
+ #[cfg(not(esp32c2))]
+ 2 => DmaChannel2::handler(),
+ #[cfg(esp32s3)]
+ 3 => DmaChannel3::handler(),
+ #[cfg(esp32s3)]
+ 4 => DmaChannel4::handler(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn interrupts(ch: &Channel<'_, Self, M>) -> &'static [Interrupt] {
+ match ch.tx.tx_impl.0.number() {
+ 0 => DmaChannel0::isrs(),
+ #[cfg(not(esp32c2))]
+ 1 => DmaChannel1::isrs(),
+ #[cfg(not(esp32c2))]
+ 2 => DmaChannel2::isrs(),
+ #[cfg(esp32s3)]
+ 3 => DmaChannel3::isrs(),
+ #[cfg(esp32s3)]
+ 4 => DmaChannel4::isrs(),
+ _ => unreachable!(),
+ }
+ }
}
#[non_exhaustive]
@@ -142,6 +174,12 @@ impl RegisterAccess for ChannelTxImpl {
.modify(|_, w| w.outlink_restart().set_bit());
}
+ fn set_check_owner(&self, check_owner: Option) {
+ self.ch()
+ .out_conf1()
+ .modify(|_, w| w.out_check_owner().bit(check_owner.unwrap_or(true)));
+ }
+
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
self.ch()
@@ -151,6 +189,12 @@ impl RegisterAccess for ChannelTxImpl {
}
impl TxRegisterAccess for ChannelTxImpl {
+ fn set_auto_write_back(&self, enable: bool) {
+ self.ch()
+ .out_conf0()
+ .modify(|_, w| w.out_auto_wrback().bit(enable));
+ }
+
fn last_dscr_address(&self) -> usize {
self.ch()
.out_eof_des_addr()
@@ -172,7 +216,7 @@ impl InterruptAccess for ChannelTxImpl {
};
}
w
- })
+ });
}
fn is_listening(&self) -> EnumSet {
@@ -206,7 +250,7 @@ impl InterruptAccess for ChannelTxImpl {
};
}
w
- })
+ });
}
fn pending_interrupts(&self) -> EnumSet {
@@ -321,6 +365,12 @@ impl RegisterAccess for ChannelRxImpl {
.modify(|_, w| w.inlink_restart().set_bit());
}
+ fn set_check_owner(&self, check_owner: Option) {
+ self.ch()
+ .in_conf1()
+ .modify(|_, w| w.in_check_owner().bit(check_owner.unwrap_or(true)));
+ }
+
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
self.ch()
@@ -388,7 +438,7 @@ impl InterruptAccess for ChannelRxImpl {
};
}
w
- })
+ });
}
fn pending_interrupts(&self) -> EnumSet {
@@ -425,10 +475,7 @@ pub struct ChannelCreator {}
impl Channel<'_, CH, M> {
/// Asserts that the channel is compatible with the given peripheral.
- pub fn runtime_ensure_compatible(
- &self,
- _peripheral: &PeripheralRef<'_, P>,
- ) {
+ pub fn runtime_ensure_compatible(&self, _peripheral: &PeripheralRef<'_, P>) {
// No runtime checks; GDMA channels are compatible with any peripheral
}
}
@@ -442,9 +489,27 @@ macro_rules! impl_channel {
impl crate::private::Sealed for [] {}
+ impl [] {
+ fn handler() -> InterruptHandler {
+ $async_handler
+ }
+
+ fn isrs() -> &'static [Interrupt] {
+ &[$(Interrupt::$interrupt),*]
+ }
+ }
+
impl DmaChannel for [] {
type Rx = ChannelRxImpl>;
type Tx = ChannelTxImpl>;
+
+ fn async_handler(_ch: &Channel<'_, Self, M>) -> InterruptHandler {
+ Self::handler()
+ }
+
+ fn interrupts(_ch: &Channel<'_, Self, M>,) -> &'static [Interrupt] {
+ Self::isrs()
+ }
}
impl DmaChannelConvert for [] {
@@ -457,71 +522,29 @@ macro_rules! impl_channel {
}
impl DmaChannelExt for [] {
- fn get_rx_interrupts() -> impl InterruptAccess {
+ fn rx_interrupts() -> impl InterruptAccess {
ChannelRxImpl(SpecificGdmaChannel::<$num> {})
}
- fn get_tx_interrupts() -> impl InterruptAccess {
+ fn tx_interrupts() -> impl InterruptAccess {
ChannelTxImpl(SpecificGdmaChannel::<$num> {})
}
-
- fn set_isr(handler: $crate::interrupt::InterruptHandler) {
- let mut dma = unsafe { crate::peripherals::DMA::steal() };
- $(
- dma.[< bind_ $interrupt:lower _interrupt >](handler.handler());
- $crate::interrupt::enable($crate::peripherals::Interrupt::$interrupt, handler.priority()).unwrap();
- )*
- }
}
impl ChannelCreator<$num> {
- fn do_configure<'a, M: crate::Mode>(
- self,
- burst_mode: bool,
- priority: DmaPriority,
- ) -> crate::dma::Channel<'a, [], M> {
- let tx_impl = ChannelTxImpl(SpecificGdmaChannel::<$num> {});
- tx_impl.set_burst_mode(burst_mode);
- tx_impl.set_priority(priority);
-
- let rx_impl = ChannelRxImpl(SpecificGdmaChannel::<$num> {});
- rx_impl.set_burst_mode(burst_mode);
- rx_impl.set_priority(priority);
- // clear the mem2mem mode to avoid failed DMA if this
- // channel was previously used for a mem2mem transfer.
- rx_impl.set_mem2mem_mode(false);
-
- crate::dma::Channel {
- tx: ChannelTx::new(tx_impl, burst_mode),
- rx: ChannelRx::new(rx_impl, burst_mode),
- phantom: PhantomData,
- }
- }
-
/// Configure the channel for use with blocking APIs
- ///
- /// Descriptors should be sized as `(CHUNK_SIZE + 4091) / 4092`. I.e., to
- /// transfer buffers of size `1..=4092`, you need 1 descriptor.
pub fn configure<'a>(
self,
burst_mode: bool,
priority: DmaPriority,
- ) -> crate::dma::Channel<'a, [], crate::Blocking> {
- self.do_configure(burst_mode, priority)
- }
-
- /// Configure the channel for use with async APIs
- ///
- /// Descriptors should be sized as `(CHUNK_SIZE + 4091) / 4092`. I.e., to
- /// transfer buffers of size `1..=4092`, you need 1 descriptor.
- pub fn configure_for_async<'a>(
- self,
- burst_mode: bool,
- priority: DmaPriority,
- ) -> crate::dma::Channel<'a, [], $crate::Async> {
- let this = self.do_configure(burst_mode, priority);
+ ) -> Channel<'a, [], Blocking> {
+ let mut this = Channel {
+ tx: ChannelTx::new(ChannelTxImpl(SpecificGdmaChannel::<$num> {})),
+ rx: ChannelRx::new(ChannelRxImpl(SpecificGdmaChannel::<$num> {})),
+ phantom: PhantomData,
+ };
- []::set_isr($async_handler);
+ this.configure(burst_mode, priority);
this
}
diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs
index 0e0aee187d1..b1ff0f60bc5 100644
--- a/esp-hal/src/dma/m2m.rs
+++ b/esp-hal/src/dma/m2m.rs
@@ -18,6 +18,8 @@ use crate::{
Tx,
WriteBuffer,
},
+ Async,
+ Blocking,
Mode,
};
@@ -36,19 +38,18 @@ where
peripheral: DmaPeripheral,
}
-impl<'d, M> Mem2Mem<'d, M>
-where
- M: Mode,
-{
+impl<'d> Mem2Mem<'d, Blocking> {
/// Create a new Mem2Mem instance.
- pub fn new(
- channel: Channel<'d, CH, M>,
+ pub fn new(
+ channel: Channel<'d, CH, DM>,
peripheral: impl DmaEligible,
rx_descriptors: &'static mut [DmaDescriptor],
tx_descriptors: &'static mut [DmaDescriptor],
) -> Result
where
CH: DmaChannelConvert,
+ DM: Mode,
+ Channel<'d, CH, Blocking>: From>,
{
unsafe {
Self::new_unsafe(
@@ -62,8 +63,8 @@ where
}
/// Create a new Mem2Mem instance with specific chunk size.
- pub fn new_with_chunk_size(
- channel: Channel<'d, CH, M>,
+ pub fn new_with_chunk_size(
+ channel: Channel<'d, CH, DM>,
peripheral: impl DmaEligible,
rx_descriptors: &'static mut [DmaDescriptor],
tx_descriptors: &'static mut [DmaDescriptor],
@@ -71,6 +72,8 @@ where
) -> Result
where
CH: DmaChannelConvert,
+ DM: Mode,
+ Channel<'d, CH, Blocking>: From>,
{
unsafe {
Self::new_unsafe(
@@ -89,8 +92,8 @@ where
///
/// You must ensure that your not using DMA for the same peripheral and
/// that your the only one using the DmaPeripheral.
- pub unsafe fn new_unsafe(
- channel: Channel<'d, CH, M>,
+ pub unsafe fn new_unsafe(
+ channel: Channel<'d, CH, DM>,
peripheral: DmaPeripheral,
rx_descriptors: &'static mut [DmaDescriptor],
tx_descriptors: &'static mut [DmaDescriptor],
@@ -98,6 +101,8 @@ where
) -> Result
where
CH: DmaChannelConvert,
+ DM: Mode,
+ Channel<'d, CH, Blocking>: From>,
{
if !(1..=4092).contains(&chunk_size) {
return Err(DmaError::InvalidChunkSize);
@@ -106,13 +111,28 @@ where
return Err(DmaError::OutOfDescriptors);
}
Ok(Mem2Mem {
- channel: channel.degrade(),
+ channel: Channel::<_, Blocking>::from(channel).degrade(),
peripheral,
rx_chain: DescriptorChain::new_with_chunk_size(rx_descriptors, chunk_size),
tx_chain: DescriptorChain::new_with_chunk_size(tx_descriptors, chunk_size),
})
}
+ /// Convert Mem2Mem to an async Mem2Mem.
+ pub fn into_async(self) -> Mem2Mem<'d, Async> {
+ Mem2Mem {
+ channel: self.channel.into_async(),
+ rx_chain: self.rx_chain,
+ tx_chain: self.tx_chain,
+ peripheral: self.peripheral,
+ }
+ }
+}
+
+impl Mem2Mem<'_, M>
+where
+ M: Mode,
+{
/// Start a memory to memory transfer.
pub fn start_transfer<'t, TXBUF, RXBUF>(
&mut self,
@@ -157,7 +177,7 @@ where
}
}
-impl<'d, MODE> DmaSupport for Mem2Mem<'d, MODE>
+impl DmaSupport for Mem2Mem<'_, MODE>
where
MODE: Mode,
{
diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs
index c228517e691..6deca41d657 100644
--- a/esp-hal/src/dma/mod.rs
+++ b/esp-hal/src/dma/mod.rs
@@ -18,22 +18,23 @@
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! # use esp_hal::dma_buffers;
-//! # use esp_hal::gpio::Io;
-//! # use esp_hal::spi::{master::Spi, SpiMode};
+//! # use esp_hal::spi::{master::{Config, Spi}, SpiMode};
//! # use esp_hal::dma::{Dma, DmaPriority};
//! let dma = Dma::new(peripherals.DMA);
#![cfg_attr(any(esp32, esp32s2), doc = "let dma_channel = dma.spi2channel;")]
#![cfg_attr(not(any(esp32, esp32s2)), doc = "let dma_channel = dma.channel0;")]
-//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
-//! let sclk = io.pins.gpio0;
-//! let miso = io.pins.gpio2;
-//! let mosi = io.pins.gpio4;
-//! let cs = io.pins.gpio5;
+//! let sclk = peripherals.GPIO0;
+//! let miso = peripherals.GPIO2;
+//! let mosi = peripherals.GPIO4;
+//! let cs = peripherals.GPIO5;
//!
-//! let mut spi = Spi::new(
+//! let mut spi = Spi::new_with_config(
//! peripherals.SPI2,
-//! 100.kHz(),
-//! SpiMode::Mode0,
+//! Config {
+//! frequency: 100.kHz(),
+//! mode: SpiMode::Mode0,
+//! ..Config::default()
+//! },
//! )
//! .with_sck(sclk)
//! .with_mosi(mosi)
@@ -58,6 +59,25 @@
use core::{cmp::min, fmt::Debug, marker::PhantomData, sync::atomic::compiler_fence};
+use enumset::{EnumSet, EnumSetType};
+
+pub use self::buffers::*;
+#[cfg(gdma)]
+pub use self::gdma::*;
+#[cfg(gdma)]
+pub use self::m2m::*;
+#[cfg(pdma)]
+pub use self::pdma::*;
+use crate::{
+ interrupt::InterruptHandler,
+ peripherals::Interrupt,
+ soc::is_slice_in_dram,
+ Async,
+ Blocking,
+ Cpu,
+ Mode,
+};
+
trait Word: crate::private::Sealed {}
macro_rules! impl_word {
@@ -351,16 +371,10 @@ impl DmaDescriptor {
}
}
-use enumset::{EnumSet, EnumSetType};
-
-pub use self::buffers::*;
-#[cfg(gdma)]
-pub use self::gdma::*;
-#[cfg(gdma)]
-pub use self::m2m::*;
-#[cfg(pdma)]
-pub use self::pdma::*;
-use crate::{interrupt::InterruptHandler, soc::is_slice_in_dram, Mode};
+// The pointers in the descriptor can be Sent.
+// Marking this Send also allows DmaBuffer implementations to automatically be
+// Send (where the compiler sees fit).
+unsafe impl Send for DmaDescriptor {}
mod buffers;
#[cfg(gdma)]
@@ -781,6 +795,9 @@ pub enum DmaError {
UnsupportedMemoryRegion,
/// Invalid DMA chunk size
InvalidChunkSize,
+ /// Indicates writing to or reading from a circular DMA transaction is done
+ /// too late and the DMA buffers already overrun / underrun.
+ Late,
}
impl From for DmaError {
@@ -930,12 +947,6 @@ macro_rules! impl_dma_eligible {
};
}
-/// Marker trait
-#[doc(hidden)]
-pub trait PeripheralMarker {
- fn peripheral(&self) -> crate::system::Peripheral;
-}
-
#[doc(hidden)]
#[derive(Debug)]
pub struct DescriptorChain {
@@ -1313,7 +1324,7 @@ impl TxCircularState {
}
}
- pub(crate) fn update(&mut self, channel: &T)
+ pub(crate) fn update(&mut self, channel: &T) -> Result<(), DmaError>
where
T: Tx,
{
@@ -1323,6 +1334,23 @@ impl TxCircularState {
{
channel.clear_out(DmaTxInterrupt::Eof);
+ // check if all descriptors are owned by CPU - this indicates we failed to push
+ // data fast enough in future we can enable `check_owner` and check
+ // the interrupt instead
+ let mut current = self.last_seen_handled_descriptor_ptr;
+ loop {
+ let descr = unsafe { current.read_volatile() };
+ if descr.owner() == Owner::Cpu {
+ current = descr.next;
+ } else {
+ break;
+ }
+
+ if current == self.last_seen_handled_descriptor_ptr {
+ return Err(DmaError::Late);
+ }
+ }
+
let descr_address = channel.last_out_dscr_address() as *mut DmaDescriptor;
let mut ptr = self.last_seen_handled_descriptor_ptr;
@@ -1376,6 +1404,8 @@ impl TxCircularState {
self.last_seen_handled_descriptor_ptr = descr_address;
}
+
+ Ok(())
}
pub(crate) fn push(&mut self, data: &[u8]) -> Result {
@@ -1404,6 +1434,8 @@ impl TxCircularState {
&mut self,
f: impl FnOnce(&mut [u8]) -> usize,
) -> Result {
+ // this might write less than available in case of a wrap around
+ // caller needs to check and write the remaining part
let written = unsafe {
let dst = self.buffer_start.add(self.write_offset).cast_mut();
let block_size = usize::min(self.available, self.buffer_len - self.write_offset);
@@ -1414,12 +1446,15 @@ impl TxCircularState {
let mut forward = written;
loop {
unsafe {
- let dw0 = self.write_descr_ptr.read_volatile();
- let segment_len = dw0.len();
- self.write_descr_ptr = if dw0.next.is_null() {
+ let mut descr = self.write_descr_ptr.read_volatile();
+ descr.set_owner(Owner::Dma);
+ self.write_descr_ptr.write_volatile(descr);
+
+ let segment_len = descr.len();
+ self.write_descr_ptr = if descr.next.is_null() {
self.first_desc_ptr
} else {
- dw0.next
+ descr.next
};
if forward <= segment_len {
@@ -1454,7 +1489,7 @@ impl RxCircularState {
}
}
- pub(crate) fn update(&mut self) {
+ pub(crate) fn update(&mut self) -> Result<(), DmaError> {
if self.last_seen_handled_descriptor_ptr.is_null() {
// initially start at last descriptor (so that next will be the first
// descriptor)
@@ -1465,6 +1500,7 @@ impl RxCircularState {
unsafe { self.last_seen_handled_descriptor_ptr.read_volatile() }.next;
let mut current_in_descr = unsafe { current_in_descr_ptr.read_volatile() };
+ let last_seen_ptr = self.last_seen_handled_descriptor_ptr;
while current_in_descr.owner() == Owner::Cpu {
self.available += current_in_descr.len();
self.last_seen_handled_descriptor_ptr = current_in_descr_ptr;
@@ -1472,7 +1508,13 @@ impl RxCircularState {
current_in_descr_ptr =
unsafe { self.last_seen_handled_descriptor_ptr.read_volatile() }.next;
current_in_descr = unsafe { current_in_descr_ptr.read_volatile() };
+
+ if current_in_descr_ptr == last_seen_ptr {
+ return Err(DmaError::Late);
+ }
}
+
+ Ok(())
}
pub(crate) fn pop(&mut self, data: &mut [u8]) -> Result {
@@ -1523,23 +1565,31 @@ impl RxCircularState {
}
/// A description of a DMA Channel.
-pub trait DmaChannel: crate::private::Sealed {
+pub trait DmaChannel: crate::private::Sealed + Sized {
/// A description of the RX half of a DMA Channel.
type Rx: RxRegisterAccess + InterruptAccess;
/// A description of the TX half of a DMA Channel.
type Tx: TxRegisterAccess + InterruptAccess;
+
+ /// Returns the async interrupt handler.
+ fn async_handler(ch: &Channel<'_, Self, M>) -> InterruptHandler;
+
+ /// Returns the interrupt.
+ fn interrupts(ch: &Channel<'_, Self, M>) -> &'static [Interrupt];
}
#[doc(hidden)]
pub trait DmaChannelExt: DmaChannel {
- fn get_rx_interrupts() -> impl InterruptAccess;
- fn get_tx_interrupts() -> impl InterruptAccess;
-
- #[doc(hidden)]
- fn set_isr(handler: InterruptHandler);
+ fn rx_interrupts() -> impl InterruptAccess;
+ fn tx_interrupts() -> impl InterruptAccess;
}
+#[diagnostic::on_unimplemented(
+ message = "The DMA channel isn't suitable for this peripheral",
+ label = "This DMA channel",
+ note = "Not all channels are useable with all peripherals"
+)]
#[doc(hidden)]
pub trait DmaChannelConvert: DmaChannel {
fn degrade_rx(rx: Self::Rx) -> DEG::Rx;
@@ -1629,9 +1679,14 @@ impl<'a, CH> ChannelRx<'a, CH>
where
CH: DmaChannel,
{
- fn new(rx_impl: CH::Rx, burst_mode: bool) -> Self {
+ fn new(rx_impl: CH::Rx) -> Self {
+ #[cfg(gdma)]
+ // clear the mem2mem mode to avoid failed DMA if this
+ // channel was previously used for a mem2mem transfer.
+ rx_impl.set_mem2mem_mode(false);
+
Self {
- burst_mode,
+ burst_mode: false,
rx_impl,
_phantom: PhantomData,
}
@@ -1649,11 +1704,17 @@ where
_phantom: PhantomData,
}
}
+
+ /// Configure the channel.
+ pub fn configure(&mut self, burst_mode: bool, priority: DmaPriority) {
+ self.burst_mode = burst_mode;
+ self.rx_impl.configure(burst_mode, priority);
+ }
}
-impl<'a, CH> crate::private::Sealed for ChannelRx<'a, CH> where CH: DmaChannel {}
+impl crate::private::Sealed for ChannelRx<'_, CH> where CH: DmaChannel {}
-impl<'a, CH> Rx for ChannelRx<'a, CH>
+impl Rx for ChannelRx<'_, CH>
where
CH: DmaChannel,
{
@@ -1709,6 +1770,8 @@ where
self.rx_impl
.set_burst_mode(self.burst_mode && preparation.is_burstable);
+ self.rx_impl.set_check_owner(preparation.check_owner);
+
compiler_fence(core::sync::atomic::Ordering::SeqCst);
self.rx_impl.clear_all();
@@ -1845,9 +1908,9 @@ impl<'a, CH> ChannelTx<'a, CH>
where
CH: DmaChannel,
{
- fn new(tx_impl: CH::Tx, burst_mode: bool) -> Self {
+ fn new(tx_impl: CH::Tx) -> Self {
Self {
- burst_mode,
+ burst_mode: false,
tx_impl,
_phantom: PhantomData,
}
@@ -1865,11 +1928,17 @@ where
_phantom: PhantomData,
}
}
+
+ /// Configure the channel.
+ pub fn configure(&mut self, burst_mode: bool, priority: DmaPriority) {
+ self.burst_mode = burst_mode;
+ self.tx_impl.configure(burst_mode, priority);
+ }
}
-impl<'a, CH> crate::private::Sealed for ChannelTx<'a, CH> where CH: DmaChannel {}
+impl crate::private::Sealed for ChannelTx<'_, CH> where CH: DmaChannel {}
-impl<'a, CH> Tx for ChannelTx<'a, CH>
+impl Tx for ChannelTx<'_, CH>
where
CH: DmaChannel,
{
@@ -1902,6 +1971,10 @@ where
self.tx_impl.set_link_addr(chain.first() as u32);
self.tx_impl.set_peripheral(peri as u8);
+ // enable descriptor write back in circular mode
+ self.tx_impl
+ .set_auto_write_back(!(*chain.last()).next.is_null());
+
Ok(())
}
@@ -1927,6 +2000,8 @@ where
self.tx_impl
.set_burst_mode(self.burst_mode && preparation.is_burstable);
+ self.tx_impl.set_check_owner(preparation.check_owner);
+
compiler_fence(core::sync::atomic::Ordering::SeqCst);
self.tx_impl.clear_all();
@@ -2020,11 +2095,21 @@ pub trait RegisterAccess: crate::private::Sealed {
/// Mount a new descriptor.
fn restart(&self);
+ /// Configure the bit to enable checking the owner attribute of the
+ /// descriptor.
+ fn set_check_owner(&self, check_owner: Option);
+
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize);
#[cfg(pdma)]
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool;
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool;
+
+ /// Configure the channel.
+ fn configure(&self, burst_mode: bool, priority: DmaPriority) {
+ self.set_burst_mode(burst_mode);
+ self.set_priority(priority);
+ }
}
#[doc(hidden)]
@@ -2035,6 +2120,9 @@ pub trait RxRegisterAccess: RegisterAccess {
#[doc(hidden)]
pub trait TxRegisterAccess: RegisterAccess {
+ /// Enable/disable outlink-writeback
+ fn set_auto_write_back(&self, enable: bool);
+
/// Outlink descriptor address when EOF occurs of Tx channel.
fn last_dscr_address(&self) -> usize;
}
@@ -2060,36 +2148,43 @@ pub trait InterruptAccess: crate::private::Sealed {
}
/// DMA Channel
-pub struct Channel<'d, CH, MODE>
+pub struct Channel<'d, CH, M>
where
CH: DmaChannel,
- MODE: Mode,
+ M: Mode,
{
/// RX half of the channel
pub rx: ChannelRx<'d, CH>,
/// TX half of the channel
pub tx: ChannelTx<'d, CH>,
- phantom: PhantomData,
+ pub(crate) phantom: PhantomData,
}
-impl<'d, C> Channel<'d, C, crate::Blocking>
+impl<'d, C> Channel<'d, C, Blocking>
where
C: DmaChannel,
{
- /// Sets the interrupt handler for RX and TX interrupts, enables them
- /// with [crate::interrupt::Priority::max()]
+ /// Sets the interrupt handler for RX and TX interrupts.
///
/// Interrupts are not enabled at the peripheral level here.
pub fn set_interrupt_handler(&mut self, handler: InterruptHandler)
where
- C: DmaChannelExt,
+ C: DmaChannel,
{
- C::set_isr(handler);
+ self.unlisten(EnumSet::all());
+ self.clear_interrupts(EnumSet::all());
+ for interrupt in C::interrupts(self).iter().copied() {
+ for core in crate::Cpu::other() {
+ crate::interrupt::disable(core, interrupt);
+ }
+ unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) };
+ unwrap!(crate::interrupt::enable(interrupt, handler.priority()));
+ }
}
/// Listen for the given interrupts
- pub fn listen(&mut self, interrupts: EnumSet) {
- for interrupt in interrupts {
+ pub fn listen(&mut self, interrupts: impl Into>) {
+ for interrupt in interrupts.into() {
match interrupt {
DmaInterrupt::RxDone => self.rx.listen_in(DmaRxInterrupt::Done),
DmaInterrupt::TxDone => self.tx.listen_out(DmaTxInterrupt::Done),
@@ -2098,8 +2193,8 @@ where
}
/// Unlisten the given interrupts
- pub fn unlisten(&mut self, interrupts: EnumSet) {
- for interrupt in interrupts {
+ pub fn unlisten(&mut self, interrupts: impl Into>) {
+ for interrupt in interrupts.into() {
match interrupt {
DmaInterrupt::RxDone => self.rx.unlisten_in(DmaRxInterrupt::Done),
DmaInterrupt::TxDone => self.tx.unlisten_out(DmaTxInterrupt::Done),
@@ -2120,14 +2215,61 @@ where
}
/// Resets asserted interrupts
- pub fn clear_interrupts(&mut self, interrupts: EnumSet) {
- for interrupt in interrupts {
+ pub fn clear_interrupts(&mut self, interrupts: impl Into>) {
+ for interrupt in interrupts.into() {
match interrupt {
DmaInterrupt::RxDone => self.rx.clear_in(DmaRxInterrupt::Done),
DmaInterrupt::TxDone => self.tx.clear_out(DmaTxInterrupt::Done),
}
}
}
+
+ /// Configure the channel.
+ pub fn configure(&mut self, burst_mode: bool, priority: DmaPriority) {
+ self.tx.configure(burst_mode, priority);
+ self.rx.configure(burst_mode, priority);
+ }
+
+ /// Converts a blocking channel to an async channel.
+ pub fn into_async(mut self) -> Channel<'d, C, Async> {
+ self.set_interrupt_handler(C::async_handler(&self));
+
+ Channel {
+ tx: self.tx,
+ rx: self.rx,
+ phantom: PhantomData,
+ }
+ }
+}
+
+impl<'d, C> Channel<'d, C, Async>
+where
+ C: DmaChannel,
+{
+ /// Converts an async channel to a blocking channel.
+ pub fn into_blocking(self) -> Channel<'d, C, Blocking> {
+ for interrupt in C::interrupts(&self).iter().copied() {
+ crate::interrupt::disable(Cpu::current(), interrupt);
+ }
+
+ Channel {
+ tx: self.tx,
+ rx: self.rx,
+ phantom: PhantomData,
+ }
+ }
+}
+
+impl<'d, C: DmaChannel> From> for Channel<'d, C, Async> {
+ fn from(channel: Channel<'d, C, Blocking>) -> Self {
+ channel.into_async()
+ }
+}
+
+impl<'d, C: DmaChannel> From> for Channel<'d, C, Blocking> {
+ fn from(channel: Channel<'d, C, Async>) -> Self {
+ channel.into_blocking()
+ }
}
impl<'d, CH, M: Mode> Channel<'d, CH, M>
@@ -2231,7 +2373,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferTx<'a, I>
+impl Drop for DmaTransferTx<'_, I>
where
I: dma_private::DmaSupportTx,
{
@@ -2284,7 +2426,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferRx<'a, I>
+impl Drop for DmaTransferRx<'_, I>
where
I: dma_private::DmaSupportRx,
{
@@ -2343,7 +2485,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferRxTx<'a, I>
+impl Drop for DmaTransferRxTx<'_, I>
where
I: dma_private::DmaSupportTx + dma_private::DmaSupportRx,
{
@@ -2378,14 +2520,14 @@ where
}
/// Amount of bytes which can be pushed.
- pub fn available(&mut self) -> usize {
- self.state.update(self.instance.tx());
- self.state.available
+ pub fn available(&mut self) -> Result {
+ self.state.update(self.instance.tx())?;
+ Ok(self.state.available)
}
/// Push bytes into the DMA buffer.
pub fn push(&mut self, data: &[u8]) -> Result {
- self.state.update(self.instance.tx());
+ self.state.update(self.instance.tx())?;
self.state.push(data)
}
@@ -2394,7 +2536,7 @@ where
/// The closure *might* get called with a slice which is smaller than the
/// total available buffer.
pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> Result {
- self.state.update(self.instance.tx());
+ self.state.update(self.instance.tx())?;
self.state.push_with(f)
}
@@ -2416,7 +2558,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferTxCircular<'a, I>
+impl Drop for DmaTransferTxCircular<'_, I>
where
I: dma_private::DmaSupportTx,
{
@@ -2454,9 +2596,9 @@ where
///
/// It's expected to call this before trying to [DmaTransferRxCircular::pop]
/// data.
- pub fn available(&mut self) -> usize {
- self.state.update();
- self.state.available
+ pub fn available(&mut self) -> Result {
+ self.state.update()?;
+ Ok(self.state.available)
}
/// Get available data.
@@ -2468,12 +2610,12 @@ where
/// Fails with [DmaError::BufferTooSmall] if the given buffer is too small
/// to hold all available data
pub fn pop(&mut self, data: &mut [u8]) -> Result {
- self.state.update();
+ self.state.update()?;
self.state.pop(data)
}
}
-impl<'a, I> Drop for DmaTransferRxCircular<'a, I>
+impl Drop for DmaTransferRxCircular<'_, I>
where
I: dma_private::DmaSupportRx,
{
@@ -2504,7 +2646,7 @@ pub(crate) mod asynch {
}
}
- impl<'a, TX> core::future::Future for DmaTxFuture<'a, TX>
+ impl core::future::Future for DmaTxFuture<'_, TX>
where
TX: Tx,
{
@@ -2533,7 +2675,7 @@ pub(crate) mod asynch {
}
}
- impl<'a, TX> Drop for DmaTxFuture<'a, TX>
+ impl Drop for DmaTxFuture<'_, TX>
where
TX: Tx,
{
@@ -2560,7 +2702,7 @@ pub(crate) mod asynch {
}
}
- impl<'a, RX> core::future::Future for DmaRxFuture<'a, RX>
+ impl core::future::Future for DmaRxFuture<'_, RX>
where
RX: Rx,
{
@@ -2593,7 +2735,7 @@ pub(crate) mod asynch {
}
}
- impl<'a, RX> Drop for DmaRxFuture<'a, RX>
+ impl Drop for DmaRxFuture<'_, RX>
where
RX: Rx,
{
@@ -2626,7 +2768,7 @@ pub(crate) mod asynch {
}
#[cfg(any(i2s0, i2s1))]
- impl<'a, TX> core::future::Future for DmaTxDoneChFuture<'a, TX>
+ impl core::future::Future for DmaTxDoneChFuture<'_, TX>
where
TX: Tx,
{
@@ -2660,7 +2802,7 @@ pub(crate) mod asynch {
}
#[cfg(any(i2s0, i2s1))]
- impl<'a, TX> Drop for DmaTxDoneChFuture<'a, TX>
+ impl Drop for DmaTxDoneChFuture<'_, TX>
where
TX: Tx,
{
@@ -2690,7 +2832,7 @@ pub(crate) mod asynch {
}
#[cfg(any(i2s0, i2s1))]
- impl<'a, RX> core::future::Future for DmaRxDoneChFuture<'a, RX>
+ impl core::future::Future for DmaRxDoneChFuture<'_, RX>
where
RX: Rx,
{
@@ -2728,7 +2870,7 @@ pub(crate) mod asynch {
}
#[cfg(any(i2s0, i2s1))]
- impl<'a, RX> Drop for DmaRxDoneChFuture<'a, RX>
+ impl Drop for DmaRxDoneChFuture<'_, RX>
where
RX: Rx,
{
@@ -2743,8 +2885,8 @@ pub(crate) mod asynch {
}
fn handle_interrupt() {
- let rx = CH::get_rx_interrupts();
- let tx = CH::get_tx_interrupts();
+ let rx = CH::rx_interrupts();
+ let tx = CH::tx_interrupts();
if rx.pending_interrupts().is_disjoint(
DmaRxInterrupt::DescriptorError
@@ -2852,13 +2994,13 @@ pub(crate) mod asynch {
#[cfg(i2s0)]
#[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_i2s0() {
+ pub(crate) fn interrupt_handler_i2s0_dma() {
handle_interrupt::();
}
#[cfg(i2s1)]
#[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_i2s1() {
+ pub(crate) fn interrupt_handler_i2s1_dma() {
handle_interrupt::();
}
}
diff --git a/esp-hal/src/dma/pdma.rs b/esp-hal/src/dma/pdma.rs
index de853696aee..bfbaee48dfc 100644
--- a/esp-hal/src/dma/pdma.rs
+++ b/esp-hal/src/dma/pdma.rs
@@ -16,7 +16,9 @@ use embassy_sync::waitqueue::AtomicWaker;
use crate::{
dma::*,
peripheral::PeripheralRef,
+ peripherals::Interrupt,
system::{Peripheral, PeripheralClockControl},
+ Blocking,
};
type SpiRegisterBlock = crate::peripherals::spi2::RegisterBlock;
@@ -29,7 +31,7 @@ pub trait PdmaChannel: crate::private::Sealed {
fn register_block(&self) -> &Self::RegisterBlock;
fn tx_waker(&self) -> &'static AtomicWaker;
fn rx_waker(&self) -> &'static AtomicWaker;
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool;
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool;
}
#[doc(hidden)]
@@ -84,12 +86,23 @@ impl> RegisterAccess for SpiDma
.modify(|_, w| w.outlink_restart().set_bit());
}
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool {
+ fn set_check_owner(&self, check_owner: Option) {
+ if check_owner == Some(true) {
+ panic!("SPI DMA does not support checking descriptor ownership");
+ }
+ }
+
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
}
impl> TxRegisterAccess for SpiDmaTxChannelImpl {
+ fn set_auto_write_back(&self, enable: bool) {
+ // there is no `auto_wrback` for SPI
+ assert!(!enable);
+ }
+
fn last_dscr_address(&self) -> usize {
let spi = self.0.register_block();
spi.out_eof_des_addr().read().dma_out_eof_des_addr().bits() as usize
@@ -111,7 +124,7 @@ impl> InterruptAccess EnumSet {
@@ -147,7 +160,7 @@ impl> InterruptAccess EnumSet {
@@ -217,7 +230,13 @@ impl> RegisterAccess for SpiDma
.modify(|_, w| w.inlink_restart().set_bit());
}
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool {
+ fn set_check_owner(&self, check_owner: Option) {
+ if check_owner == Some(true) {
+ panic!("SPI DMA does not support checking descriptor ownership");
+ }
+ }
+
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
}
@@ -240,7 +259,7 @@ impl> InterruptAccess EnumSet {
@@ -280,7 +299,7 @@ impl> InterruptAccess EnumSet {
@@ -324,26 +343,36 @@ macro_rules! ImplSpiChannel {
#[non_exhaustive]
pub struct [] {}
+ impl [] {
+ fn handler() -> InterruptHandler {
+ super::asynch::interrupt::[< interrupt_handler_spi $num _dma >]
+ }
+
+ fn isrs() -> &'static [Interrupt] {
+ &[Interrupt::[< SPI $num _DMA >]]
+ }
+ }
+
impl DmaChannel for [] {
type Rx = SpiDmaRxChannelImpl;
type Tx = SpiDmaTxChannelImpl;
+
+ fn async_handler(_ch: &Channel<'_, Self, M>) -> InterruptHandler {
+ Self::handler()
+ }
+
+ fn interrupts(_ch: &Channel<'_, Self, M>) -> &'static [Interrupt] {
+ Self::isrs()
+ }
}
impl DmaChannelExt for [] {
- fn get_rx_interrupts() -> impl InterruptAccess {
+ fn rx_interrupts() -> impl InterruptAccess {
SpiDmaRxChannelImpl(Self {})
}
- fn get_tx_interrupts() -> impl InterruptAccess {
+ fn tx_interrupts() -> impl InterruptAccess {
SpiDmaTxChannelImpl(Self {})
}
-
- fn set_isr(handler: InterruptHandler) {
- let interrupt = $crate::peripherals::Interrupt::[< SPI $num _DMA >];
- unsafe {
- crate::interrupt::bind_interrupt(interrupt, handler.handler());
- }
- crate::interrupt::enable(interrupt, handler.priority()).unwrap();
- }
}
impl PdmaChannel for [] {
@@ -361,8 +390,8 @@ macro_rules! ImplSpiChannel {
&WAKER
}
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool {
- peripheral.peripheral() == crate::system::Peripheral::[]
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
+ peripheral == DmaPeripheral::[]
}
}
@@ -382,59 +411,19 @@ macro_rules! ImplSpiChannel {
pub struct [] {}
impl [] {
- fn do_configure<'a, M: $crate::Mode>(
- self,
- burst_mode: bool,
- priority: DmaPriority,
- ) -> Channel<'a, [], M> {
- #[cfg(esp32)]
- {
- // (only) on ESP32 we need to configure DPORT for the SPI DMA channels
- let dport = unsafe { &*crate::peripherals::DPORT::PTR };
- dport
- .spi_dma_chan_sel()
- .modify(|_, w| unsafe { w.[< spi $num _dma_chan_sel>]().bits($num - 1) });
- }
-
- let tx_impl = SpiDmaTxChannelImpl([] {});
- tx_impl.set_burst_mode(burst_mode);
- tx_impl.set_priority(priority);
-
- let rx_impl = SpiDmaRxChannelImpl([] {});
- rx_impl.set_burst_mode(burst_mode);
- rx_impl.set_priority(priority);
-
- Channel {
- tx: ChannelTx::new(tx_impl, burst_mode),
- rx: ChannelRx::new(rx_impl, burst_mode),
- phantom: PhantomData,
- }
- }
-
/// Configure the channel for use with blocking APIs
- ///
- /// Descriptors should be sized as `(CHUNK_SIZE + 4091) / 4092`. I.e., to
- /// transfer buffers of size `1..=4092`, you need 1 descriptor.
pub fn configure<'a>(
self,
burst_mode: bool,
priority: DmaPriority,
- ) -> Channel<'a, [], $crate::Blocking> {
- Self::do_configure(self, burst_mode, priority)
- }
-
- /// Configure the channel for use with async APIs
- ///
- /// Descriptors should be sized as `(CHUNK_SIZE + 4091) / 4092`. I.e., to
- /// transfer buffers of size `1..=4092`, you need 1 descriptor.
- pub fn configure_for_async<'a>(
- self,
- burst_mode: bool,
- priority: DmaPriority,
- ) -> Channel<'a, [], $crate::Async> {
- let this = Self::do_configure(self, burst_mode, priority);
+ ) -> Channel<'a, [], Blocking> {
+ let mut this = Channel {
+ tx: ChannelTx::new(SpiDmaTxChannelImpl([] {})),
+ rx: ChannelRx::new(SpiDmaRxChannelImpl([] {})),
+ phantom: PhantomData,
+ };
- []::set_isr(super::asynch::interrupt::[< interrupt_handler_spi $num _dma >]);
+ this.configure(burst_mode, priority);
this
}
@@ -501,12 +490,26 @@ impl> RegisterAccess for I2sDma
.modify(|_, w| w.outlink_restart().set_bit());
}
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool {
+ fn set_check_owner(&self, check_owner: Option) {
+ let reg_block = self.0.register_block();
+ reg_block
+ .lc_conf()
+ .modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true)));
+ }
+
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
}
impl> TxRegisterAccess for I2sDmaTxChannelImpl {
+ fn set_auto_write_back(&self, enable: bool) {
+ let reg_block = self.0.register_block();
+ reg_block
+ .lc_conf()
+ .modify(|_, w| w.out_auto_wrback().bit(enable));
+ }
+
fn last_dscr_address(&self) -> usize {
let reg_block = self.0.register_block();
reg_block
@@ -532,7 +535,7 @@ impl> InterruptAccess EnumSet {
@@ -589,7 +592,7 @@ impl> InterruptAccess &'static AtomicWaker {
@@ -643,7 +646,14 @@ impl> RegisterAccess for I2sDma
.modify(|_, w| w.inlink_restart().set_bit());
}
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool {
+ fn set_check_owner(&self, check_owner: Option) {
+ let reg_block = self.0.register_block();
+ reg_block
+ .lc_conf()
+ .modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true)));
+ }
+
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
}
@@ -666,7 +676,7 @@ impl> InterruptAccess EnumSet {
@@ -730,7 +740,7 @@ impl> InterruptAccess &'static AtomicWaker {
@@ -746,26 +756,36 @@ macro_rules! ImplI2sChannel {
impl $crate::private::Sealed for [] {}
+ impl [] {
+ fn handler() -> InterruptHandler {
+ super::asynch::interrupt::[< interrupt_handler_i2s $num _dma >]
+ }
+
+ fn isrs() -> &'static [Interrupt] {
+ &[Interrupt::[< I2S $num >]]
+ }
+ }
+
impl DmaChannel for [] {
type Rx = I2sDmaRxChannelImpl;
type Tx = I2sDmaTxChannelImpl;
+
+ fn async_handler(_ch: &Channel<'_, Self, M>) -> InterruptHandler {
+ Self::handler()
+ }
+
+ fn interrupts(_ch: &Channel<'_, Self, M>) -> &'static [Interrupt] {
+ Self::isrs()
+ }
}
impl DmaChannelExt for [] {
- fn get_rx_interrupts() -> impl InterruptAccess {
+ fn rx_interrupts() -> impl InterruptAccess {
I2sDmaRxChannelImpl(Self {})
}
- fn get_tx_interrupts() -> impl InterruptAccess {
+ fn tx_interrupts() -> impl InterruptAccess {
I2sDmaTxChannelImpl(Self {})
}
-
- fn set_isr(handler: InterruptHandler) {
- let interrupt = $crate::peripherals::Interrupt::[< I2S $num >];
- unsafe {
- crate::interrupt::bind_interrupt(interrupt, handler.handler());
- }
- crate::interrupt::enable(interrupt, handler.priority()).unwrap();
- }
}
impl PdmaChannel for [] {
@@ -782,8 +802,8 @@ macro_rules! ImplI2sChannel {
static WAKER: embassy_sync::waitqueue::AtomicWaker = embassy_sync::waitqueue::AtomicWaker::new();
&WAKER
}
- fn is_compatible_with(&self, peripheral: &impl PeripheralMarker) -> bool {
- peripheral.peripheral() == crate::system::Peripheral::[]
+ fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
+ peripheral == DmaPeripheral::[