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 95d69b3ddc4..3449b640ed2 100644
--- a/esp-hal-embassy/CHANGELOG.md
+++ b/esp-hal-embassy/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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/build.rs b/esp-hal-embassy/build.rs
index ff1606cbeef..5a4cf918864 100644
--- a/esp-hal-embassy/build.rs
+++ b/esp-hal-embassy/build.rs
@@ -43,8 +43,9 @@ fn main() -> Result<(), Box> {
"esp_hal_embassy",
&[(
"low-power-wait",
- Value::Bool(true),
"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,
);
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 b279c6d417b..3539a36d204 100644
--- a/esp-hal-embassy/src/executor/thread.rs
+++ b/esp-hal-embassy/src/executor/thread.rs
@@ -3,7 +3,7 @@
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)]
@@ -35,7 +35,7 @@ pub(crate) fn pend_thread_mode(_core: usize) {
// 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 {
+ 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.
@@ -74,7 +74,7 @@ This will use software-interrupt 3 which isn't available for anything else to wa
}
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,
}
}
@@ -103,7 +103,7 @@ This will use software-interrupt 3 which isn't available for anything else to wa
init(self.inner.spawner());
#[cfg(low_power_wait)]
- let cpu = get_core() as usize;
+ let cpu = Cpu::current() as usize;
loop {
unsafe { self.inner.poll() };
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 06e1acda0d8..8fcc49df2a7 100644
--- a/esp-hal/CHANGELOG.md
+++ b/esp-hal/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+
- A new config option `PLACE_SWITCH_TABLES_IN_RAM` to improve performance (especially for interrupts) at the cost of slightly more RAM usage (#2331)
- A new config option `PLACE_ANON_IN_RAM` to improve performance (especially for interrupts) at the cost of RAM usage (#2331)
- Add burst transfer support to DMA buffers (#2336)
@@ -24,8 +25,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `DmaLoopBuf` (#2415)
- `Cpu::COUNT` and `Cpu::current()` (#2411)
- `UartInterrupt` and related functions (#2406)
-- I2S Parallel output driver for esp32. (#2348)
+- 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
@@ -38,18 +54,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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)
+- The `Camera` and `I8080` drivers' constructors now only accepts blocking-mode DMA channels. (#2519)
### 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)
@@ -62,12 +99,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]
@@ -132,6 +183,8 @@ 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)
+- `dma::{ChannelRx, ChannelTx}` now have a `Mode` type parameter (#2519)
### Fixed
@@ -156,6 +209,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
@@ -176,6 +230,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 7898ecabc5b..c6cb5fa5f0e 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. `new` and `new_typed` now take a `Config` struct
+with the available configuration options.
-The `with_timeout` constructors have been removed in favour of `set_timeout` or `with_timeout`.
+- 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,8 +242,43 @@ 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);
-```Ë›
-## Circular DMA transfer's `available` returns `Result` now
+```
+
+## 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>) {
+ // ...
+ }
+```
+
+## DMA related changes
+
+### Circular DMA transfer's `available` returns `Result` now
In case of any error you should drop the transfer and restart it.
@@ -176,3 +294,178 @@ In case of any error you should drop the transfer and restart it.
+ },
+ };
```
+
+### Channel, ChannelRx and ChannelTx types have changed
+
+- `Channel`'s `Async`/`Blocking` mode has been moved before the channel instance parameter.
+- `ChannelRx` and `ChannelTx` have gained a new `Async`/`Blocking` mode parameter.
+
+```diff
+-Channel<'d, DmaChannel0, Async>
++Channel<'d, Async, DmaChannel0>
+
+-ChannelRx<'d, DmaChannel0>
++ChannelRx<'d, Async, DmaChannel0>
+
+-ChannelTx<'d, DmaChannel0>
++ChannelTx<'d, Async, DmaChannel0>
+```
+
+## 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()
++ },
++)
+```
+
+## LCD_CAM changes
+
+### 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.
+
+### Mixed mode constructors
+
+It is no longer possible to construct `I8080` or `Camera` with an async-mode DMA channel.
+Convert the DMA channel into blocking before passing it to these constructors.
+
+```diff
+ let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
+ let channel = ctx
+ .dma
+ .channel0
+- .configure(false, DmaPriority::Priority0)
+- .into_async();
++ .configure(false, DmaPriority::Priority0);
+
+ let i8080 = I8080::new(
+ lcd_cam.lcd,
+ channel.tx,
+ pins,
+ 20.MHz(),
+ Config::default(),
+ );
+```
+
+## `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..34a14f03ce9 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,22 +276,21 @@ pub mod dma {
/// The underlying [`Aes`](super::Aes) driver
pub aes: super::Aes<'d>,
- channel: Channel<'d, ::Dma, crate::Blocking>,
+ channel: Channel<'d, Blocking, ::Dma>,
rx_chain: DescriptorChain,
tx_chain: DescriptorChain,
}
impl<'d> crate::aes::Aes<'d> {
/// Enable DMA for the current instance of the AES driver
- pub fn with_dma(
+ pub fn with_dma(
self,
- channel: Channel<'d, C, crate::Blocking>,
+ channel: Channel<'d, Blocking, CH>,
rx_descriptors: &'static mut [DmaDescriptor],
tx_descriptors: &'static mut [DmaDescriptor],
) -> AesDma<'d>
where
- Self: Sized,
- C: DmaChannelConvert<::Dma>,
+ CH: DmaChannelConvert<::Dma>,
{
AesDma {
aes: self,
@@ -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()
@@ -324,7 +324,7 @@ pub mod dma {
}
impl<'d> DmaSupportTx for AesDma<'d> {
- type TX = ChannelTx<'d, ::Dma>;
+ type TX = ChannelTx<'d, Blocking, ::Dma>;
fn tx(&mut self) -> &mut Self::TX {
&mut self.channel.tx
@@ -336,7 +336,7 @@ pub mod dma {
}
impl<'d> DmaSupportRx for AesDma<'d> {
- type RX = ChannelRx<'d, ::Dma>;
+ type RX = ChannelRx<'d, Blocking, ::Dma>;
fn rx(&mut self) -> &mut Self::RX {
&mut self.channel.rx
@@ -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 48aa156db96..9576572fcd2 100644
--- a/esp-hal/src/dma/buffers.rs
+++ b/esp-hal/src/dma/buffers.rs
@@ -10,11 +10,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.
@@ -25,8 +26,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
@@ -306,6 +334,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,
}
}
@@ -456,6 +485,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,
}
}
@@ -583,6 +613,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,
}
}
@@ -614,6 +645,7 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf {
// DmaRxTxBuf doesn't currently enforce the alignment requirements required for
// bursting.
is_burstable: false,
+ check_owner: None,
}
}
@@ -754,6 +786,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,
}
}
@@ -961,6 +999,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),
}
}
@@ -988,6 +1030,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 11c0b624884..8c2ca9a2468 100644
--- a/esp-hal/src/dma/gdma.rs
+++ b/esp-hal/src/dma/gdma.rs
@@ -17,12 +17,74 @@
use crate::{
dma::*,
peripheral::PeripheralRef,
+ peripherals::Interrupt,
system::{Peripheral, PeripheralClockControl},
+ Blocking,
};
#[doc(hidden)]
pub trait GdmaChannel {
fn number(&self) -> u8;
+
+ fn async_handler_out(&self) -> Option {
+ match self.number() {
+ 0 => DmaChannel0::handler_out(),
+ #[cfg(not(esp32c2))]
+ 1 => DmaChannel1::handler_out(),
+ #[cfg(not(esp32c2))]
+ 2 => DmaChannel2::handler_out(),
+ #[cfg(esp32s3)]
+ 3 => DmaChannel3::handler_out(),
+ #[cfg(esp32s3)]
+ 4 => DmaChannel4::handler_out(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn peripheral_interrupt_out(&self) -> Option {
+ match self.number() {
+ 0 => DmaChannel0::isr_out(),
+ #[cfg(not(esp32c2))]
+ 1 => DmaChannel1::isr_out(),
+ #[cfg(not(esp32c2))]
+ 2 => DmaChannel2::isr_out(),
+ #[cfg(esp32s3)]
+ 3 => DmaChannel3::isr_out(),
+ #[cfg(esp32s3)]
+ 4 => DmaChannel4::isr_out(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn async_handler_in(&self) -> Option {
+ match self.number() {
+ 0 => DmaChannel0::handler_in(),
+ #[cfg(not(esp32c2))]
+ 1 => DmaChannel1::handler_in(),
+ #[cfg(not(esp32c2))]
+ 2 => DmaChannel2::handler_in(),
+ #[cfg(esp32s3)]
+ 3 => DmaChannel3::handler_in(),
+ #[cfg(esp32s3)]
+ 4 => DmaChannel4::handler_in(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn peripheral_interrupt_in(&self) -> Option {
+ match self.number() {
+ 0 => DmaChannel0::isr_in(),
+ #[cfg(not(esp32c2))]
+ 1 => DmaChannel1::isr_in(),
+ #[cfg(not(esp32c2))]
+ 2 => DmaChannel2::isr_in(),
+ #[cfg(esp32s3)]
+ 3 => DmaChannel3::isr_in(),
+ #[cfg(esp32s3)]
+ 4 => DmaChannel4::isr_in(),
+ _ => unreachable!(),
+ }
+ }
}
/// An arbitrary GDMA channel
@@ -59,6 +121,14 @@ use embassy_sync::waitqueue::AtomicWaker;
static TX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
static RX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
+cfg_if::cfg_if! {
+ if #[cfg(any(esp32c2, esp32c3))] {
+ use portable_atomic::AtomicBool;
+ static TX_IS_ASYNC: [AtomicBool; CHANNEL_COUNT] = [const { AtomicBool::new(false) }; CHANNEL_COUNT];
+ static RX_IS_ASYNC: [AtomicBool; CHANNEL_COUNT] = [const { AtomicBool::new(false) }; CHANNEL_COUNT];
+ }
+}
+
impl crate::private::Sealed for ChannelTxImpl {}
impl ChannelTxImpl {
@@ -142,6 +212,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()
@@ -164,6 +240,14 @@ impl TxRegisterAccess for ChannelTxImpl {
.out_eof_des_addr()
.bits() as _
}
+
+ fn async_handler(&self) -> Option {
+ self.0.async_handler_out()
+ }
+
+ fn peripheral_interrupt(&self) -> Option {
+ self.0.peripheral_interrupt_out()
+ }
}
impl InterruptAccess for ChannelTxImpl {
@@ -178,7 +262,7 @@ impl InterruptAccess for ChannelTxImpl {
};
}
w
- })
+ });
}
fn is_listening(&self) -> EnumSet {
@@ -212,7 +296,7 @@ impl InterruptAccess for ChannelTxImpl {
};
}
w
- })
+ });
}
fn pending_interrupts(&self) -> EnumSet {
@@ -238,6 +322,24 @@ impl InterruptAccess for ChannelTxImpl {
fn waker(&self) -> &'static AtomicWaker {
&TX_WAKERS[self.0.number() as usize]
}
+
+ fn is_async(&self) -> bool {
+ cfg_if::cfg_if! {
+ if #[cfg(any(esp32c2, esp32c3))] {
+ TX_IS_ASYNC[self.0.number() as usize].load(portable_atomic::Ordering::Acquire)
+ } else {
+ true
+ }
+ }
+ }
+
+ fn set_async(&self, _is_async: bool) {
+ cfg_if::cfg_if! {
+ if #[cfg(any(esp32c2, esp32c3))] {
+ TX_IS_ASYNC[self.0.number() as usize].store(_is_async, portable_atomic::Ordering::Release);
+ }
+ }
+ }
}
#[non_exhaustive]
@@ -327,6 +429,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()
@@ -341,6 +449,14 @@ impl RxRegisterAccess for ChannelRxImpl {
.in_conf0()
.modify(|_, w| w.mem_trans_en().bit(value));
}
+
+ fn async_handler(&self) -> Option {
+ self.0.async_handler_in()
+ }
+
+ fn peripheral_interrupt(&self) -> Option {
+ self.0.peripheral_interrupt_in()
+ }
}
impl InterruptAccess for ChannelRxImpl {
@@ -394,7 +510,7 @@ impl InterruptAccess for ChannelRxImpl {
};
}
w
- })
+ });
}
fn pending_interrupts(&self) -> EnumSet {
@@ -423,24 +539,39 @@ impl InterruptAccess for ChannelRxImpl {
fn waker(&self) -> &'static AtomicWaker {
&RX_WAKERS[self.0.number() as usize]
}
+
+ fn is_async(&self) -> bool {
+ cfg_if::cfg_if! {
+ if #[cfg(any(esp32c2, esp32c3))] {
+ RX_IS_ASYNC[self.0.number() as usize].load(portable_atomic::Ordering::Acquire)
+ } else {
+ true
+ }
+ }
+ }
+
+ fn set_async(&self, _is_async: bool) {
+ cfg_if::cfg_if! {
+ if #[cfg(any(esp32c2, esp32c3))] {
+ RX_IS_ASYNC[self.0.number() as usize].store(_is_async, portable_atomic::Ordering::Release);
+ }
+ }
+ }
}
/// A Channel can be created from this
#[non_exhaustive]
pub struct ChannelCreator {}
-impl Channel<'_, CH, M> {
+impl Channel<'_, M, CH> {
/// 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
}
}
macro_rules! impl_channel {
- ($num: literal, $async_handler: path, $($interrupt: ident),* ) => {
+ ($num:literal, $interrupt_in:ident, $async_handler:path $(, $interrupt_out:ident , $async_handler_out:path)? ) => {
paste::paste! {
/// A description of a specific GDMA channel
#[non_exhaustive]
@@ -448,6 +579,24 @@ macro_rules! impl_channel {
impl crate::private::Sealed for [] {}
+ impl [] {
+ fn handler_in() -> Option {
+ Some($async_handler)
+ }
+
+ fn isr_in() -> Option {
+ Some(Interrupt::$interrupt_in)
+ }
+
+ fn handler_out() -> Option {
+ $crate::if_set! { $(Some($async_handler_out))?, None }
+ }
+
+ fn isr_out() -> Option {
+ $crate::if_set! { $(Some(Interrupt::$interrupt_out))?, None }
+ }
+ }
+
impl DmaChannel for [] {
type Rx = ChannelRxImpl>;
type Tx = ChannelTxImpl>;
@@ -463,71 +612,28 @@ 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)
- }
+ ) -> Channel<'a, Blocking, []> {
+ let mut this = Channel {
+ tx: ChannelTx::new(ChannelTxImpl(SpecificGdmaChannel::<$num> {})),
+ rx: ChannelRx::new(ChannelRxImpl(SpecificGdmaChannel::<$num> {})),
+ };
- /// 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);
-
- []::set_isr($async_handler);
+ this.configure(burst_mode, priority);
this
}
@@ -536,27 +642,29 @@ macro_rules! impl_channel {
};
}
+use super::asynch::interrupt as asynch_handler;
+
cfg_if::cfg_if! {
if #[cfg(esp32c2)] {
const CHANNEL_COUNT: usize = 1;
- impl_channel!(0, super::asynch::interrupt::interrupt_handler_ch0, DMA_CH0);
+ impl_channel!(0, DMA_CH0, asynch_handler::interrupt_handler_ch0);
} else if #[cfg(esp32c3)] {
const CHANNEL_COUNT: usize = 3;
- impl_channel!(0, super::asynch::interrupt::interrupt_handler_ch0, DMA_CH0);
- impl_channel!(1, super::asynch::interrupt::interrupt_handler_ch1, DMA_CH1);
- impl_channel!(2, super::asynch::interrupt::interrupt_handler_ch2, DMA_CH2);
+ impl_channel!(0, DMA_CH0, asynch_handler::interrupt_handler_ch0);
+ impl_channel!(1, DMA_CH1, asynch_handler::interrupt_handler_ch1);
+ impl_channel!(2, DMA_CH2, asynch_handler::interrupt_handler_ch2);
} else if #[cfg(any(esp32c6, esp32h2))] {
const CHANNEL_COUNT: usize = 3;
- impl_channel!(0, super::asynch::interrupt::interrupt_handler_ch0, DMA_IN_CH0, DMA_OUT_CH0);
- impl_channel!(1, super::asynch::interrupt::interrupt_handler_ch1, DMA_IN_CH1, DMA_OUT_CH1);
- impl_channel!(2, super::asynch::interrupt::interrupt_handler_ch2, DMA_IN_CH2, DMA_OUT_CH2);
+ impl_channel!(0, DMA_IN_CH0, asynch_handler::interrupt_handler_in_ch0, DMA_OUT_CH0, asynch_handler::interrupt_handler_out_ch0);
+ impl_channel!(1, DMA_IN_CH1, asynch_handler::interrupt_handler_in_ch1, DMA_OUT_CH1, asynch_handler::interrupt_handler_out_ch1);
+ impl_channel!(2, DMA_IN_CH2, asynch_handler::interrupt_handler_in_ch2, DMA_OUT_CH2, asynch_handler::interrupt_handler_out_ch2);
} else if #[cfg(esp32s3)] {
const CHANNEL_COUNT: usize = 5;
- impl_channel!(0, super::asynch::interrupt::interrupt_handler_ch0, DMA_IN_CH0, DMA_OUT_CH0);
- impl_channel!(1, super::asynch::interrupt::interrupt_handler_ch1, DMA_IN_CH1, DMA_OUT_CH1);
- impl_channel!(2, super::asynch::interrupt::interrupt_handler_ch2, DMA_IN_CH2, DMA_OUT_CH2);
- impl_channel!(3, super::asynch::interrupt::interrupt_handler_ch3, DMA_IN_CH3, DMA_OUT_CH3);
- impl_channel!(4, super::asynch::interrupt::interrupt_handler_ch4, DMA_IN_CH4, DMA_OUT_CH4);
+ impl_channel!(0, DMA_IN_CH0, asynch_handler::interrupt_handler_in_ch0, DMA_OUT_CH0, asynch_handler::interrupt_handler_out_ch0);
+ impl_channel!(1, DMA_IN_CH1, asynch_handler::interrupt_handler_in_ch1, DMA_OUT_CH1, asynch_handler::interrupt_handler_out_ch1);
+ impl_channel!(2, DMA_IN_CH2, asynch_handler::interrupt_handler_in_ch2, DMA_OUT_CH2, asynch_handler::interrupt_handler_out_ch2);
+ impl_channel!(3, DMA_IN_CH3, asynch_handler::interrupt_handler_in_ch3, DMA_OUT_CH3, asynch_handler::interrupt_handler_out_ch3);
+ impl_channel!(4, DMA_IN_CH4, asynch_handler::interrupt_handler_in_ch4, DMA_OUT_CH4, asynch_handler::interrupt_handler_out_ch4);
}
}
diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs
index 0e0aee187d1..0b2009950eb 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,
};
@@ -30,25 +32,24 @@ pub struct Mem2Mem<'d, M>
where
M: Mode,
{
- channel: Channel<'d, AnyGdmaChannel, M>,
+ channel: Channel<'d, M, AnyGdmaChannel>,
rx_chain: DescriptorChain,
tx_chain: DescriptorChain,
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, DM, CH>,
peripheral: impl DmaEligible,
rx_descriptors: &'static mut [DmaDescriptor],
tx_descriptors: &'static mut [DmaDescriptor],
) -> Result
where
CH: DmaChannelConvert,
+ DM: Mode,
+ Channel<'d, Blocking, CH>: 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, DM, CH>,
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, Blocking, CH>: 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, DM, CH>,
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, Blocking, CH>: 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::::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,
{
@@ -170,11 +190,11 @@ where
}
}
-impl<'d, MODE> DmaSupportRx for Mem2Mem<'d, MODE>
+impl<'d, M> DmaSupportRx for Mem2Mem<'d, M>
where
- MODE: Mode,
+ M: Mode,
{
- type RX = ChannelRx<'d, AnyGdmaChannel>;
+ type RX = ChannelRx<'d, M, AnyGdmaChannel>;
fn rx(&mut self) -> &mut Self::RX {
&mut self.channel.rx
diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs
index 275939b89ca..87110231901 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)]
@@ -957,12 +971,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 {
@@ -1581,7 +1589,7 @@ 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;
@@ -1591,13 +1599,15 @@ pub trait DmaChannel: crate::private::Sealed {
#[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;
@@ -1674,30 +1684,95 @@ pub trait Rx: crate::private::Sealed {
// DMA receive channel
#[non_exhaustive]
#[doc(hidden)]
-pub struct ChannelRx<'a, CH>
+pub struct ChannelRx<'a, M, CH>
where
CH: DmaChannel,
{
pub(crate) burst_mode: bool,
pub(crate) rx_impl: CH::Rx,
- pub(crate) _phantom: PhantomData<(&'a (), CH)>,
+ pub(crate) _phantom: PhantomData<(&'a (), CH, M)>,
}
-impl<'a, CH> ChannelRx<'a, CH>
+impl<'a, CH> ChannelRx<'a, Blocking, 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);
+
+ if let Some(interrupt) = rx_impl.peripheral_interrupt() {
+ for cpu in Cpu::all() {
+ crate::interrupt::disable(cpu, interrupt);
+ }
+ }
+ rx_impl.set_async(false);
+
Self {
- burst_mode,
+ burst_mode: false,
rx_impl,
_phantom: PhantomData,
}
}
+ /// Converts a blocking channel to an async channel.
+ pub(crate) fn into_async(mut self) -> ChannelRx<'a, Async, CH> {
+ if let Some(handler) = self.rx_impl.async_handler() {
+ self.set_interrupt_handler(handler);
+ }
+ self.rx_impl.set_async(true);
+ ChannelRx {
+ burst_mode: self.burst_mode,
+ rx_impl: self.rx_impl,
+ _phantom: PhantomData,
+ }
+ }
+
+ fn set_interrupt_handler(&mut self, handler: InterruptHandler)
+ where
+ CH: DmaChannel,
+ {
+ self.unlisten_in(EnumSet::all());
+ self.clear_in(EnumSet::all());
+
+ if let Some(interrupt) = self.rx_impl.peripheral_interrupt() {
+ 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()));
+ }
+ }
+}
+
+impl<'a, CH> ChannelRx<'a, Async, CH>
+where
+ CH: DmaChannel,
+{
+ /// Converts an async channel into a blocking channel.
+ pub(crate) fn into_blocking(self) -> ChannelRx<'a, Blocking, CH> {
+ if let Some(interrupt) = self.rx_impl.peripheral_interrupt() {
+ crate::interrupt::disable(Cpu::current(), interrupt);
+ }
+ self.rx_impl.set_async(false);
+ ChannelRx {
+ burst_mode: self.burst_mode,
+ rx_impl: self.rx_impl,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<'a, M, CH> ChannelRx<'a, M, CH>
+where
+ M: Mode,
+ CH: DmaChannel,
+{
/// Return a less specific (degraded) version of this channel.
#[doc(hidden)]
- pub fn degrade(self) -> ChannelRx<'a, DEG>
+ pub fn degrade(self) -> ChannelRx<'a, M, DEG>
where
CH: DmaChannelConvert,
{
@@ -1707,12 +1782,24 @@ 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<'_, M, CH>
+where
+ M: Mode,
+ CH: DmaChannel,
+{
+}
-impl<'a, CH> Rx for ChannelRx<'a, CH>
+impl Rx for ChannelRx<'_, M, CH>
where
+ M: Mode,
CH: DmaChannel,
{
unsafe fn prepare_transfer_without_start(
@@ -1767,6 +1854,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();
@@ -1889,31 +1978,91 @@ pub trait Tx: crate::private::Sealed {
/// DMA transmit channel
#[doc(hidden)]
-pub struct ChannelTx<'a, CH>
+pub struct ChannelTx<'a, M, CH>
where
CH: DmaChannel,
{
#[allow(unused)]
pub(crate) burst_mode: bool,
pub(crate) tx_impl: CH::Tx,
- pub(crate) _phantom: PhantomData<(&'a (), CH)>,
+ pub(crate) _phantom: PhantomData<(&'a (), CH, M)>,
}
-impl<'a, CH> ChannelTx<'a, CH>
+impl<'a, CH> ChannelTx<'a, Blocking, CH>
where
CH: DmaChannel,
{
- fn new(tx_impl: CH::Tx, burst_mode: bool) -> Self {
+ fn new(tx_impl: CH::Tx) -> Self {
+ if let Some(interrupt) = tx_impl.peripheral_interrupt() {
+ for cpu in Cpu::all() {
+ crate::interrupt::disable(cpu, interrupt);
+ }
+ }
+ tx_impl.set_async(false);
+
Self {
- burst_mode,
+ burst_mode: false,
tx_impl,
_phantom: PhantomData,
}
}
+ /// Converts a blocking channel to an async channel.
+ pub(crate) fn into_async(mut self) -> ChannelTx<'a, Async, CH> {
+ if let Some(handler) = self.tx_impl.async_handler() {
+ self.set_interrupt_handler(handler);
+ }
+ self.tx_impl.set_async(true);
+ ChannelTx {
+ burst_mode: self.burst_mode,
+ tx_impl: self.tx_impl,
+ _phantom: PhantomData,
+ }
+ }
+
+ fn set_interrupt_handler(&mut self, handler: InterruptHandler)
+ where
+ CH: DmaChannel,
+ {
+ self.unlisten_out(EnumSet::all());
+ self.clear_out(EnumSet::all());
+
+ if let Some(interrupt) = self.tx_impl.peripheral_interrupt() {
+ 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()));
+ }
+ }
+}
+
+impl<'a, CH> ChannelTx<'a, Async, CH>
+where
+ CH: DmaChannel,
+{
+ /// Converts an async channel into a blocking channel.
+ pub(crate) fn into_blocking(self) -> ChannelTx<'a, Blocking, CH> {
+ if let Some(interrupt) = self.tx_impl.peripheral_interrupt() {
+ crate::interrupt::disable(Cpu::current(), interrupt);
+ }
+ self.tx_impl.set_async(false);
+ ChannelTx {
+ burst_mode: self.burst_mode,
+ tx_impl: self.tx_impl,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<'a, M, CH> ChannelTx<'a, M, CH>
+where
+ M: Mode,
+ CH: DmaChannel,
+{
/// Return a less specific (degraded) version of this channel.
#[doc(hidden)]
- pub fn degrade(self) -> ChannelTx<'a, DEG>
+ pub fn degrade(self) -> ChannelTx<'a, M, DEG>
where
CH: DmaChannelConvert,
{
@@ -1923,12 +2072,24 @@ 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<'_, M, CH>
+where
+ M: Mode,
+ CH: DmaChannel,
+{
+}
-impl<'a, CH> Tx for ChannelTx<'a, CH>
+impl Tx for ChannelTx<'_, M, CH>
where
+ M: Mode,
CH: DmaChannel,
{
unsafe fn prepare_transfer_without_start(
@@ -1989,6 +2150,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();
@@ -2082,17 +2245,30 @@ 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)]
pub trait RxRegisterAccess: RegisterAccess {
#[cfg(gdma)]
fn set_mem2mem_mode(&self, value: bool);
+
+ fn peripheral_interrupt(&self) -> Option;
+ fn async_handler(&self) -> Option;
}
#[doc(hidden)]
@@ -2102,6 +2278,9 @@ pub trait TxRegisterAccess: RegisterAccess {
/// Outlink descriptor address when EOF occurs of Tx channel.
fn last_dscr_address(&self) -> usize;
+
+ fn peripheral_interrupt(&self) -> Option;
+ fn async_handler(&self) -> Option;
}
#[doc(hidden)]
@@ -2122,39 +2301,41 @@ pub trait InterruptAccess: crate::private::Sealed {
fn clear(&self, interrupts: impl Into>);
fn pending_interrupts(&self) -> EnumSet;
fn waker(&self) -> &'static embassy_sync::waitqueue::AtomicWaker;
+
+ fn is_async(&self) -> bool;
+ fn set_async(&self, is_async: bool);
}
/// DMA Channel
-pub struct Channel<'d, CH, MODE>
+pub struct Channel<'d, M, CH>
where
+ M: Mode,
CH: DmaChannel,
- MODE: Mode,
{
/// RX half of the channel
- pub rx: ChannelRx<'d, CH>,
+ pub rx: ChannelRx<'d, M, CH>,
/// TX half of the channel
- pub tx: ChannelTx<'d, CH>,
- phantom: PhantomData,
+ pub tx: ChannelTx<'d, M, CH>,
}
-impl<'d, C> Channel<'d, C, crate::Blocking>
+impl<'d, CH> Channel<'d, Blocking, CH>
where
- C: DmaChannel,
+ CH: 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,
+ CH: DmaChannel,
{
- C::set_isr(handler);
+ self.rx.set_interrupt_handler(handler);
+ self.tx.set_interrupt_handler(handler);
}
/// 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),
@@ -2163,8 +2344,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),
@@ -2185,31 +2366,70 @@ 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.rx.configure(burst_mode, priority);
+ self.tx.configure(burst_mode, priority);
+ }
+
+ /// Converts a blocking channel to an async channel.
+ pub fn into_async(self) -> Channel<'d, Async, CH> {
+ Channel {
+ rx: self.rx.into_async(),
+ tx: self.tx.into_async(),
+ }
+ }
+}
+
+impl<'d, CH> Channel<'d, Async, CH>
+where
+ CH: DmaChannel,
+{
+ /// Converts an async channel to a blocking channel.
+ pub fn into_blocking(self) -> Channel<'d, Blocking, CH> {
+ Channel {
+ rx: self.rx.into_blocking(),
+ tx: self.tx.into_blocking(),
+ }
+ }
+}
+
+impl<'d, CH: DmaChannel> From> for Channel<'d, Async, CH> {
+ fn from(channel: Channel<'d, Blocking, CH>) -> Self {
+ channel.into_async()
+ }
+}
+
+impl<'d, CH: DmaChannel> From> for Channel<'d, Blocking, CH> {
+ fn from(channel: Channel<'d, Async, CH>) -> Self {
+ channel.into_blocking()
+ }
}
-impl<'d, CH, M: Mode> Channel<'d, CH, M>
+impl<'d, M, CH> Channel<'d, M, CH>
where
+ M: Mode,
CH: DmaChannel,
{
/// Return a less specific (degraded) version of this channel (both rx and
/// tx).
#[doc(hidden)]
- pub fn degrade(self) -> Channel<'d, DEG, M>
+ pub fn degrade(self) -> Channel<'d, M, DEG>
where
CH: DmaChannelConvert,
{
Channel {
rx: self.rx.degrade(),
tx: self.tx.degrade(),
- phantom: PhantomData,
}
}
}
@@ -2296,7 +2516,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferTx<'a, I>
+impl Drop for DmaTransferTx<'_, I>
where
I: dma_private::DmaSupportTx,
{
@@ -2349,7 +2569,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferRx<'a, I>
+impl Drop for DmaTransferRx<'_, I>
where
I: dma_private::DmaSupportRx,
{
@@ -2408,7 +2628,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferRxTx<'a, I>
+impl Drop for DmaTransferRxTx<'_, I>
where
I: dma_private::DmaSupportTx + dma_private::DmaSupportRx,
{
@@ -2481,7 +2701,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferTxCircular<'a, I>
+impl Drop for DmaTransferTxCircular<'_, I>
where
I: dma_private::DmaSupportTx,
{
@@ -2538,7 +2758,7 @@ where
}
}
-impl<'a, I> Drop for DmaTransferRxCircular<'a, I>
+impl Drop for DmaTransferRxCircular<'_, I>
where
I: dma_private::DmaSupportRx,
{
@@ -2569,7 +2789,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,
{
@@ -2598,7 +2818,7 @@ pub(crate) mod asynch {
}
}
- impl<'a, TX> Drop for DmaTxFuture<'a, TX>
+ impl Drop for DmaTxFuture<'_, TX>
where
TX: Tx,
{
@@ -2625,7 +2845,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,
{
@@ -2658,7 +2878,7 @@ pub(crate) mod asynch {
}
}
- impl<'a, RX> Drop for DmaRxFuture<'a, RX>
+ impl Drop for DmaRxFuture<'_, RX>
where
RX: Rx,
{
@@ -2691,7 +2911,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,
{
@@ -2725,7 +2945,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,
{
@@ -2755,7 +2975,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,
{
@@ -2793,7 +3013,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,
{
@@ -2807,9 +3027,12 @@ pub(crate) mod asynch {
}
}
- fn handle_interrupt() {
- let rx = CH::get_rx_interrupts();
- let tx = CH::get_tx_interrupts();
+ fn handle_in_interrupt() {
+ let rx = CH::rx_interrupts();
+
+ if !rx.is_async() {
+ return;
+ }
if rx.pending_interrupts().is_disjoint(
DmaRxInterrupt::DescriptorError
@@ -2826,16 +3049,6 @@ pub(crate) mod asynch {
rx.waker().wake()
}
- if tx
- .pending_interrupts()
- .contains(DmaTxInterrupt::DescriptorError)
- {
- tx.unlisten(
- DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof | DmaTxInterrupt::Done,
- );
- tx.waker().wake()
- }
-
if rx
.pending_interrupts()
.contains(DmaRxInterrupt::SuccessfulEof)
@@ -2848,6 +3061,24 @@ pub(crate) mod asynch {
rx.unlisten(DmaRxInterrupt::Done);
rx.waker().wake()
}
+ }
+
+ fn handle_out_interrupt() {
+ let tx = CH::tx_interrupts();
+
+ if !tx.is_async() {
+ return;
+ }
+
+ if tx
+ .pending_interrupts()
+ .contains(DmaTxInterrupt::DescriptorError)
+ {
+ tx.unlisten(
+ DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof | DmaTxInterrupt::Done,
+ );
+ tx.waker().wake()
+ }
if tx.pending_interrupts().contains(DmaTxInterrupt::TotalEof)
&& tx.is_listening().contains(DmaTxInterrupt::TotalEof)
@@ -2862,69 +3093,78 @@ pub(crate) mod asynch {
}
}
- #[cfg(not(any(esp32, esp32s2)))]
+ #[cfg(gdma)]
pub(crate) mod interrupt {
- use procmacros::handler;
-
use super::*;
-
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_ch0() {
- handle_interrupt::();
+ use crate::{interrupt::Priority, macros::handler};
+
+ // Single interrupt handler for IN and OUT
+ #[cfg(any(esp32c2, esp32c3))]
+ macro_rules! interrupt_handler {
+ ($ch:literal) => {
+ paste::paste! {
+ #[handler(priority = Priority::max())]
+ pub(crate) fn []() {
+ handle_in_interrupt::<[< DmaChannel $ch >]>();
+ handle_out_interrupt::<[< DmaChannel $ch >]>();
+ }
+ }
+ };
}
- #[cfg(not(esp32c2))]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_ch1() {
- handle_interrupt::();
- }
+ #[cfg(not(any(esp32c2, esp32c3)))]
+ macro_rules! interrupt_handler {
+ ($ch:literal) => {
+ paste::paste! {
+ #[handler(priority = Priority::max())]
+ pub(crate) fn []() {
+ handle_in_interrupt::<[< DmaChannel $ch >]>();
+ }
- #[cfg(not(esp32c2))]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_ch2() {
- handle_interrupt::();
+ #[handler(priority = Priority::max())]
+ pub(crate) fn []() {
+ handle_out_interrupt::<[< DmaChannel $ch >]>();
+ }
+ }
+ };
}
+ interrupt_handler!(0);
+ #[cfg(not(esp32c2))]
+ interrupt_handler!(1);
+ #[cfg(not(esp32c2))]
+ interrupt_handler!(2);
#[cfg(esp32s3)]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_ch3() {
- handle_interrupt::();
- }
-
+ interrupt_handler!(3);
#[cfg(esp32s3)]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_ch4() {
- handle_interrupt::();
- }
+ interrupt_handler!(4);
}
- #[cfg(any(esp32, esp32s2))]
+ #[cfg(pdma)]
pub(crate) mod interrupt {
- use procmacros::handler;
-
use super::*;
-
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_spi2_dma() {
- handle_interrupt::();
+ use crate::{interrupt::Priority, macros::handler};
+
+ // Single interrupt handler for IN and OUT
+ macro_rules! interrupt_handler {
+ ($ch:ident) => {
+ paste::paste! {
+ #[handler(priority = Priority::max())]
+ pub(crate) fn []() {
+ handle_in_interrupt::<[< $ch DmaChannel >]>();
+ handle_out_interrupt::<[< $ch DmaChannel >]>();
+ }
+ }
+ };
}
+ interrupt_handler!(Spi2);
#[cfg(spi3)]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_spi3_dma() {
- handle_interrupt::();
- }
+ interrupt_handler!(Spi3);
#[cfg(i2s0)]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_i2s0() {
- handle_interrupt::();
- }
-
+ interrupt_handler!(I2s0);
#[cfg(i2s1)]
- #[handler(priority = crate::interrupt::Priority::max())]
- pub(crate) fn interrupt_handler_i2s1() {
- handle_interrupt::();
- }
+ interrupt_handler!(I2s1);
}
}
diff --git a/esp-hal/src/dma/pdma.rs b/esp-hal/src/dma/pdma.rs
index ce037bb145b..609c89f24e4 100644
--- a/esp-hal/src/dma/pdma.rs
+++ b/esp-hal/src/dma/pdma.rs
@@ -12,11 +12,14 @@
//! [I2S]: ../i2s/index.html
use embassy_sync::waitqueue::AtomicWaker;
+use portable_atomic::{AtomicBool, Ordering};
use crate::{
dma::*,
peripheral::PeripheralRef,
+ peripherals::Interrupt,
system::{Peripheral, PeripheralClockControl},
+ Blocking,
};
type SpiRegisterBlock = crate::peripherals::spi2::RegisterBlock;
@@ -29,7 +32,12 @@ 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;
+
+ fn peripheral_interrupt(&self) -> Interrupt;
+ fn async_handler(&self) -> InterruptHandler;
+ fn rx_async_flag(&self) -> &'static AtomicBool;
+ fn tx_async_flag(&self) -> &'static AtomicBool;
}
#[doc(hidden)]
@@ -84,7 +92,13 @@ 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)
}
}
@@ -99,6 +113,14 @@ impl> TxRegisterAccess for SpiD
let spi = self.0.register_block();
spi.out_eof_des_addr().read().dma_out_eof_des_addr().bits() as usize
}
+
+ fn peripheral_interrupt(&self) -> Option {
+ None
+ }
+
+ fn async_handler(&self) -> Option {
+ None
+ }
}
impl> InterruptAccess
@@ -116,7 +138,7 @@ impl> InterruptAccess EnumSet {
@@ -152,7 +174,7 @@ impl> InterruptAccess EnumSet {
@@ -179,6 +201,14 @@ impl> InterruptAccess &'static AtomicWaker {
self.0.tx_waker()
}
+
+ fn is_async(&self) -> bool {
+ self.0.tx_async_flag().load(Ordering::Acquire)
+ }
+
+ fn set_async(&self, is_async: bool) {
+ self.0.tx_async_flag().store(is_async, Ordering::Release);
+ }
}
impl> RegisterAccess for SpiDmaRxChannelImpl {
@@ -222,12 +252,26 @@ 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)
}
}
-impl> RxRegisterAccess for SpiDmaRxChannelImpl {}
+impl> RxRegisterAccess for SpiDmaRxChannelImpl {
+ fn peripheral_interrupt(&self) -> Option {
+ Some(self.0.peripheral_interrupt())
+ }
+
+ fn async_handler(&self) -> Option {
+ Some(self.0.async_handler())
+ }
+}
impl> InterruptAccess
for SpiDmaRxChannelImpl
@@ -245,7 +289,7 @@ impl> InterruptAccess EnumSet {
@@ -285,7 +329,7 @@ impl> InterruptAccess EnumSet