Compare commits
129 Commits
esp-wifi-v
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7612ada45 | ||
|
|
d581195df7 | ||
|
|
040c0fd353 | ||
|
|
021676e871 | ||
|
|
848029b152 | ||
|
|
2b80e4d123 | ||
|
|
0ef00206d5 | ||
|
|
409641dd7e | ||
|
|
9f553b1b0e | ||
|
|
8a7f8361a6 | ||
|
|
7d0b39dbd2 | ||
|
|
246e7f1c8b | ||
|
|
6b4312fb90 | ||
|
|
5a64d9ba8f | ||
|
|
f1276f7d1b | ||
|
|
bb406cec6b | ||
|
|
66d2effee2 | ||
|
|
b3401bff59 | ||
|
|
2a4e58a230 | ||
|
|
5cd0d6f6bf | ||
|
|
39da5337ac | ||
|
|
7319458dd2 | ||
|
|
05b5bac027 | ||
|
|
6494354a91 | ||
|
|
713cd491b6 | ||
|
|
337b3cc6b7 | ||
|
|
dc2b968491 | ||
|
|
19eb7728bb | ||
|
|
551386216e | ||
|
|
1f929af377 | ||
|
|
06a15807e3 | ||
|
|
0f384992cf | ||
|
|
36095e447e | ||
|
|
c2de8a1859 | ||
|
|
b9ebeb1809 | ||
|
|
8b16365c2d | ||
|
|
62c72947dd | ||
|
|
286219707c | ||
|
|
d66e153686 | ||
|
|
7f8af8a651 | ||
|
|
2e6a95ac99 | ||
|
|
5c30c925ca | ||
|
|
f83ab23f04 | ||
|
|
f1c372f250 | ||
|
|
151e66c3b3 | ||
|
|
a3b3547212 | ||
|
|
1fff464b66 | ||
|
|
036d08e09f | ||
|
|
cde6169d6d | ||
|
|
d4386adfc7 | ||
|
|
97598968eb | ||
|
|
b8f15f0a8b | ||
|
|
f2b958b501 | ||
|
|
f7c1bdbfc5 | ||
|
|
78c63a7a0f | ||
|
|
af8eaea1e3 | ||
|
|
1684ba10f0 | ||
|
|
2ca1545b50 | ||
|
|
f990957f21 | ||
|
|
d9f1e9a53f | ||
|
|
85d30e9816 | ||
|
|
a12e7fece1 | ||
|
|
bc0bedd628 | ||
|
|
3a03dd88c7 | ||
|
|
cc4e527eaf | ||
|
|
d66281609b | ||
|
|
62994578c2 | ||
|
|
35d3ed301c | ||
|
|
273fdc0928 | ||
|
|
3cc8d611bd | ||
|
|
f6126502ae | ||
|
|
d2f15d69d7 | ||
|
|
5135965116 | ||
|
|
6b01f7993b | ||
|
|
d86a079ea9 | ||
|
|
9458fd3ed4 | ||
|
|
2d4ccd735f | ||
|
|
b4379b8518 | ||
|
|
b26b31f1ce | ||
|
|
dfa96820f0 | ||
|
|
033c4b84c6 | ||
|
|
50387290d9 | ||
|
|
dcded33d3d | ||
|
|
d54f8440a5 | ||
|
|
91d7f23982 | ||
|
|
a189eff517 | ||
|
|
da59a4eb36 | ||
|
|
94e7ffbcef | ||
|
|
a6a83d3bb5 | ||
|
|
891a12e13f | ||
|
|
9f3476b006 | ||
|
|
2d3fdeb876 | ||
|
|
2d87bb0002 | ||
|
|
b6117d5040 | ||
|
|
92910bf1cb | ||
|
|
1a2bee6f1f | ||
|
|
aeda6ac00a | ||
|
|
cfb83b153d | ||
|
|
2512658653 | ||
|
|
b50a075449 | ||
|
|
7095576e6a | ||
|
|
eec75c8f82 | ||
|
|
aed0fac0eb | ||
|
|
b06c7a470c | ||
|
|
6a008bf597 | ||
|
|
a00643f22d | ||
|
|
ef98e2b24f | ||
|
|
8b36a43c07 | ||
|
|
3adb0b288e | ||
|
|
fe482bdd05 | ||
|
|
09511c750a | ||
|
|
51215093ca | ||
|
|
45395d4cc9 | ||
|
|
79ca9d07aa | ||
|
|
e98674e8fa | ||
|
|
c1f0c134c3 | ||
|
|
b5775667b4 | ||
|
|
60a2c76005 | ||
|
|
9c1d99d9b4 | ||
|
|
3a4a7632b1 | ||
|
|
f81b5f6c7f | ||
|
|
973671c3cb | ||
|
|
f9203dc523 | ||
|
|
457ed44802 | ||
|
|
02ddad47c0 | ||
|
|
a824963943 | ||
|
|
2fc10d684c | ||
|
|
8600c4009f | ||
|
|
3c1aba5088 |
@ -1,3 +1,4 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
xfmt = "xtask fmt-packages"
|
||||
qa = "xtask run-example qa-test"
|
||||
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@ -23,7 +23,7 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MSRV: "1.79.0"
|
||||
MSRV: "1.83.0"
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
DEFMT_LOG: trace
|
||||
|
||||
@ -70,6 +70,7 @@ jobs:
|
||||
- uses: esp-rs/xtensa-toolchain@v1.5
|
||||
with:
|
||||
ldproxy: false
|
||||
version: 1.83.0.1
|
||||
# Install the Rust stable toolchain for RISC-V devices:
|
||||
- uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
@ -137,10 +138,10 @@ jobs:
|
||||
|
||||
- name: msrv RISCV (esp-wifi)
|
||||
run: |
|
||||
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
|
||||
cargo xtask build-package --features=esp32c2,wifi,ble,esp-hal/unstable --target=riscv32imc-unknown-none-elf esp-wifi
|
||||
cargo xtask build-package --features=esp32c3,wifi,ble,esp-hal/unstable --target=riscv32imc-unknown-none-elf esp-wifi
|
||||
cargo xtask build-package --features=esp32c6,wifi,ble,esp-hal/unstable --target=riscv32imac-unknown-none-elf esp-wifi
|
||||
cargo xtask build-package --features=esp32h2,ble,esp-hal/unstable --target=riscv32imac-unknown-none-elf esp-wifi
|
||||
|
||||
# Verify the MSRV for all Xtensa chips:
|
||||
- name: msrv Xtensa (esp-hal)
|
||||
@ -151,9 +152,9 @@ jobs:
|
||||
|
||||
- name: msrv Xtensa (esp-wifi)
|
||||
run: |
|
||||
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
|
||||
cargo xtask build-package --toolchain=esp --features=esp32,wifi,ble,esp-hal/unstable --target=xtensa-esp32-none-elf esp-wifi
|
||||
cargo xtask build-package --toolchain=esp --features=esp32s2,wifi,esp-hal/unstable --target=xtensa-esp32s2-none-elf esp-wifi
|
||||
cargo xtask build-package --toolchain=esp --features=esp32s3,wifi,ble,esp-hal/unstable --target=xtensa-esp32s3-none-elf esp-wifi
|
||||
|
||||
- name: msrv (esp-lp-hal)
|
||||
run: |
|
||||
|
||||
1
.github/workflows/documentation.yml
vendored
1
.github/workflows/documentation.yml
vendored
@ -36,6 +36,7 @@ jobs:
|
||||
with:
|
||||
default: true
|
||||
ldproxy: false
|
||||
version: 1.83.0.1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
51
.github/workflows/hil.yml
vendored
51
.github/workflows/hil.yml
vendored
@ -116,30 +116,15 @@ jobs:
|
||||
buildtargets: ${{ matrix.target.soc }}
|
||||
default: true
|
||||
ldproxy: false
|
||||
version: 1.83.0.1
|
||||
|
||||
- name: Build tests
|
||||
run: cargo xtask build-tests ${{ matrix.target.soc }}
|
||||
|
||||
- name: Prepare artifact
|
||||
run: |
|
||||
# Create the 'tests' directory if it doesn't exist
|
||||
mkdir -p tests
|
||||
|
||||
# Find ELF files in the specified path and move them to 'tests'
|
||||
find "hil-test/target/${{ matrix.target.rust-target }}/release/deps/" -type f -exec file {} + | \
|
||||
grep ELF | \
|
||||
awk -F: '{print $1}' | \
|
||||
xargs -I {} mv {} tests
|
||||
|
||||
# Rename files in 'tests' by removing everything after the first dash
|
||||
for file in tests/*-*; do
|
||||
base_name="$(basename "$file" | cut -d'-' -f1)"
|
||||
mv "$file" "tests/$base_name"
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tests-${{ matrix.target.soc }}
|
||||
path: /home/runner/work/esp-hal/esp-hal/tests
|
||||
path: /home/runner/work/esp-hal/esp-hal/target/tests/${{ matrix.target.soc }}
|
||||
if-no-files-found: error
|
||||
overwrite: true
|
||||
|
||||
@ -156,32 +141,25 @@ jobs:
|
||||
- soc: esp32c2
|
||||
runner: esp32c2-jtag
|
||||
host: aarch64
|
||||
hubs: "1 3"
|
||||
- soc: esp32c3
|
||||
runner: esp32c3-usb
|
||||
host: armv7
|
||||
hubs: "1-1"
|
||||
- soc: esp32c6
|
||||
runner: esp32c6-usb
|
||||
host: armv7
|
||||
hubs: "1-1"
|
||||
- soc: esp32h2
|
||||
runner: esp32h2-usb
|
||||
host: armv7
|
||||
hubs: "1-1"
|
||||
# Xtensa devices:
|
||||
- soc: esp32
|
||||
runner: esp32-jtag
|
||||
host: aarch64
|
||||
hubs: "1 3"
|
||||
- soc: esp32s2
|
||||
runner: esp32s2-jtag
|
||||
host: armv7
|
||||
hubs: "1-1"
|
||||
- soc: esp32s3
|
||||
runner: esp32s3-usb
|
||||
host: armv7
|
||||
hubs: "1-1"
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
@ -192,32 +170,11 @@ jobs:
|
||||
with:
|
||||
name: xtask-${{ matrix.target.host }}
|
||||
|
||||
- name: Cycle USB ports
|
||||
run: |
|
||||
export PATH=$PATH:/home/espressif/.cargo/bin
|
||||
for i in {1..10}; do
|
||||
# Disable all used hubs
|
||||
for hub in ${{ matrix.target.hubs }}; do
|
||||
sudo uhubctl -a off -l $hub
|
||||
done
|
||||
|
||||
sleep 5
|
||||
|
||||
# Enable all used hubs
|
||||
for hub in ${{ matrix.target.hubs }}; do
|
||||
sudo uhubctl -a on -l $hub
|
||||
done
|
||||
|
||||
sleep 0.5
|
||||
|
||||
if probe-rs list | grep -q "\[0\]:"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Run Tests
|
||||
id: run-tests
|
||||
run: |
|
||||
[ -f ~/setup.sh ] && source ~/setup.sh
|
||||
|
||||
export PATH=$PATH:/home/espressif/.cargo/bin
|
||||
chmod +x xtask
|
||||
./xtask run-elfs ${{ matrix.target.soc }} tests-${{ matrix.target.soc }}
|
||||
|
||||
@ -20,7 +20,7 @@ If you have any questions, comments, or concerns, please [open an issue], [start
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This project is still in the relatively early stages of development, and as such there should be no expectation of API stability. A significant number of peripherals currently have drivers implemented but have varying levels of functionality. For most tasks, this should be usable already, however some more advanced or uncommon features may not yet be implemented.
|
||||
> This repository includes crates that are at various stages of maturity and stability. While many functionalities have already been implemented and are usable for most tasks, certain advanced or less common features may still be under development. Each crate may offer different levels of functionality and guarantees.
|
||||
|
||||
[esp-lp-hal]: https://github.com/esp-rs/esp-hal/tree/main/esp-lp-hal
|
||||
[esp-idf-svc]: https://github.com/esp-rs/esp-idf-svc
|
||||
|
||||
@ -7,43 +7,61 @@ This is a living document - make sure to check the latest version of this docume
|
||||
> [!NOTE]
|
||||
> Not all of the currently existing code follows this guideline, yet.
|
||||
|
||||
In general, the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines) apply to all projects in the ESP-RS GitHub organization where possible. (`C-RW-VALUE` and `C-SERDE` do not apply)
|
||||
In general, the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines) apply to all projects in the ESP-RS GitHub organization where possible.
|
||||
- Especially for public API but if possible also for internal APIs.
|
||||
|
||||
Especially for public API but if possible also for internal APIs.
|
||||
## Amendments to the Rust API Guidelines
|
||||
|
||||
The following paragraphs contain additional recommendations.
|
||||
- `C-RW-VALUE` and `C-SERDE` do not apply.
|
||||
- `C-COMMON-TRAITS`:
|
||||
The set of traits to implement depend on the type and use case. In esp-hal, we can highlight a few such use cases and provide recommendations what should be implemented. If nothing here applies, use your best judgement.
|
||||
- Driver structures: `Debug`
|
||||
- Driver configuration: `Default`, `Debug`, `PartialEq/Eq`, `Clone/Copy`, `Hash`
|
||||
- `Clone/Copy` depends on the size and contents of the structure. They should generally be implemented, unless there is a good reason not to.
|
||||
- The `Default` configuration needs to make sense for a particular driver, and applying the default configuration must not fail.
|
||||
- Error types: `Debug`, `PartialEq/Eq`, `Clone/Copy`, `Hash`, `Error`, `Display`
|
||||
|
||||
## Construction and Destruction of Drivers
|
||||
|
||||
- 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<P = InputConnection> + 'd) -> Self)` or `fn with_signal_name(self, pin: impl Peripheral<P = OutputConnection> + 'd) -> Self)`
|
||||
- Drivers must 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<P = impl PeripheralInput> + 'd) -> Self` or `fn with_signal_name(self, pin: impl Peripheral<P = impl PeripheralOutput> + '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.
|
||||
- The driver should not be generic over the peripheral instance.
|
||||
- The author must to use `crate::any_peripheral` to define the "any" peripheral instance type.
|
||||
- The driver must implement a `new` constructor that automatically converts the peripheral instance into the any type.
|
||||
- If a driver is configurable, configuration options should be implemented as a `Config` struct in the same module where the driver is located.
|
||||
- The driver's constructor should take the config struct by value, and it should return `Result<Self, ConfigError>`.
|
||||
- The `ConfigError` enum should be separate from other `Error` enums used by the driver.
|
||||
- The driver should implement `fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError>`.
|
||||
- In case the driver's configuration is infallible (all possible combinations of options are supported by the hardware), the `ConfigError` should be implemented as an empty `enum`.
|
||||
- Configuration structs should derive `procmacros::BuilderLite` in order to automatically implement the Builder Lite pattern for them.
|
||||
- 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 must include a `crate::Mode` type parameter.
|
||||
- By default, constructors must configure the driver for blocking mode. The driver must implement `into_async` (and a matching `into_blocking`) function that reconfigures the driver.
|
||||
- `into_async` must configure the driver and/or the associated DMA channels. This most often means enabling an interrupt handler.
|
||||
- `into_blocking` must undo the configuration done by `into_async`.
|
||||
- The asynchronous driver implementation must also expose the blocking methods (except for interrupt related functions).
|
||||
- Drivers must have a `Drop` implementation resetting the peripheral to idle state. There are some exceptions to this:
|
||||
- GPIO where common usage is to "set and drop" so they can't be changed
|
||||
- Where we don't want to disable the peripheral as it's used internally, for example SYSTIMER is used by `time::now()` API. See `KEEP_ENABLED` in src/system.rs
|
||||
- A driver doesn't need to do anything special for deinitialization and has a `PeripheralGuard` field which implements the disabling and resetting of the peripheral.
|
||||
- Consider using a builder-like pattern for driver construction.
|
||||
|
||||
## Interoperability
|
||||
|
||||
- `cfg` gated `defmt` derives and impls are added to new structs and enums.
|
||||
- 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`.
|
||||
- Where the guidelines suggest implementing `Debug`, `defmt::Format` should also be implemented.
|
||||
- The `defmt::Format` implementation needs to be gated behind the `defmt` feature.
|
||||
- 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))]`
|
||||
- Implementations of common, but unstable traits (e.g. `embassy_embedded_hal::SetConfig`) need to be gated with the `unstable` feature.
|
||||
|
||||
## API Surface
|
||||
|
||||
- API documentation shouldn't be an afterthought.
|
||||
- Private details shouldn't leak into the public API, and should be made private where technically possible.
|
||||
- API documentation must be provided for every new driver and API.
|
||||
- Private details should not leak into the public API, and should be made private where technically possible.
|
||||
- Implementation details that _need_ to be public should be marked with `#[doc(hidden)]` and a comment as to why it needs to be public.
|
||||
- For the time being, this includes any `Instance` traits, and `State` or `Info` structs as well.
|
||||
- Functions which technically need to be public but shouldn't be callable by the user need to be sealed.
|
||||
- see [this example in Rust's core library](https://github.com/rust-lang/rust/blob/044a28a4091f2e1a5883f7fa990223f8b200a2cd/library/core/src/error.rs#L89-L100)
|
||||
- Any public traits, that **must not** be implemented downstream need to be `Sealed`.
|
||||
@ -55,12 +73,14 @@ The following paragraphs contain additional recommendations.
|
||||
- These often lead to usability problems, and tend to just complicate things needlessly - sometimes it can be a good tradeoff to make a type not ZST
|
||||
- Common cases of useless type info is storing pin information - this is usually not required after configuring the pins and will bloat the complexity of the type massively. When following the `PeripheralRef` pattern it's not needed in order to keep users from re-using the pin while in use
|
||||
- Avoiding `&mut self` when `&self` is safe to use. `&self` is generally easier to use as an API. Typical applications of this are where the methods just do writes to registers which don't have side effects.
|
||||
- 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.
|
||||
- Avoid abbreviations and contractions in the API, where possible.
|
||||
- Saving a few characters may introduce ambiguity, e.g `SpiTransDone`, is it `Transmit` or `Transfer`?
|
||||
- Common abbreviations, that are well understood such as `Dma` are perfectly fine.
|
||||
|
||||
## Maintainability
|
||||
|
||||
@ -68,9 +88,10 @@ The following paragraphs contain additional recommendations.
|
||||
- Every line of code is a liability. Take some time to see if your implementation can be simplified before opening a PR.
|
||||
- If you are porting code from ESP-IDF (or anything else), please include a link WITH the commit hash in it, and please highlight the relevant line(s) of code
|
||||
- If necessary provide further context as comments (consider linking to code, PRs, TRM - make sure to use permanent links, e.g. include the hash when linking to a Git repository, include the revision, page number etc. when linking to TRMs)
|
||||
- Prefer line comments (//) to block comments (/* ... */)
|
||||
- Generally, follow common "good practices" and idiomatic Rust style
|
||||
- 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.
|
||||
- Prefer `cfg_if!` (or, if the branches just pick between separate values of the same variable, `cfg!()`) over multiple exclusive `#[cfg]` attributes. `cfg_if!`/`cfg!()` visually divide the options, often results in simpler conditions and simplifies adding new branches in the future.
|
||||
|
||||
## Driver implementation
|
||||
|
||||
@ -114,4 +135,10 @@ Modules should have the following documentation format:
|
||||
#, "/api-reference/peripherals/etm.html)")]
|
||||
```
|
||||
- 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.
|
||||
- Documentation examples must be short
|
||||
- But must also provide value beyond what the rustdoc generated docs show
|
||||
- Showing a snippet of a slightly more complex interaction, for example inverting the signals for a driver
|
||||
- Showing construction if it is more complex, or requires some non-obvious precursor steps. Think about this for drivers that take a generic instance to construct, rustdoc doesn't do a good job of showing what concrete things can be passed into a constructor.
|
||||
- For more complex scenarios, create an example.
|
||||
- Use rustdoc syntax for linking to other documentation items instead of markdown links where possible
|
||||
- https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html
|
||||
|
||||
1
esp-alloc/.clippy.toml
Normal file
1
esp-alloc/.clippy.toml
Normal file
@ -0,0 +1 @@
|
||||
avoid-breaking-exported-api = false
|
||||
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- `esp_alloc::HEAP.stats()` can now be used to get heap usage informations (#2137)
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -4,29 +4,34 @@ version = "0.5.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
description = "A heap allocator for Espressif devices"
|
||||
keywords = ["allocator", "embedded", "embedded-hal", "esp32", "espressif", "memory"]
|
||||
categories = ["embedded", "memory-management", "no-std"]
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
keywords = [
|
||||
"allocator",
|
||||
"esp32",
|
||||
"riscv",
|
||||
"xtensa",
|
||||
]
|
||||
categories = [
|
||||
"memory-management",
|
||||
"no-std",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "riscv32imc-unknown-none-elf"
|
||||
features = ["nightly"]
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3.8", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
critical-section = "1.1.3"
|
||||
enumset = "1.1.5"
|
||||
linked_list_allocator = { version = "0.10.5", default-features = false, features = ["const_mut_refs"] }
|
||||
document-features = "0.2.10"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
nightly = []
|
||||
|
||||
## Implement `defmt::Format` on certain types.
|
||||
defmt = ["dep:defmt"]
|
||||
|
||||
## Enable this feature if you want to keep stats about the internal heap usage such as:
|
||||
## - Max memory usage since initialization of the heap
|
||||
## - Total allocated memory since initialization of the heap
|
||||
## - Total freed memory since initialization of the heap
|
||||
##
|
||||
## ⚠️ Note: Enabling this feature will require extra computation every time alloc/dealloc is called.
|
||||
internal-heap-stats = []
|
||||
|
||||
@ -51,7 +51,28 @@
|
||||
//! ```rust
|
||||
//! let large_buffer: Vec<u8, _> = Vec::with_capacity_in(1048576, &PSRAM_ALLOCATOR);
|
||||
//! ```
|
||||
|
||||
//!
|
||||
//! You can also get stats about the heap usage at anytime with:
|
||||
//! ```rust
|
||||
//! let stats: HeapStats = esp_alloc::HEAP.stats();
|
||||
//! // HeapStats implements the Display and defmt::Format traits, so you can pretty-print the heap stats.
|
||||
//! println!("{}", stats);
|
||||
//! ```
|
||||
//!
|
||||
//! ```txt
|
||||
//! HEAP INFO
|
||||
//! Size: 131068
|
||||
//! Current usage: 46148
|
||||
//! Max usage: 46148
|
||||
//! Total freed: 0
|
||||
//! Total allocated: 46148
|
||||
//! Memory Layout:
|
||||
//! Internal | ████████████░░░░░░░░░░░░░░░░░░░░░░░ | Used: 35% (Used 46148 of 131068, free: 84920)
|
||||
//! Unused | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
|
||||
//! Unused | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
|
||||
//! ```
|
||||
//! ## Feature Flags
|
||||
#![doc = document_features::document_features!()]
|
||||
#![no_std]
|
||||
#![cfg_attr(feature = "nightly", feature(allocator_api))]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
|
||||
@ -63,6 +84,7 @@ use core::alloc::{AllocError, Allocator};
|
||||
use core::{
|
||||
alloc::{GlobalAlloc, Layout},
|
||||
cell::RefCell,
|
||||
fmt::Display,
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
|
||||
@ -76,7 +98,22 @@ pub static HEAP: EspHeap = EspHeap::empty();
|
||||
|
||||
const NON_REGION: Option<HeapRegion> = None;
|
||||
|
||||
#[derive(EnumSetType)]
|
||||
const BAR_WIDTH: usize = 35;
|
||||
|
||||
fn write_bar(f: &mut core::fmt::Formatter<'_>, usage_percent: usize) -> core::fmt::Result {
|
||||
let used_blocks = BAR_WIDTH * usage_percent / 100;
|
||||
(0..used_blocks).try_for_each(|_| write!(f, "█"))?;
|
||||
(used_blocks..BAR_WIDTH).try_for_each(|_| write!(f, "░"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
fn write_bar_defmt(fmt: defmt::Formatter, usage_percent: usize) {
|
||||
let used_blocks = BAR_WIDTH * usage_percent / 100;
|
||||
(0..used_blocks).for_each(|_| defmt::write!(fmt, "█"));
|
||||
(used_blocks..BAR_WIDTH).for_each(|_| defmt::write!(fmt, "░"));
|
||||
}
|
||||
|
||||
#[derive(EnumSetType, Debug)]
|
||||
/// Describes the properties of a memory region
|
||||
pub enum MemoryCapability {
|
||||
/// Memory must be internal; specifically it should not disappear when
|
||||
@ -86,6 +123,75 @@ pub enum MemoryCapability {
|
||||
External,
|
||||
}
|
||||
|
||||
/// Stats for a heap region
|
||||
#[derive(Debug)]
|
||||
pub struct RegionStats {
|
||||
/// Total usable size of the heap region in bytes.
|
||||
size: usize,
|
||||
|
||||
/// Currently used size of the heap region in bytes.
|
||||
used: usize,
|
||||
|
||||
/// Free size of the heap region in bytes.
|
||||
free: usize,
|
||||
|
||||
/// Capabilities of the memory region.
|
||||
capabilities: EnumSet<MemoryCapability>,
|
||||
}
|
||||
|
||||
impl Display for RegionStats {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let usage_percent = self.used * 100 / self.size;
|
||||
|
||||
// Display Memory type
|
||||
if self.capabilities.contains(MemoryCapability::Internal) {
|
||||
write!(f, "Internal")?;
|
||||
} else if self.capabilities.contains(MemoryCapability::External) {
|
||||
write!(f, "External")?;
|
||||
} else {
|
||||
write!(f, "Unknown")?;
|
||||
}
|
||||
|
||||
write!(f, " | ")?;
|
||||
|
||||
write_bar(f, usage_percent)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
" | Used: {}% (Used {} of {}, free: {})",
|
||||
usage_percent, self.used, self.size, self.free
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for RegionStats {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
let usage_percent = self.used * 100 / self.size;
|
||||
|
||||
if self.capabilities.contains(MemoryCapability::Internal) {
|
||||
defmt::write!(fmt, "Internal");
|
||||
} else if self.capabilities.contains(MemoryCapability::External) {
|
||||
defmt::write!(fmt, "External");
|
||||
} else {
|
||||
defmt::write!(fmt, "Unknown");
|
||||
}
|
||||
|
||||
defmt::write!(fmt, " | ");
|
||||
|
||||
write_bar_defmt(fmt, usage_percent);
|
||||
|
||||
defmt::write!(
|
||||
fmt,
|
||||
" | Used: {}% (Used {} of {}, free: {})",
|
||||
usage_percent,
|
||||
self.used,
|
||||
self.size,
|
||||
self.free
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A memory region to be used as heap memory
|
||||
pub struct HeapRegion {
|
||||
heap: Heap,
|
||||
@ -112,6 +218,104 @@ impl HeapRegion {
|
||||
|
||||
Self { heap, capabilities }
|
||||
}
|
||||
|
||||
/// Return stats for the current memory region
|
||||
pub fn stats(&self) -> RegionStats {
|
||||
RegionStats {
|
||||
size: self.heap.size(),
|
||||
used: self.heap.used(),
|
||||
free: self.heap.free(),
|
||||
capabilities: self.capabilities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stats for a heap allocator
|
||||
///
|
||||
/// Enable the "internal-heap-stats" feature if you want collect additional heap
|
||||
/// informations at the cost of extra cpu time during every alloc/dealloc.
|
||||
#[derive(Debug)]
|
||||
pub struct HeapStats {
|
||||
/// Granular stats for all the configured memory regions.
|
||||
region_stats: [Option<RegionStats>; 3],
|
||||
|
||||
/// Total size of all combined heap regions in bytes.
|
||||
size: usize,
|
||||
|
||||
/// Current usage of the heap across all configured regions in bytes.
|
||||
current_usage: usize,
|
||||
|
||||
/// Estimation of the max used heap in bytes.
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
max_usage: usize,
|
||||
|
||||
/// Estimation of the total allocated bytes since initialization.
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
total_allocated: usize,
|
||||
|
||||
/// Estimation of the total freed bytes since initialization.
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
total_freed: usize,
|
||||
}
|
||||
|
||||
impl Display for HeapStats {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
writeln!(f, "HEAP INFO")?;
|
||||
writeln!(f, "Size: {}", self.size)?;
|
||||
writeln!(f, "Current usage: {}", self.current_usage)?;
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
{
|
||||
writeln!(f, "Max usage: {}", self.max_usage)?;
|
||||
writeln!(f, "Total freed: {}", self.total_freed)?;
|
||||
writeln!(f, "Total allocated: {}", self.total_allocated)?;
|
||||
}
|
||||
writeln!(f, "Memory Layout: ")?;
|
||||
for region in self.region_stats.iter() {
|
||||
if let Some(region) = region.as_ref() {
|
||||
region.fmt(f)?;
|
||||
writeln!(f)?;
|
||||
} else {
|
||||
// Display unused memory regions
|
||||
write!(f, "Unused | ")?;
|
||||
write_bar(f, 0)?;
|
||||
writeln!(f, " |")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for HeapStats {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "HEAP INFO\n");
|
||||
defmt::write!(fmt, "Size: {}\n", self.size);
|
||||
defmt::write!(fmt, "Current usage: {}\n", self.current_usage);
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
{
|
||||
defmt::write!(fmt, "Max usage: {}\n", self.max_usage);
|
||||
defmt::write!(fmt, "Total freed: {}\n", self.total_freed);
|
||||
defmt::write!(fmt, "Total allocated: {}\n", self.total_allocated);
|
||||
}
|
||||
defmt::write!(fmt, "Memory Layout:\n");
|
||||
for region in self.region_stats.iter() {
|
||||
if let Some(region) = region.as_ref() {
|
||||
defmt::write!(fmt, "{}\n", region);
|
||||
} else {
|
||||
defmt::write!(fmt, "Unused | ");
|
||||
write_bar_defmt(fmt, 0);
|
||||
defmt::write!(fmt, " |\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal stats to keep track across multiple regions.
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
struct InternalHeapStats {
|
||||
max_usage: usize,
|
||||
total_allocated: usize,
|
||||
total_freed: usize,
|
||||
}
|
||||
|
||||
/// A memory allocator
|
||||
@ -120,6 +324,8 @@ impl HeapRegion {
|
||||
/// memory in regions satisfying specific needs.
|
||||
pub struct EspHeap {
|
||||
heap: Mutex<RefCell<[Option<HeapRegion>; 3]>>,
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
internal_heap_stats: Mutex<RefCell<InternalHeapStats>>,
|
||||
}
|
||||
|
||||
impl EspHeap {
|
||||
@ -127,6 +333,12 @@ impl EspHeap {
|
||||
pub const fn empty() -> Self {
|
||||
EspHeap {
|
||||
heap: Mutex::new(RefCell::new([NON_REGION; 3])),
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
internal_heap_stats: Mutex::new(RefCell::new(InternalHeapStats {
|
||||
max_usage: 0,
|
||||
total_allocated: 0,
|
||||
total_freed: 0,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +401,51 @@ impl EspHeap {
|
||||
})
|
||||
}
|
||||
|
||||
/// Return usage stats for the [Heap].
|
||||
///
|
||||
/// Note:
|
||||
/// [HeapStats] directly implements [Display], so this function can be
|
||||
/// called from within `println!()` to pretty-print the usage of the
|
||||
/// heap.
|
||||
pub fn stats(&self) -> HeapStats {
|
||||
const EMPTY_REGION_STAT: Option<RegionStats> = None;
|
||||
let mut region_stats: [Option<RegionStats>; 3] = [EMPTY_REGION_STAT; 3];
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let mut used = 0;
|
||||
let mut free = 0;
|
||||
let regions = self.heap.borrow_ref(cs);
|
||||
for (id, region) in regions.iter().enumerate() {
|
||||
if let Some(region) = region.as_ref() {
|
||||
let stats = region.stats();
|
||||
free += stats.free;
|
||||
used += stats.used;
|
||||
region_stats[id] = Some(region.stats());
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "internal-heap-stats")] {
|
||||
let internal_heap_stats = self.internal_heap_stats.borrow_ref(cs);
|
||||
HeapStats {
|
||||
region_stats,
|
||||
size: free + used,
|
||||
current_usage: used,
|
||||
max_usage: internal_heap_stats.max_usage,
|
||||
total_allocated: internal_heap_stats.total_allocated,
|
||||
total_freed: internal_heap_stats.total_freed,
|
||||
}
|
||||
} else {
|
||||
HeapStats {
|
||||
region_stats,
|
||||
size: free + used,
|
||||
current_usage: used,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an estimate of the amount of bytes available.
|
||||
pub fn free(&self) -> usize {
|
||||
self.free_caps(EnumSet::empty())
|
||||
@ -232,6 +489,8 @@ impl EspHeap {
|
||||
layout: Layout,
|
||||
) -> *mut u8 {
|
||||
critical_section::with(|cs| {
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
let before = self.used();
|
||||
let mut regions = self.heap.borrow_ref_mut(cs);
|
||||
let mut iter = (*regions).iter_mut().filter(|region| {
|
||||
if region.is_some() {
|
||||
@ -256,7 +515,22 @@ impl EspHeap {
|
||||
}
|
||||
};
|
||||
|
||||
res.map_or(ptr::null_mut(), |allocation| allocation.as_ptr())
|
||||
res.map_or(ptr::null_mut(), |allocation| {
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
{
|
||||
let mut internal_heap_stats = self.internal_heap_stats.borrow_ref_mut(cs);
|
||||
drop(regions);
|
||||
// We need to call used because [linked_list_allocator::Heap] does internal size
|
||||
// alignment so we cannot use the size provided by the layout.
|
||||
let used = self.used();
|
||||
|
||||
internal_heap_stats.total_allocated += used - before;
|
||||
internal_heap_stats.max_usage =
|
||||
core::cmp::max(internal_heap_stats.max_usage, used);
|
||||
}
|
||||
|
||||
allocation.as_ptr()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -272,6 +546,8 @@ unsafe impl GlobalAlloc for EspHeap {
|
||||
}
|
||||
|
||||
critical_section::with(|cs| {
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
let before = self.used();
|
||||
let mut regions = self.heap.borrow_ref_mut(cs);
|
||||
let mut iter = (*regions).iter_mut();
|
||||
|
||||
@ -280,6 +556,16 @@ unsafe impl GlobalAlloc for EspHeap {
|
||||
region.heap.deallocate(NonNull::new_unchecked(ptr), layout);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal-heap-stats")]
|
||||
{
|
||||
let mut internal_heap_stats = self.internal_heap_stats.borrow_ref_mut(cs);
|
||||
drop(regions);
|
||||
// We need to call `used()` because [linked_list_allocator::Heap] does internal
|
||||
// size alignment so we cannot use the size provided by the
|
||||
// layout.
|
||||
internal_heap_stats.total_freed += before - self.used();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,9 +29,9 @@ macro_rules! heap_allocator {
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! psram_allocator {
|
||||
($peripheral:expr,$psram_module:path) => {{
|
||||
($peripheral:expr, $psram_module:path) => {{
|
||||
use $psram_module as _psram;
|
||||
let (start, size) = _psram::init_psram($peripheral, _psram::PsramConfig::default());
|
||||
let (start, size) = _psram::psram_raw_parts(&$peripheral);
|
||||
unsafe {
|
||||
$crate::HEAP.add_region($crate::HeapRegion::new(
|
||||
start,
|
||||
|
||||
@ -4,6 +4,8 @@ version = "0.14.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.76.0"
|
||||
description = "Bare-metal backtrace support for Espressif devices"
|
||||
keywords = ["backtrace", "embedded", "esp32", "espressif"]
|
||||
categories = ["embedded", "hardware-support", "no-std"]
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
|
||||
@ -11,8 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Users no longer have to manually import `esp_config_int_parse`. (#2630)
|
||||
|
||||
### Changed
|
||||
|
||||
- Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848)
|
||||
|
||||
### Removed
|
||||
|
||||
## 0.2.0 - 2024-11-20
|
||||
|
||||
@ -249,7 +249,7 @@ fn integer_in_range(range: &Range<i128>, value: &Value) -> Result<(), Error> {
|
||||
///
|
||||
/// Unknown keys with the supplied prefix will cause this function to panic.
|
||||
pub fn generate_config(
|
||||
prefix: &str,
|
||||
crate_name: &str,
|
||||
config: &[(&str, &str, Value, Option<Validator>)],
|
||||
emit_md_tables: bool,
|
||||
) -> HashMap<String, Value> {
|
||||
@ -264,7 +264,7 @@ pub fn generate_config(
|
||||
let mut selected_config = String::from(SELECTED_TABLE_HEADER);
|
||||
|
||||
// Ensure that the prefix is `SCREAMING_SNAKE_CASE`:
|
||||
let prefix = screaming_snake_case(prefix);
|
||||
let prefix = format!("{}_CONFIG_", screaming_snake_case(crate_name));
|
||||
|
||||
// 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
|
||||
@ -273,7 +273,7 @@ pub fn generate_config(
|
||||
.iter()
|
||||
.flat_map(|(name, _description, _default, validator)| {
|
||||
if let Some(validator) = validator {
|
||||
let name = format!("{prefix}_{}", screaming_snake_case(name));
|
||||
let name = format!("{prefix}{}", screaming_snake_case(name));
|
||||
Some((name, validator))
|
||||
} else {
|
||||
None
|
||||
@ -293,7 +293,7 @@ pub fn generate_config(
|
||||
emit_configuration(&prefix, &configs, &mut selected_config);
|
||||
|
||||
if emit_md_tables {
|
||||
let file_name = snake_case(&prefix);
|
||||
let file_name = snake_case(crate_name);
|
||||
write_config_tables(&file_name, doc_table, selected_config);
|
||||
}
|
||||
|
||||
@ -341,7 +341,7 @@ fn create_config(
|
||||
let mut configs = HashMap::new();
|
||||
|
||||
for (name, description, default, _validator) in config {
|
||||
let name = format!("{prefix}_{}", screaming_snake_case(name));
|
||||
let name = format!("{prefix}{}", screaming_snake_case(name));
|
||||
configs.insert(name.clone(), default.clone());
|
||||
|
||||
// Write documentation table line:
|
||||
@ -361,7 +361,7 @@ fn capture_from_env(prefix: &str, configs: &mut HashMap<String, Value>) {
|
||||
|
||||
// Try and capture input from the environment:
|
||||
for (var, value) in env::vars() {
|
||||
if var.strip_prefix(prefix).is_some() {
|
||||
if var.starts_with(prefix) {
|
||||
let Some(cfg) = configs.get_mut(&var) else {
|
||||
unknown.push(var);
|
||||
continue;
|
||||
@ -388,7 +388,7 @@ fn emit_configuration(
|
||||
selected_config: &mut String,
|
||||
) {
|
||||
for (name, value) in configs.iter() {
|
||||
let cfg_name = snake_case(name.trim_start_matches(&format!("{prefix}_")));
|
||||
let cfg_name = snake_case(name.trim_start_matches(prefix));
|
||||
println!("cargo:rustc-check-cfg=cfg({cfg_name})");
|
||||
|
||||
if let Value::Bool(true) = value {
|
||||
@ -464,10 +464,10 @@ mod test {
|
||||
fn env_override() {
|
||||
temp_env::with_vars(
|
||||
[
|
||||
("ESP_TEST_NUMBER", Some("0xaa")),
|
||||
("ESP_TEST_NUMBER_SIGNED", Some("-999")),
|
||||
("ESP_TEST_STRING", Some("Hello world!")),
|
||||
("ESP_TEST_BOOL", Some("true")),
|
||||
("ESP_TEST_CONFIG_NUMBER", Some("0xaa")),
|
||||
("ESP_TEST_CONFIG_NUMBER_SIGNED", Some("-999")),
|
||||
("ESP_TEST_CONFIG_STRING", Some("Hello world!")),
|
||||
("ESP_TEST_CONFIG_BOOL", Some("true")),
|
||||
],
|
||||
|| {
|
||||
let configs = generate_config(
|
||||
@ -491,28 +491,28 @@ mod test {
|
||||
|
||||
// some values have changed
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_NUMBER").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_NUMBER").unwrap() {
|
||||
Value::Integer(num) => *num,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
0xaa
|
||||
);
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_NUMBER_SIGNED").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_NUMBER_SIGNED").unwrap() {
|
||||
Value::Integer(num) => *num,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
-999
|
||||
);
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_STRING").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_STRING").unwrap() {
|
||||
Value::String(val) => val,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
"Hello world!"
|
||||
);
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_BOOL").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_BOOL").unwrap() {
|
||||
Value::Bool(val) => *val,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
@ -521,21 +521,21 @@ mod test {
|
||||
|
||||
// the rest are the defaults
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_NUMBER_DEFAULT").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_NUMBER_DEFAULT").unwrap() {
|
||||
Value::Integer(num) => *num,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
999
|
||||
);
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_STRING_DEFAULT").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_STRING_DEFAULT").unwrap() {
|
||||
Value::String(val) => val,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
"Demo"
|
||||
);
|
||||
assert_eq!(
|
||||
match configs.get("ESP_TEST_BOOL_DEFAULT").unwrap() {
|
||||
match configs.get("ESP_TEST_CONFIG_BOOL_DEFAULT").unwrap() {
|
||||
Value::Bool(val) => *val,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
@ -549,10 +549,10 @@ mod 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")),
|
||||
("ESP_TEST_CONFIG_POSITIVE_NUMBER", Some("7")),
|
||||
("ESP_TEST_CONFIG_NEGATIVE_NUMBER", Some("-1")),
|
||||
("ESP_TEST_CONFIG_NON_NEGATIVE_NUMBER", Some("0")),
|
||||
("ESP_TEST_CONFIG_RANGE", Some("9")),
|
||||
],
|
||||
|| {
|
||||
generate_config(
|
||||
@ -591,7 +591,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn custom_validation_passes() {
|
||||
temp_env::with_vars([("ESP_TEST_NUMBER", Some("13"))], || {
|
||||
temp_env::with_vars([("ESP_TEST_CONFIG_NUMBER", Some("13"))], || {
|
||||
generate_config(
|
||||
"esp-test",
|
||||
&[(
|
||||
@ -615,7 +615,7 @@ mod test {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn builtin_validation_bails() {
|
||||
temp_env::with_vars([("ESP_TEST_POSITIVE_NUMBER", Some("-99"))], || {
|
||||
temp_env::with_vars([("ESP_TEST_CONFIG_POSITIVE_NUMBER", Some("-99"))], || {
|
||||
generate_config(
|
||||
"esp-test",
|
||||
&[(
|
||||
@ -632,7 +632,7 @@ mod test {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn custom_validation_bails() {
|
||||
temp_env::with_vars([("ESP_TEST_NUMBER", Some("37"))], || {
|
||||
temp_env::with_vars([("ESP_TEST_CONFIG_NUMBER", Some("37"))], || {
|
||||
generate_config(
|
||||
"esp-test",
|
||||
&[(
|
||||
@ -658,8 +658,8 @@ mod test {
|
||||
fn env_unknown_bails() {
|
||||
temp_env::with_vars(
|
||||
[
|
||||
("ESP_TEST_NUMBER", Some("0xaa")),
|
||||
("ESP_TEST_RANDOM_VARIABLE", Some("")),
|
||||
("ESP_TEST_CONFIG_NUMBER", Some("0xaa")),
|
||||
("ESP_TEST_CONFIG_RANDOM_VARIABLE", Some("")),
|
||||
],
|
||||
|| {
|
||||
generate_config(
|
||||
@ -674,7 +674,7 @@ mod test {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn env_invalid_values_bails() {
|
||||
temp_env::with_vars([("ESP_TEST_NUMBER", Some("Hello world"))], || {
|
||||
temp_env::with_vars([("ESP_TEST_CONFIG_NUMBER", Some("Hello world"))], || {
|
||||
generate_config(
|
||||
"esp-test",
|
||||
&[("number", "NA", Value::Integer(999), None)],
|
||||
@ -682,4 +682,18 @@ mod test {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_unknown_prefix_is_ignored() {
|
||||
temp_env::with_vars(
|
||||
[("ESP_TEST_OTHER_CONFIG_NUMBER", Some("Hello world"))],
|
||||
|| {
|
||||
generate_config(
|
||||
"esp-test",
|
||||
&[("number", "NA", Value::Integer(999), None)],
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ macro_rules! esp_config_int {
|
||||
( $ty:ty, $var:expr ) => {
|
||||
const {
|
||||
const BYTES: &[u8] = env!($var).as_bytes();
|
||||
esp_config_int_parse!($ty, BYTES)
|
||||
$crate::esp_config_int_parse!($ty, BYTES)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
1
esp-hal-embassy/.clippy.toml
Normal file
1
esp-hal-embassy/.clippy.toml
Normal file
@ -0,0 +1 @@
|
||||
avoid-breaking-exported-api = false
|
||||
@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
- Bump MSRV to 1.83 (#2615)
|
||||
|
||||
- Config: Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848)
|
||||
|
||||
### Fixed
|
||||
|
||||
### Removed
|
||||
@ -24,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
|
||||
- Reduce memory footprint by 4 bytes on multi-core MCUs.
|
||||
- The time driver no longer uses cross-core critical sections. (#2559)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
name = "esp-hal-embassy"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.79.0"
|
||||
rust-version = "1.83.0"
|
||||
description = "Embassy support for esp-hal"
|
||||
keywords = ["async", "embedded", "esp32", "espressif"]
|
||||
categories = ["asynchronous", "embedded", "hardware-support", "no-std"]
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
|
||||
15
esp-hal-embassy/MIGRATING-0.5.md
Normal file
15
esp-hal-embassy/MIGRATING-0.5.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Migration Guide from 0.5.x to v0.6.x
|
||||
|
||||
## Crate configuration changes
|
||||
|
||||
To prevent ambiguity between configurations, we had to change the naming format of configuration
|
||||
keys. Before, we used `{prefix}_{key}`, which meant that esp-hal and esp-hal-* configuration keys
|
||||
were impossible to tell apart. To fix this issue, we are changing the separator from one underscore
|
||||
character to `_CONFIG_`. This also means that users will have to change their `config.toml`
|
||||
configurations to match the new format.
|
||||
|
||||
```diff
|
||||
[env]
|
||||
-ESP_HAL_EMBASSY_LOW_POWER_WAIT="false"
|
||||
+ESP_HAL_EMBASSY_CONFIG_LOW_POWER_WAIT="false"
|
||||
```
|
||||
@ -2,12 +2,14 @@
|
||||
|
||||
[](https://crates.io/crates/esp-hal-embassy)
|
||||
[](https://docs.rs/esp-hal-embassy)
|
||||

|
||||

|
||||

|
||||
[](https://matrix.to/#/#esp-rs:matrix.org)
|
||||
|
||||
[Embassy] support for `esp-hal`.
|
||||
|
||||
Note that this crate currently requires you to enable the `unstable` feature on `esp-hal`.
|
||||
|
||||
[embassy]: https://github.com/embassy-rs/embassy
|
||||
|
||||
## [Documentation]
|
||||
@ -16,7 +18,7 @@
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
This crate is guaranteed to compile on stable Rust 1.76 and up. It _might_
|
||||
This crate is guaranteed to compile on stable Rust 1.83 and up. It _might_
|
||||
compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
@ -30,7 +30,7 @@ pub(crate) fn pend_thread_mode(_core: usize) {
|
||||
#[cfg(low_power_wait)]
|
||||
{
|
||||
// Signal that there is work to be done.
|
||||
SIGNAL_WORK_THREAD_MODE[_core].store(true, Ordering::SeqCst);
|
||||
SIGNAL_WORK_THREAD_MODE[_core].store(true, Ordering::Relaxed);
|
||||
|
||||
// If we are pending a task on the current core, we're done. Otherwise, we
|
||||
// need to make sure the other core wakes up.
|
||||
@ -123,7 +123,8 @@ This will use software-interrupt 3 which isn't available for anything else to wa
|
||||
|
||||
// we do not care about race conditions between the load and store operations,
|
||||
// interrupts will only set this value to true.
|
||||
if SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::SeqCst) {
|
||||
// Acquire makes no sense but at this time it's slightly faster than Relaxed.
|
||||
if SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::Acquire) {
|
||||
// if there is work to do, exit critical section and loop back to polling
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
@ -137,10 +138,18 @@ This will use software-interrupt 3 which isn't available for anything else to wa
|
||||
// sections in Xtensa are implemented via increasing `PS.INTLEVEL`.
|
||||
// The critical section ends here. Take care not add code after
|
||||
// `waiti` if it needs to be inside the CS.
|
||||
unsafe { core::arch::asm!("waiti 0") };
|
||||
// Do not lower INTLEVEL below the current value.
|
||||
match token & 0x0F {
|
||||
0 => unsafe { core::arch::asm!("waiti 0") },
|
||||
1 => unsafe { core::arch::asm!("waiti 1") },
|
||||
2 => unsafe { core::arch::asm!("waiti 2") },
|
||||
3 => unsafe { core::arch::asm!("waiti 3") },
|
||||
4 => unsafe { core::arch::asm!("waiti 4") },
|
||||
_ => unsafe { core::arch::asm!("waiti 5") },
|
||||
}
|
||||
}
|
||||
// If this races and some waker sets the signal, we'll reset it, but still poll.
|
||||
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::SeqCst);
|
||||
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(all(riscv, low_power_wait))]
|
||||
@ -149,14 +158,14 @@ This will use software-interrupt 3 which isn't available for anything else to wa
|
||||
// interrupts will only set this value to true.
|
||||
critical_section::with(|_| {
|
||||
// if there is work to do, loop back to polling
|
||||
if !SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::SeqCst) {
|
||||
if !SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::Relaxed) {
|
||||
// if not, wait for interrupt
|
||||
unsafe { core::arch::asm!("wfi") };
|
||||
}
|
||||
});
|
||||
// if an interrupt occurred while waiting, it will be serviced here
|
||||
// If this races and some waker sets the signal, we'll reset it, but still poll.
|
||||
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::SeqCst);
|
||||
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
//! systems. This package provides support for building applications using
|
||||
//! Embassy with [esp-hal].
|
||||
//!
|
||||
//! Note that this crate currently requires you to enable the `unstable` feature
|
||||
//! on `esp-hal`.
|
||||
//!
|
||||
//! [esp-hal]: https://github.com/esp-rs/esp-hal
|
||||
//! [embassy]: https://github.com/embassy-rs/embassy
|
||||
//!
|
||||
@ -75,20 +78,10 @@ trait IntoAnyTimer: Into<AnyTimer> {}
|
||||
|
||||
impl IntoAnyTimer for AnyTimer {}
|
||||
|
||||
impl<T, DM> IntoAnyTimer for TimgTimer<T, DM>
|
||||
where
|
||||
DM: esp_hal::Mode,
|
||||
Self: Into<AnyTimer>,
|
||||
{
|
||||
}
|
||||
impl IntoAnyTimer for TimgTimer where Self: Into<AnyTimer> {}
|
||||
|
||||
#[cfg(not(feature = "esp32"))]
|
||||
impl<T, DM, COMP, UNIT> IntoAnyTimer for Alarm<'_, T, DM, COMP, UNIT>
|
||||
where
|
||||
DM: esp_hal::Mode,
|
||||
Self: Into<AnyTimer>,
|
||||
{
|
||||
}
|
||||
impl IntoAnyTimer for Alarm where Self: Into<AnyTimer> {}
|
||||
|
||||
impl<T> TimerCollection for T
|
||||
where
|
||||
@ -156,7 +149,7 @@ impl_array!(4);
|
||||
///
|
||||
/// ```rust, no_run
|
||||
#[doc = esp_hal::before_snippet!()]
|
||||
/// use esp_hal::timg::TimerGroup;
|
||||
/// use esp_hal::timer::timg::TimerGroup;
|
||||
///
|
||||
/// let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
/// esp_hal_embassy::init(timg0.timer0);
|
||||
|
||||
@ -1,94 +1,160 @@
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::cell::Cell;
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_time_driver::{AlarmHandle, Driver};
|
||||
use esp_hal::{
|
||||
interrupt::{InterruptHandler, Priority},
|
||||
prelude::*,
|
||||
time::now,
|
||||
timer::{AnyTimer, OneShotTimer},
|
||||
sync::Locked,
|
||||
time::{now, ExtU64},
|
||||
timer::OneShotTimer,
|
||||
Blocking,
|
||||
};
|
||||
|
||||
pub const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
|
||||
pub type Timer = OneShotTimer<'static, Blocking>;
|
||||
|
||||
pub type Timer = OneShotTimer<'static, AnyTimer>;
|
||||
|
||||
static TIMERS: Mutex<RefCell<Option<&'static mut [Timer]>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct AlarmState {
|
||||
pub callback: Cell<Option<(fn(*mut ()), *mut ())>>,
|
||||
pub allocated: Cell<bool>,
|
||||
enum AlarmState {
|
||||
Created(extern "C" fn()),
|
||||
Allocated(extern "C" fn()),
|
||||
Initialized(&'static mut Timer),
|
||||
}
|
||||
impl AlarmState {
|
||||
fn initialize(timer: &'static mut Timer, interrupt_handler: extern "C" fn()) -> AlarmState {
|
||||
// If the driver is initialized, bind the interrupt handler to the
|
||||
// timer. This ensures that alarms allocated after init are correctly
|
||||
// bound to the core that created the executor.
|
||||
timer.set_interrupt_handler(InterruptHandler::new(interrupt_handler, Priority::max()));
|
||||
timer.enable_interrupt(true);
|
||||
AlarmState::Initialized(timer)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
struct AlarmInner {
|
||||
pub callback: Cell<(*const (), *mut ())>,
|
||||
pub state: AlarmState,
|
||||
}
|
||||
|
||||
impl AlarmState {
|
||||
pub const fn new() -> Self {
|
||||
struct Alarm {
|
||||
pub inner: Locked<AlarmInner>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Alarm {}
|
||||
|
||||
impl Alarm {
|
||||
pub const fn new(handler: extern "C" fn()) -> Self {
|
||||
Self {
|
||||
callback: Cell::new(None),
|
||||
allocated: Cell::new(false),
|
||||
inner: Locked::new(AlarmInner {
|
||||
callback: Cell::new((core::ptr::null(), core::ptr::null_mut())),
|
||||
state: AlarmState::Created(handler),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct EmbassyTimer {
|
||||
alarms: Mutex<[AlarmState; MAX_SUPPORTED_ALARM_COUNT]>,
|
||||
alarms: [Alarm; MAX_SUPPORTED_ALARM_COUNT],
|
||||
available_timers: Locked<Option<&'static mut [Timer]>>,
|
||||
}
|
||||
|
||||
/// Repeats the `Alarm::new` constructor for each alarm, creating an interrupt
|
||||
/// handler for each of them.
|
||||
macro_rules! alarms {
|
||||
($($idx:literal),*) => {
|
||||
[$(
|
||||
Alarm::new({
|
||||
// Not #[handler] so we don't have to store the priority - which is constant.
|
||||
extern "C" fn handler() {
|
||||
DRIVER.on_interrupt($idx);
|
||||
}
|
||||
handler
|
||||
})
|
||||
),*]
|
||||
};
|
||||
}
|
||||
|
||||
const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
|
||||
embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer {
|
||||
alarms: Mutex::new([const { AlarmState::new() }; MAX_SUPPORTED_ALARM_COUNT]),
|
||||
alarms: alarms!(0, 1, 2, 3, 4, 5, 6),
|
||||
available_timers: Locked::new(None),
|
||||
});
|
||||
|
||||
impl EmbassyTimer {
|
||||
pub(super) fn init(timers: &'static mut [Timer]) {
|
||||
if timers.len() > MAX_SUPPORTED_ALARM_COUNT {
|
||||
panic!(
|
||||
"Maximum of {} timers can be used.",
|
||||
pub(super) fn init(mut timers: &'static mut [Timer]) {
|
||||
assert!(
|
||||
timers.len() <= MAX_SUPPORTED_ALARM_COUNT,
|
||||
"Maximum {} timers can be used.",
|
||||
MAX_SUPPORTED_ALARM_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
critical_section::with(|cs| {
|
||||
timers.iter_mut().enumerate().for_each(|(n, timer)| {
|
||||
// Reset timers
|
||||
timers.iter_mut().for_each(|timer| {
|
||||
timer.enable_interrupt(false);
|
||||
timer.stop();
|
||||
});
|
||||
|
||||
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]);
|
||||
// Initialize already allocated timers
|
||||
for alarm in DRIVER.alarms.iter() {
|
||||
timers = alarm.inner.with(move |alarm| {
|
||||
if let AlarmState::Allocated(interrupt_handler) = alarm.state {
|
||||
// Pluck off a timer
|
||||
|
||||
let Some((timer, remaining_timers)) = timers.split_first_mut() else {
|
||||
not_enough_timers();
|
||||
};
|
||||
|
||||
alarm.state = AlarmState::initialize(timer, interrupt_handler);
|
||||
|
||||
remaining_timers
|
||||
} else {
|
||||
timers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TIMERS.replace(cs, Some(timers));
|
||||
});
|
||||
// Store the available timers
|
||||
DRIVER
|
||||
.available_timers
|
||||
.with(|available_timers| *available_timers = Some(timers));
|
||||
}
|
||||
|
||||
fn on_interrupt(&self, id: usize) {
|
||||
let cb = critical_section::with(|cs| {
|
||||
let mut timers = TIMERS.borrow_ref_mut(cs);
|
||||
let timers = unwrap!(timers.as_mut(), "Time driver not initialized");
|
||||
let timer = &mut timers[id];
|
||||
|
||||
let (cb, ctx) = self.alarms[id].inner.with(|alarm| {
|
||||
if let AlarmState::Initialized(timer) = &mut alarm.state {
|
||||
timer.clear_interrupt();
|
||||
|
||||
let alarm = &self.alarms.borrow(cs)[id];
|
||||
alarm.callback.get()
|
||||
} else {
|
||||
unsafe {
|
||||
// SAFETY: `on_interrupt` is registered right when the alarm is initialized.
|
||||
core::hint::unreachable_unchecked()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Some((f, ctx)) = cb {
|
||||
f(ctx);
|
||||
}
|
||||
let cb: fn(*mut ()) = unsafe {
|
||||
// Safety:
|
||||
// - we can ignore the possibility of `f` being unset (null) because of the
|
||||
// safety contract of `allocate_alarm`.
|
||||
// - other than that we only store valid function pointers into alarm.callback
|
||||
core::mem::transmute(cb)
|
||||
};
|
||||
|
||||
cb(ctx);
|
||||
}
|
||||
|
||||
fn arm(timer: &mut Timer, timestamp: u64) {
|
||||
/// Returns `true` if the timer was armed, `false` if the timestamp is in
|
||||
/// the past.
|
||||
fn arm(timer: &mut Timer, timestamp: u64) -> bool {
|
||||
let now = now().duration_since_epoch();
|
||||
let ts = timestamp.micros();
|
||||
// if the TS is already in the past make the timer fire immediately
|
||||
let timeout = if ts > now { ts - now } else { 0.micros() };
|
||||
|
||||
if ts > now {
|
||||
let timeout = ts - now;
|
||||
unwrap!(timer.schedule(timeout));
|
||||
timer.enable_interrupt(true);
|
||||
true
|
||||
} else {
|
||||
// If the timestamp is past, we return `false` to ask embassy to poll again
|
||||
// immediately.
|
||||
timer.stop();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,42 +164,60 @@ impl Driver for EmbassyTimer {
|
||||
}
|
||||
|
||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||
critical_section::with(|cs| {
|
||||
for (i, alarm) in self.alarms.borrow(cs).iter().enumerate() {
|
||||
if alarm.allocated.get() {
|
||||
continue;
|
||||
for (i, alarm) in self.alarms.iter().enumerate() {
|
||||
let handle = alarm.inner.with(|alarm| {
|
||||
let AlarmState::Created(interrupt_handler) = alarm.state else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let timer = self.available_timers.with(|available_timers| {
|
||||
// `allocate_alarm` may be called before `esp_hal_embassy::init()`. If
|
||||
// `timers` is `None`, we return `None` to signal that the alarm cannot be
|
||||
// initialized yet. These alarms will be initialized when `init` is called.
|
||||
if let Some(timers) = available_timers.take() {
|
||||
// If the driver is initialized, we can allocate a timer.
|
||||
// If this fails, we can't do anything about it.
|
||||
let Some((timer, rest)) = timers.split_first_mut() else {
|
||||
not_enough_timers();
|
||||
};
|
||||
*available_timers = Some(rest);
|
||||
Some(timer)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
alarm.state = match timer {
|
||||
Some(timer) => AlarmState::initialize(timer, interrupt_handler),
|
||||
|
||||
None => {
|
||||
// No timers are available yet, mark the alarm as allocated.
|
||||
AlarmState::Allocated(interrupt_handler)
|
||||
}
|
||||
};
|
||||
|
||||
Some(AlarmHandle::new(i as u8))
|
||||
});
|
||||
|
||||
if handle.is_some() {
|
||||
return handle;
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||
let n = alarm.id() as usize;
|
||||
critical_section::with(|cs| {
|
||||
let alarm = &self.alarms.borrow(cs)[n];
|
||||
alarm.callback.set(Some((callback, ctx)));
|
||||
|
||||
self.alarms[n].inner.with(|alarm| {
|
||||
alarm.callback.set((callback as *const (), ctx));
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||
let alarm = &self.alarms[alarm.id() as usize];
|
||||
|
||||
// If `embassy-executor/integrated-timers` is enabled and there are no pending
|
||||
// timers, embassy still calls `set_alarm` with `u64::MAX`. By returning
|
||||
// `true` we signal that no re-polling is necessary.
|
||||
@ -148,47 +232,22 @@ impl Driver for EmbassyTimer {
|
||||
// This is correct behavior. See https://docs.rs/embassy-time-driver/0.1.0/embassy_time_driver/trait.Driver.html#tymethod.set_alarm
|
||||
// (... the driver should return true and arrange to call the alarm callback as
|
||||
// soon as possible, but not synchronously.)
|
||||
critical_section::with(|cs| {
|
||||
let mut timers = TIMERS.borrow_ref_mut(cs);
|
||||
let timers = unwrap!(timers.as_mut(), "Time driver not initialized");
|
||||
let timer = &mut timers[alarm.id() as usize];
|
||||
|
||||
Self::arm(timer, timestamp);
|
||||
});
|
||||
|
||||
true
|
||||
alarm.inner.with(|alarm| {
|
||||
if let AlarmState::Initialized(timer) = &mut alarm.state {
|
||||
Self::arm(timer, timestamp)
|
||||
} else {
|
||||
panic!("set_alarm called before esp_hal_embassy::init()")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
#[cold]
|
||||
#[track_caller]
|
||||
fn not_enough_timers() -> ! {
|
||||
// This is wrapped in a separate function because rustfmt does not like
|
||||
// extremely long strings. Also, if log is used, this avoids storing the string
|
||||
// twice.
|
||||
panic!("There are not enough timers to allocate a new alarm. Call esp_hal_embassy::init() with the correct number of timers, or consider using one of the embassy-timer/generic-queue-X features.");
|
||||
}
|
||||
|
||||
@ -9,12 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Added the `BuilderLite` derive macro which implements the Builder Lite pattern for a struct (#2614)
|
||||
|
||||
### Fixed
|
||||
|
||||
### Changed
|
||||
|
||||
- Functions marked with `#[handler]` can now be referenced in `const` context. (#2559)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed the `enum-dispatch`, `interrupt`, and `ram` features (#2594)
|
||||
|
||||
## [0.15.0] - 2024-11-20
|
||||
|
||||
### Changed
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
name = "esp-hal-procmacros"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.76.0"
|
||||
rust-version = "1.83.0"
|
||||
description = "Procedural macros for esp-hal"
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@ -17,24 +17,18 @@ proc-macro = true
|
||||
darling = "0.20.10"
|
||||
document-features = "0.2.10"
|
||||
litrs = "0.4.1"
|
||||
object = { version = "0.36.5", optional = true, default-features = false, features = ["read_core", "elf"] }
|
||||
object = { version = "0.36.5", default-features = false, features = ["read_core", "elf"], optional = true }
|
||||
proc-macro-crate = "3.2.0"
|
||||
proc-macro-error2 = "2.0.1"
|
||||
proc-macro2 = "1.0.89"
|
||||
proc-macro2 = "1.0.92"
|
||||
quote = "1.0.37"
|
||||
syn = { version = "2.0.87", features = ["extra-traits", "full"] }
|
||||
syn = { version = "2.0.89", features = ["extra-traits", "full"] }
|
||||
|
||||
[features]
|
||||
## Provide a `#[main]` procmacro to mark the entry point for Embassy applications.
|
||||
embassy = []
|
||||
## Provide enum dispatch helpers.
|
||||
enum-dispatch = []
|
||||
## Provide an `#[interrupt]` procmacro for defining interrupt service routines.
|
||||
interrupt = []
|
||||
## Provide a `#[ram]` procmacro to place functions in RAM instead of flash.
|
||||
ram = []
|
||||
## Indicates the target device has RTC slow memory available.
|
||||
rtc_slow = []
|
||||
rtc-slow = []
|
||||
|
||||
#! ### Low-power Core Feature Flags
|
||||
## Indicate that the SoC contains an LP core.
|
||||
|
||||
@ -47,12 +47,32 @@
|
||||
#![doc = document_features::document_features!()]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
|
||||
|
||||
#[allow(unused)]
|
||||
use proc_macro::TokenStream;
|
||||
use darling::{ast::NestedMeta, Error, FromMeta};
|
||||
use proc_macro::{Span, TokenStream};
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use proc_macro_error2::abort;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse,
|
||||
parse::Error as ParseError,
|
||||
spanned::Spanned,
|
||||
Data,
|
||||
DataStruct,
|
||||
GenericArgument,
|
||||
Item,
|
||||
ItemFn,
|
||||
Path,
|
||||
PathArguments,
|
||||
PathSegment,
|
||||
ReturnType,
|
||||
Type,
|
||||
};
|
||||
|
||||
use self::interrupt::{check_attr_whitelist, WhiteListCaller};
|
||||
|
||||
#[cfg(feature = "embassy")]
|
||||
mod embassy;
|
||||
#[cfg(feature = "interrupt")]
|
||||
mod interrupt;
|
||||
#[cfg(any(
|
||||
feature = "is-lp-core",
|
||||
@ -62,7 +82,6 @@ mod interrupt;
|
||||
))]
|
||||
mod lp_core;
|
||||
|
||||
#[cfg(feature = "ram")]
|
||||
#[derive(Debug, Default, darling::FromMeta)]
|
||||
#[darling(default)]
|
||||
struct RamArgs {
|
||||
@ -80,7 +99,7 @@ struct RamArgs {
|
||||
/// # Options
|
||||
///
|
||||
/// - `rtc_fast`: Use RTC fast RAM.
|
||||
/// - `rtc_slow`: Use RTC slow RAM. **Note**: not available on all targets
|
||||
/// - `rtc_slow`: Use RTC slow RAM. **Note**: not available on all targets.
|
||||
/// - `persistent`: Persist the contents of the `static` across resets. See [the
|
||||
/// section below](#persistent) for details.
|
||||
/// - `zeroed`: Initialize the memory of the `static` to zero. The initializer
|
||||
@ -126,15 +145,9 @@ struct RamArgs {
|
||||
///
|
||||
/// [`bytemuck::AnyBitPattern`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.AnyBitPattern.html
|
||||
/// [`bytemuck::Zeroable`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.Zeroable.html
|
||||
#[cfg(feature = "ram")]
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use darling::{ast::NestedMeta, Error, FromMeta};
|
||||
use proc_macro::Span;
|
||||
use proc_macro_error2::abort;
|
||||
use syn::{parse, Item};
|
||||
|
||||
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@ -156,7 +169,7 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
|
||||
let item: Item = parse(input).expect("failed to parse input");
|
||||
|
||||
#[cfg(not(feature = "rtc_slow"))]
|
||||
#[cfg(not(feature = "rtc-slow"))]
|
||||
if rtc_slow {
|
||||
abort!(
|
||||
Span::call_site(),
|
||||
@ -206,7 +219,7 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let trait_check = trait_check.map(|name| {
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
|
||||
let hal = proc_macro2::Ident::new(
|
||||
let hal = Ident::new(
|
||||
if let Ok(FoundCrate::Name(ref name)) = crate_name("esp-hal") {
|
||||
name
|
||||
} else {
|
||||
@ -240,18 +253,9 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
/// esp_hal::interrupt::Priority::Priority2)]`.
|
||||
///
|
||||
/// If no priority is given, `Priority::min()` is assumed
|
||||
#[cfg(feature = "interrupt")]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use darling::{ast::NestedMeta, FromMeta};
|
||||
use proc_macro::Span;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use syn::{parse::Error as ParseError, spanned::Spanned, ItemFn, ReturnType, Type};
|
||||
|
||||
use self::interrupt::{check_attr_whitelist, WhiteListCaller};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct MacroArgs {
|
||||
priority: Option<syn::Expr>,
|
||||
@ -332,7 +336,7 @@ pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
#f
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#vis static #orig: #root::interrupt::InterruptHandler = #root::interrupt::InterruptHandler::new(#new, #priority);
|
||||
#vis const #orig: #root::interrupt::InterruptHandler = #root::interrupt::InterruptHandler::new(#new, #priority);
|
||||
)
|
||||
.into()
|
||||
}
|
||||
@ -352,8 +356,8 @@ pub fn load_lp_code(input: TokenStream) -> TokenStream {
|
||||
|
||||
/// Marks the entry function of a LP core / ULP program.
|
||||
#[cfg(any(feature = "is-lp-core", feature = "is-ulp-core"))]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
lp_core::entry(args, input)
|
||||
}
|
||||
@ -392,3 +396,129 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
run(&args.meta, f, main()).unwrap_or_else(|x| x).into()
|
||||
}
|
||||
|
||||
/// Automatically implement the [Builder Lite] pattern for a struct.
|
||||
///
|
||||
/// This will create an `impl` which contains methods for each field of a
|
||||
/// struct, allowing users to easily set the values. The generated methods will
|
||||
/// be the field name prefixed with `with_`, and calls to these methods can be
|
||||
/// chained as needed.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// #[derive(Default)]
|
||||
/// enum MyEnum {
|
||||
/// #[default]
|
||||
/// A,
|
||||
/// B,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Default, BuilderLite)]
|
||||
/// #[non_exhaustive]
|
||||
/// struct MyStruct {
|
||||
/// enum_field: MyEnum,
|
||||
/// bool_field: bool,
|
||||
/// option_field: Option<i32>,
|
||||
/// }
|
||||
///
|
||||
/// MyStruct::default()
|
||||
/// .with_enum_field(MyEnum::B)
|
||||
/// .with_bool_field(true)
|
||||
/// .with_option_field(-5);
|
||||
/// ```
|
||||
///
|
||||
/// [Builder Lite]: https://matklad.github.io/2022/05/29/builder-lite.html
|
||||
#[proc_macro_derive(BuilderLite)]
|
||||
pub fn builder_lite_derive(item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::DeriveInput);
|
||||
|
||||
let span = input.span();
|
||||
let ident = input.ident;
|
||||
|
||||
let mut fns = Vec::new();
|
||||
if let Data::Struct(DataStruct { fields, .. }) = &input.data {
|
||||
for field in fields {
|
||||
let field_ident = field.ident.as_ref().unwrap();
|
||||
let field_type = &field.ty;
|
||||
|
||||
let function_ident = format_ident!("with_{}", field_ident);
|
||||
|
||||
let maybe_path_type = extract_type_path(field_type)
|
||||
.and_then(|path| extract_option_segment(path))
|
||||
.and_then(|path_seg| match path_seg.arguments {
|
||||
PathArguments::AngleBracketed(ref params) => params.args.first(),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|generic_arg| match *generic_arg {
|
||||
GenericArgument::Type(ref ty) => Some(ty),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let (field_type, field_assigns) = if let Some(inner_type) = maybe_path_type {
|
||||
(inner_type, quote! { Some(#field_ident) })
|
||||
} else {
|
||||
(field_type, quote! { #field_ident })
|
||||
};
|
||||
|
||||
fns.push(quote! {
|
||||
#[doc = concat!(" Assign the given value to the `", stringify!(#field_ident) ,"` field.")]
|
||||
#[must_use]
|
||||
pub fn #function_ident(mut self, #field_ident: #field_type) -> Self {
|
||||
self.#field_ident = #field_assigns;
|
||||
self
|
||||
}
|
||||
});
|
||||
|
||||
if maybe_path_type.is_some() {
|
||||
let function_ident = format_ident!("with_{}_none", field_ident);
|
||||
fns.push(quote! {
|
||||
#[doc = concat!(" Set the value of `", stringify!(#field_ident), "` to `None`.")]
|
||||
#[must_use]
|
||||
pub fn #function_ident(mut self) -> Self {
|
||||
self.#field_ident = None;
|
||||
self
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ParseError::new(
|
||||
span,
|
||||
"#[derive(Builder)] is only defined for structs, not for enums or unions!",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let implementation = quote! {
|
||||
#[automatically_derived]
|
||||
impl #ident {
|
||||
#(#fns)*
|
||||
}
|
||||
};
|
||||
|
||||
implementation.into()
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/56264023
|
||||
fn extract_type_path(ty: &Type) -> Option<&Path> {
|
||||
match *ty {
|
||||
Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/56264023
|
||||
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
|
||||
let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| {
|
||||
acc.push_str(&v.ident.to_string());
|
||||
acc.push('|');
|
||||
acc
|
||||
});
|
||||
|
||||
vec!["Option|", "std|option|Option|", "core|option|Option|"]
|
||||
.into_iter()
|
||||
.find(|s| idents_of_path == *s)
|
||||
.and_then(|_| path.segments.last())
|
||||
}
|
||||
|
||||
1
esp-hal/.clippy.toml
Normal file
1
esp-hal/.clippy.toml
Normal file
@ -0,0 +1 @@
|
||||
avoid-breaking-exported-api = false
|
||||
@ -9,12 +9,131 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- ESP32-S3: Added SDMMC signals (#2556)
|
||||
- Added `set_priority` to the `DmaChannel` trait on GDMA devices (#2403, #2526)
|
||||
- Added `into_async` and `into_blocking` functions for `ParlIoTxOnly`, `ParlIoRxOnly` (#2526)
|
||||
- ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526, #2532)
|
||||
- DMA: `PeripheralDmaChannel` type aliasses and `DmaChannelFor` traits to improve usability. (#2532)
|
||||
- `dma::{Channel, ChannelRx, ChannelTx}::set_priority` for GDMA devices (#2403)
|
||||
- `esp_hal::asynch::AtomicWaker` that does not hold a global critical section (#2555)
|
||||
- `esp_hal::sync::RawMutex` for embassy-sync. (#2555)
|
||||
- ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526)
|
||||
- Added PSRAM configuration to `esp_hal::Config` if `quad-psram` or `octal-psram` is enabled (#2546)
|
||||
- Added `esp_hal::psram::psram_raw_parts` (#2546)
|
||||
- The timer drivers `OneShotTimer` & `PeriodicTimer` have `into_async` and `new_typed` methods (#2586)
|
||||
- `timer::Timer` trait has three new methods, `wait`, `async_interrupt_handler` and `peripheral_interrupt` (#2586)
|
||||
- Configuration structs in the I2C, SPI, and UART drivers now implement the Builder Lite pattern (#2614)
|
||||
- Added `I8080::apply_config`, `DPI::apply_config` and `Camera::apply_config` (#2610)
|
||||
- Introduced the `unstable` feature which will be used to restrict stable APIs to a subset of esp-hal. (#2628)
|
||||
- HAL configuration structs now implement the Builder Lite pattern (#2645)
|
||||
- Added `OutputOpenDrain::unlisten` (#2625)
|
||||
- Added `{Input, Flex}::wait_for` (#2625)
|
||||
- Peripheral singletons now implement `Debug` and `defmt::Format` (#2682, #2834)
|
||||
- `BurstConfig`, a device-specific configuration for configuring DMA transfers in burst mode (#2543)
|
||||
- `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543)
|
||||
- Added `SpiDmaBus::split` for moving between manual & automatic DMA buffers (#2824)
|
||||
- ESP32-S2: DMA support for AES (#2699)
|
||||
- Added `transfer_in_place_async` and embedded-hal-async implementation to `Spi` (#2691)
|
||||
- `InterruptHandler` now implements `Hash` and `defmt::Format` (#2830)
|
||||
- `uart::ConfigError` now implements `Eq` (#2825)
|
||||
- `i2c::master::Error` now implements `Eq` and `Hash` (#2825)
|
||||
- `i2c::master::Operation` now implements `Debug`, `PartialEq`, `Eq`, `Hash`, and `Display` (#2825)
|
||||
- `i2c::master::Config` now implements `PartialEq`, `Eq`, ans `Hash` (#2825)
|
||||
- `i2c::master::I2c` now implements `Debug`, `PartialEq`, and `Eq` (#2825)
|
||||
- `i2c::master::Info` now implements `Debug` (#2825)
|
||||
- `spi::master::Config` now implements `Hash` (#2823)
|
||||
- `spi::master` drivers now implement `Debug` and `defmt::Format` (#2823)
|
||||
- `DmaRxBuf`, `DmaTxBuf` and `DmaRxTxBuf` now implement `Debug` and `defmt::Format` (#2823)
|
||||
- DMA channels (`AnyGdmaChannel`, `SpiDmaChannel`, `I2sDmaChannel`, `CryptoDmaChannel`) and their RX/TX halves now implement `Debug` and `defmt::Format` (#2823)
|
||||
- `DmaDescriptor` and `DmaDescriptorFlags` now implement `PartialEq` and `Eq` (#2823)
|
||||
- `gpio::{Event, WakeEvent, GpioRegisterAccess}` now implement `Debug`, `Eq`, `PartialEq` and `Hash` (#2842)
|
||||
- `gpio::{Level, Pull, AlternateFunction, RtcFunction}` now implement `Hash` (#2842)
|
||||
- `gpio::{GpioPin, AnyPin, Io, Output, OutputOpenDrain, Input, Flex}` now implement `Debug`, `defmt::Format` (#2842)
|
||||
- More interrupts are available in `esp_hal::spi::master::SpiInterrupt`, add `enable_listen`,`interrupts` and `clear_interrupts` for ESP32/ESP32-S2 (#2833)
|
||||
- The `ExtU64` and `RateExtU32` traits have been added to `esp_hal::time` (#2845)
|
||||
- Added `AnyPin::steal(pin_number)` (#2854)
|
||||
- `adc::{AdcCalSource, Attenuation, Resolution}` now implement `Hash` and `defmt::Format` (#2840)
|
||||
- `rtc_cntl::{RtcFastClock, RtcSlowClock, RtcCalSel}` now implement `PartialEq`, `Eq`, `Hash` and `defmt::Format` (#2840)
|
||||
- Added `tsens::TemperatureSensor` peripheral for ESP32C6 and ESP32C3 (#2875)
|
||||
- Added `with_rx()` and `with_tx()` methods to Uart, UartRx, and UartTx ()
|
||||
|
||||
### Changed
|
||||
|
||||
- Bump MSRV to 1.83 (#2615)
|
||||
- In addition to taking by value, peripheral drivers can now mutably borrow DMA channel objects. (#2526)
|
||||
- DMA channel objects are no longer wrapped in `Channel`. The `Channel` drivers are now managed by DMA enabled peripheral drivers. (#2526)
|
||||
- The `Dpi` driver and `DpiTransfer` now have a `Mode` type parameter. The driver's asyncness is determined by the asyncness of the `Lcd` used to create it. (#2526)
|
||||
- `dma::{Channel, ChannelRx, ChannelTx}::set_priority` for GDMA devices (#2403)
|
||||
- `SystemTimer::set_unit_value` & `SystemTimer::configure_unit` (#2576)
|
||||
- `SystemTimer` no longer uses peripheral ref (#2576)
|
||||
- `TIMGX` no longer uses peripheral ref (#2581)
|
||||
- `SystemTimer::now` has been renamed `SystemTimer::unit_value(Unit)` (#2576)
|
||||
- `SpiDma` transfers now explicitly take a length along with the DMA buffer object (#2587)
|
||||
- `dma::{Channel, ChannelRx, ChannelTx}::set_priority` for GDMA devices (#2403)
|
||||
- `SystemTimer`s `Alarm`s are now type erased (#2576)
|
||||
- `TimerGroup` `Timer`s are now type erased (#2581)
|
||||
- PSRAM is now initialized automatically if `quad-psram` or `octal-psram` is enabled (#2546)
|
||||
- DMA channels are now available via the `Peripherals` struct, and have been renamed accordingly. (#2545)
|
||||
- Moved interrupt related items from lib.rs, moved to the `interrupt` module (#2613)
|
||||
- The timer drivers `OneShotTimer` & `PeriodicTimer` now have a `Mode` parameter and type erase the underlying driver by default (#2586)
|
||||
- `timer::Timer` has new trait requirements of `Into<AnyTimer>`, `'static` and `InterruptConfigurable` (#2586)
|
||||
- `systimer::etm::Event` no longer borrows the alarm indefinitely (#2586)
|
||||
- A number of public enums and structs in the I2C, SPI, and UART drivers have been marked with `#[non_exhaustive]` (#2614)
|
||||
- Interrupt handling related functions are only provided for Blocking UART. (#2610)
|
||||
- Changed how `Spi`, (split or unsplit) `Uart`, `LpUart`, `I8080`, `Camera`, `DPI` and `I2C` drivers are constructed (#2610)
|
||||
- I8080, camera, DPI: The various standalone configuration options have been merged into `Config` (#2610)
|
||||
- Dropped GPIO futures stop listening for interrupts (#2625)
|
||||
- UART driver's `StopBits` enum variants now correctly use UpperCamelCase (#2669)
|
||||
- The `PeripheralInput` and `PeripheralOutput` traits are now sealed (#2690)
|
||||
- `esp_hal::sync::Lock` has been renamed to RawMutex (#2684)
|
||||
- Updated `esp-pacs` with support for Wi-Fi on the ESP32 and made the peripheral non virtual
|
||||
- `SpiBitOrder`, `SpiDataMode`, `SpiMode` were renamed to `BitOder`, `DataMode` and `Mode` (#2828)
|
||||
- `crate::Mode` was renamed to `crate::DriverMode` (#2828)
|
||||
- `Spi::with_miso` has been overloaded into `Spi::with_miso` and `Spi::with_sio1` (#2557)
|
||||
- Renamed some I2C error variants (#2844)
|
||||
- I2C: Replaced potential panics with errors. (#2831)
|
||||
- UART: Make `AtCmdConfig` and `ConfigError` non-exhaustive (#2851)
|
||||
- UART: Make `AtCmdConfig` use builder-lite pattern (#2851)
|
||||
- UART: Fix naming violations for `DataBits`, `Parity`, and `StopBits` enum variants (#2893)
|
||||
- UART: Remove blocking version of `read_bytes` and rename `drain_fifo` to `read_bytes` instead (#2895)
|
||||
- Renamed variants of `CpuClock`, made the enum non-exhaustive (#2899)
|
||||
- SPI: Fix naming violations for `Mode` enum variants (#2902)
|
||||
- SPI: Fix naming violations for `Address` and `Command` enum variants (#2906)
|
||||
|
||||
- `ClockSource` enums are now `#[non_exhaustive]` (#2912)
|
||||
|
||||
- `gpio::{Input, Flex}::wakeup_enable` now returns an error instead of panicking. (#2916)
|
||||
|
||||
- Removed the `I` prefix from `DriveStrength` enum variants. (#2922)
|
||||
- Removed the `Attenuation` prefix from `Attenuation` enum variants. (#2922)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Xtensa devices now correctly enable the `esp-hal-procmacros/rtc-slow` feature (#2594)
|
||||
- User-bound GPIO interrupt handlers should no longer interfere with async pins. (#2625)
|
||||
- `spi::master::Spi::{into_async, into_blocking}` are now correctly available on the typed driver, to. (#2674)
|
||||
- It is no longer possible to safely conjure `GpioPin` instances (#2688)
|
||||
- UART: Public API follows `C-WORD_ORDER` Rust API standard (`VerbObject` order) (#2851)
|
||||
- `DmaRxStreamBuf` now correctly resets the descriptors the next time it's used (#2890)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove more examples. Update doctests. (#2547)
|
||||
- The `configure` and `configure_for_async` DMA channel functions has been removed (#2403)
|
||||
- The DMA channel objects no longer have `tx` and `rx` fields. (#2526)
|
||||
- `SysTimerAlarms` has been removed, alarms are now part of the `SystemTimer` struct (#2576)
|
||||
- `FrozenUnit`, `AnyUnit`, `SpecificUnit`, `SpecificComparator`, `AnyComparator` have been removed from `systimer` (#2576)
|
||||
- Remove Dma[Rx|Tx]Buffer::length (#2587)
|
||||
- `esp_hal::psram::psram_range` (#2546)
|
||||
- The `Dma` structure has been removed. (#2545)
|
||||
- Removed `embedded-hal 0.2.x` impls and deps from `esp-hal` (#2593)
|
||||
- Removed `Camera::set_` functions (#2610)
|
||||
- `DmaTxBuf::{compute_chunk_size, compute_descriptor_count, new_with_block_size}` (#2543)
|
||||
|
||||
- The `prelude` module has been removed (#2845)
|
||||
|
||||
- Removed all peripheral instance type parameters and `new_typed` constructors (#2907)
|
||||
|
||||
## [0.22.0] - 2024-11-20
|
||||
|
||||
### Added
|
||||
@ -78,6 +197,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- 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)
|
||||
- Many peripherals are now disabled by default and also get disabled when the driver is dropped (#2544)
|
||||
|
||||
- Config: Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848)
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -122,6 +244,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `parl_io::{no_clk_pin(), NoClkPin}` (#2531)
|
||||
- Removed `get_core` function in favour of `Cpu::current` (#2533)
|
||||
|
||||
- Removed `uart::Config` setters and `symbol_length`. (#2847)
|
||||
|
||||
## [0.21.1]
|
||||
|
||||
### Fixed
|
||||
@ -131,8 +255,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [0.21.0]
|
||||
|
||||
- Bump MSRV to 1.79.0 (#1971)
|
||||
|
||||
### Added
|
||||
|
||||
- Introduce traits for the DMA buffer objects (#1976, #2213)
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
name = "esp-hal"
|
||||
version = "0.22.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.79.0"
|
||||
rust-version = "1.83.0"
|
||||
description = "Bare-metal HAL for Espressif devices"
|
||||
documentation = "https://docs.esp-rs.org/esp-hal/"
|
||||
keywords = ["embedded", "embedded-hal", "esp32", "espressif", "hal"]
|
||||
categories = ["embedded", "hardware-support", "no-std"]
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
links = "esp-hal"
|
||||
@ -25,50 +27,53 @@ defmt = { version = "0.3.8", optional = true }
|
||||
delegate = "0.13.1"
|
||||
digest = { version = "0.10.7", default-features = false, optional = true }
|
||||
document-features = "0.2.10"
|
||||
embassy-embedded-hal = "0.2.0"
|
||||
embassy-embedded-hal = { version = "0.2.0", optional = true }
|
||||
embassy-futures = "0.1.1"
|
||||
embassy-sync = "0.6.0"
|
||||
embassy-sync = "0.6.1"
|
||||
embassy-usb-driver = { version = "0.1.0", optional = true }
|
||||
embassy-usb-synopsys-otg = { version = "0.1.0", optional = true }
|
||||
embedded-can = "0.4.1"
|
||||
embedded-hal-02 = { version = "0.2.7", features = ["unproven"], package = "embedded-hal" }
|
||||
embassy-usb-synopsys-otg = { version = "0.2.0", optional = true }
|
||||
embedded-can = { version = "0.4.1", optional = true }
|
||||
embedded-hal = "1.0.0"
|
||||
embedded-hal-async = "1.0.0"
|
||||
embedded-hal-nb = "1.0.0"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
embedded-io = { version = "0.6.1", optional = true }
|
||||
embedded-io-async = { version = "0.6.1", optional = true }
|
||||
enumset = "1.1.5"
|
||||
esp-build = { version = "0.1.0", path = "../esp-build" }
|
||||
esp-synopsys-usb-otg = { version = "0.4.2", optional = true, features = ["fs", "esp32sx"] }
|
||||
fugit = "0.3.7"
|
||||
instability = "0.3.6"
|
||||
log = { version = "0.4.22", optional = true }
|
||||
nb = "1.1.0"
|
||||
paste = "1.0.15"
|
||||
portable-atomic = { version = "1.9.0", default-features = false }
|
||||
procmacros = { version = "0.15.0", features = ["enum-dispatch", "interrupt", "ram"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
|
||||
riscv = { version = "0.12.1", optional = true }
|
||||
procmacros = { version = "0.15.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
|
||||
strum = { version = "0.26.3", default-features = false, features = ["derive"] }
|
||||
void = { version = "1.0.2", default-features = false }
|
||||
usb-device = { version = "0.3.2", optional = true }
|
||||
rand_core = "0.6.4"
|
||||
ufmt-write = "0.1.0"
|
||||
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.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 }
|
||||
esp32 = { version = "0.34.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
esp32c2 = { version = "0.23.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
esp32c3 = { version = "0.26.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
esp32c6 = { version = "0.17.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
esp32h2 = { version = "0.13.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
esp32s2 = { version = "0.25.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
esp32s3 = { version = "0.29.0", features = ["critical-section", "rt"], git = "https://github.com/esp-rs/esp-pacs", rev = "ffbee35069d137ef611097d39fa7734c299ce124", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "riscv32")'.dependencies]
|
||||
riscv = { version = "0.12.1" }
|
||||
esp-riscv-rt = { version = "0.9.1", path = "../esp-riscv-rt" }
|
||||
critical-section = { version = "1.2.0", features = ["restore-state-u8"] }
|
||||
|
||||
[target.'cfg(target_arch = "xtensa")'.dependencies]
|
||||
xtensa-lx = { version = "0.9.0", path = "../xtensa-lx" }
|
||||
xtensa-lx-rt = { version = "0.17.2", path = "../xtensa-lx-rt" }
|
||||
critical-section = { version = "1.2.0", features = ["restore-state-u32"] }
|
||||
|
||||
[build-dependencies]
|
||||
basic-toml = "0.1.9"
|
||||
@ -81,9 +86,6 @@ serde = { version = "1.0.215", features = ["derive"] }
|
||||
[features]
|
||||
default = []
|
||||
|
||||
riscv = ["dep:riscv", "critical-section/restore-state-u8"]
|
||||
xtensa = ["dep:xtensa-lx", "critical-section/restore-state-u32"]
|
||||
|
||||
bluetooth = []
|
||||
|
||||
usb-otg = ["dep:embassy-usb-driver", "dep:embassy-usb-synopsys-otg", "dep:esp-synopsys-usb-otg", "dep:usb-device"]
|
||||
@ -105,19 +107,19 @@ log = ["dep:log"]
|
||||
|
||||
# Chip Support Feature Flags
|
||||
# Target the ESP32.
|
||||
esp32 = ["dep:esp32", "xtensa", "xtensa-lx/spin", "xtensa-lx-rt/esp32"]
|
||||
esp32 = ["dep:esp32", "procmacros/rtc-slow", "xtensa-lx-rt/esp32"]
|
||||
# Target the ESP32-C2.
|
||||
esp32c2 = ["dep:esp32c2", "riscv", "portable-atomic/unsafe-assume-single-core"]
|
||||
esp32c2 = ["dep:esp32c2", "portable-atomic/unsafe-assume-single-core"]
|
||||
# Target the ESP32-C3.
|
||||
esp32c3 = ["dep:esp32c3", "riscv", "portable-atomic/unsafe-assume-single-core", "esp-riscv-rt/rtc-ram"]
|
||||
esp32c3 = ["dep:esp32c3", "esp-riscv-rt/rtc-ram", "portable-atomic/unsafe-assume-single-core"]
|
||||
# Target the ESP32-C6.
|
||||
esp32c6 = ["dep:esp32c6", "riscv", "procmacros/has-lp-core", "esp-riscv-rt/rtc-ram"]
|
||||
esp32c6 = ["dep:esp32c6", "esp-riscv-rt/rtc-ram", "procmacros/has-lp-core"]
|
||||
# Target the ESP32-H2.
|
||||
esp32h2 = ["dep:esp32h2", "riscv", "esp-riscv-rt/rtc-ram"]
|
||||
esp32h2 = ["dep:esp32h2", "esp-riscv-rt/rtc-ram"]
|
||||
# Target the ESP32-S2.
|
||||
esp32s2 = ["dep:esp32s2", "xtensa", "portable-atomic/critical-section", "procmacros/has-ulp-core", "xtensa-lx-rt/esp32s2", "usb-otg"]
|
||||
esp32s2 = ["dep:esp32s2", "portable-atomic/critical-section", "procmacros/has-ulp-core", "procmacros/rtc-slow", "usb-otg", "xtensa-lx-rt/esp32s2"]
|
||||
# Target the ESP32-S3.
|
||||
esp32s3 = ["dep:esp32s3", "xtensa", "procmacros/has-ulp-core", "xtensa-lx/spin", "xtensa-lx-rt/esp32s3", "usb-otg"]
|
||||
esp32s3 = ["dep:esp32s3", "procmacros/has-ulp-core", "procmacros/rtc-slow", "usb-otg", "xtensa-lx-rt/esp32s3"]
|
||||
|
||||
#! ### RISC-V Exclusive Feature Flags
|
||||
## Move the stack to start of RAM to get zero-cost stack overflow protection
|
||||
@ -153,6 +155,19 @@ octal-psram = []
|
||||
# This feature is intended for testing; you probably don't want to enable it:
|
||||
ci = ["defmt", "bluetooth"]
|
||||
|
||||
#! ### Unstable APIs
|
||||
#! Unstable APIs are drivers and features that are not yet ready for general use.
|
||||
#! They may be incomplete, have bugs, or be subject to change without notice.
|
||||
#! Unstable APIs are not covered by semver guarantees.
|
||||
|
||||
## Enables APIs that are not stable and thus come with no stability guarantees.
|
||||
unstable = [
|
||||
"dep:embassy-embedded-hal",
|
||||
"dep:embedded-can",
|
||||
"dep:embedded-io",
|
||||
"dep:embedded-io-async",
|
||||
]
|
||||
|
||||
[lints.clippy]
|
||||
mixed_attributes_style = "allow"
|
||||
|
||||
|
||||
@ -99,8 +99,8 @@ 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);
|
||||
+I2c::new_with_config(
|
||||
-let i2c = I2c::new_with_timeout(peripherals.I2C0, sda, scl, 100.kHz(), timeout);
|
||||
+let i2c = I2c::new(
|
||||
+ peripherals.I2C0,
|
||||
+ {
|
||||
+ let mut config = Config::default();
|
||||
@ -109,8 +109,8 @@ The constructors no longer take pins. Use `with_sda` and `with_scl` instead.
|
||||
+ config
|
||||
+ },
|
||||
+)
|
||||
+.with_sda(io.pins.gpio4)
|
||||
+.with_scl(io.pins.gpio5);
|
||||
+.with_sda(sda)
|
||||
+.with_scl(scl);
|
||||
```
|
||||
|
||||
### The calculation of I2C timeout has changed
|
||||
@ -137,7 +137,7 @@ drivers. It is now possible to execute half-duplex and full-duplex operations on
|
||||
```diff
|
||||
- let mut spi = Spi::new_half_duplex(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
|
||||
- .with_pins(sck, mosi, miso, sio2, sio3, cs);
|
||||
+ let mut spi = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
|
||||
+ let mut spi = Spi::new(peripherals.SPI2) // or new_with_config
|
||||
+ .with_sck(sck)
|
||||
+ .with_cs(cs)
|
||||
+ .with_mosi(mosi)
|
||||
|
||||
478
esp-hal/MIGRATING-0.22.md
Normal file
478
esp-hal/MIGRATING-0.22.md
Normal file
@ -0,0 +1,478 @@
|
||||
# Migration Guide from 0.22.x to v1.0.0-beta.0
|
||||
|
||||
Starting with this release, unstable parts of esp-hal will be gated behind the `unstable` feature.
|
||||
The `unstable` feature itself is unstable, we might change the way we hide APIs without notice.
|
||||
Unstable APIs are not covered by semver guarantees, they may break even between patch releases.
|
||||
|
||||
Please refer to the documentation to see which APIs are marked as unstable.
|
||||
|
||||
## DMA changes
|
||||
|
||||
### Accessing channel objects
|
||||
|
||||
DMA channels are now available through the `Peripherals` struct, which is returned
|
||||
by `esp_hal::init()`. The channels themselves have been renamed to match other peripheral singletons.
|
||||
|
||||
- ESP32-C2, C3, C6, H2 and S3: `channelX -> DMA_CHX`
|
||||
- ESP32 and S2: `spiXchannel -> DMA_SPIX`, `i2sXchannel -> DMA_I2SX`
|
||||
|
||||
```diff
|
||||
-let dma = Dma::new(peripherals.DMA);
|
||||
-let channel = dma.channel2;
|
||||
+let channel = peripherals.DMA_CH2;
|
||||
```
|
||||
|
||||
### Channel configuration changes
|
||||
|
||||
- `configure_for_async` and `configure` have been removed
|
||||
- PDMA devices (ESP32, ESP32-S2) provide no channel configurability
|
||||
- GDMA devices provide `set_priority` to change DMA in/out channel priority
|
||||
|
||||
```diff
|
||||
let mut spi = Spi::new_with_config(
|
||||
peripherals.SPI2,
|
||||
Config::default(),
|
||||
)
|
||||
// other setup
|
||||
-.with_dma(dma_channel.configure(false, DmaPriority::Priority0));
|
||||
+.with_dma(dma_channel);
|
||||
```
|
||||
|
||||
```diff
|
||||
+dma_channel.set_priority(DmaPriority::Priority1);
|
||||
let mut spi = Spi::new_with_config(
|
||||
peripherals.SPI2,
|
||||
Config::default(),
|
||||
)
|
||||
// other setup
|
||||
-.with_dma(dma_channel.configure(false, DmaPriority::Priority1));
|
||||
+.with_dma(dma_channel);
|
||||
```
|
||||
|
||||
### Burst mode configuration
|
||||
|
||||
Burst mode is now a property of buffers, instead of DMA channels. Configuration can be done by
|
||||
calling `set_burst_config` on buffers that support it. The configuration options and the
|
||||
corresponding `BurstConfig` type are device specfic.
|
||||
|
||||
### Usability changes affecting applications
|
||||
|
||||
Individual channels are no longer wrapped in `Channel`, but they implement the `DmaChannel` trait.
|
||||
This means that if you want to split them into an `rx` and a `tx` half (which is only supported on
|
||||
the H2, C6 and S3 currently), you can't move out of the channel but instead you need to call
|
||||
the `split` method.
|
||||
|
||||
```diff
|
||||
-let tx = channel.tx;
|
||||
+use esp_hal::dma::DmaChannel;
|
||||
+let (rx, tx) = channel.split();
|
||||
```
|
||||
|
||||
The `Channel` types remain available for use in peripheral drivers.
|
||||
|
||||
It is now simpler to work with DMA channels in generic contexts. esp-hal now provides convenience
|
||||
traits and type aliasses to specify peripheral compatibility. The `ChannelCreator` types have been
|
||||
removed, further simplifying use.
|
||||
|
||||
For example, previously you may have needed to write something like this to accept a DMA channel
|
||||
in a generic function:
|
||||
|
||||
```rust
|
||||
fn new_foo<'d, T>(
|
||||
dma_channel: ChannelCreator<2>, // It wasn't possible to accept a generic ChannelCreator.
|
||||
peripheral: impl Peripheral<P = T> + 'd,
|
||||
)
|
||||
where
|
||||
T: SomePeripheralInstance,
|
||||
ChannelCreator<2>: DmaChannelConvert<<T as DmaEligible>::Dma>,
|
||||
{
|
||||
let dma_channel = dma_channel.configure_for_async(false, DmaPriority::Priority0);
|
||||
|
||||
let driver = PeripheralDriver::new(peripheral, config).with_dma(dma_channel);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
From now on a similar, but more flexible implementation may look like:
|
||||
|
||||
```rust
|
||||
fn new_foo<'d, T, CH>(
|
||||
dma_channel: impl Peripheral<P = CH> + 'd,
|
||||
peripheral: impl Peripheral<P = T> + 'd,
|
||||
)
|
||||
where
|
||||
T: SomePeripheralInstance,
|
||||
CH: DmaChannelFor<T>,
|
||||
{
|
||||
// Optionally: dma_channel.set_priority(DmaPriority::Priority2);
|
||||
|
||||
let driver = PeripheralDriver::new(peripheral, config).with_dma(dma_channel);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Usability changes affecting third party peripheral drivers
|
||||
|
||||
If you are writing a driver and need to store a channel in a structure, you can use one of the
|
||||
`ChannelFor` type aliasses.
|
||||
|
||||
```diff
|
||||
struct Aes<'d> {
|
||||
- channel: ChannelTx<'d, Blocking, <AES as DmaEligible>::Dma>,
|
||||
+ channel: ChannelTx<'d, Blocking, PeripheralTxChannel<AES>>,
|
||||
}
|
||||
```
|
||||
|
||||
## Timer changes
|
||||
|
||||
The low level timers, `SystemTimer` and `TimerGroup` are now "dumb". They contain no logic for operating modes or trait implementations (except the low level `Timer` trait).
|
||||
|
||||
### Timer drivers - `OneShotTimer` & `PeriodicTimer`
|
||||
|
||||
Both drivers now have a `Mode` parameter. Both also type erase the underlying driver by default, call `new_typed` to retain the type.
|
||||
|
||||
```diff
|
||||
- OneShotTimer<'static, systimer::Alarm>;
|
||||
+ OneShotTimer<'static, Blocking>;
|
||||
- PeriodicTimer<'static, systimer::Alarm>;
|
||||
+ PeriodicTimer<'static, Blocking>;
|
||||
```
|
||||
|
||||
### SystemTimer
|
||||
|
||||
```diff
|
||||
let systimer = SystemTimer::new(peripherals.SYSTIMER);
|
||||
- static UNIT0: StaticCell<SpecificUnit<'static, 0>> = StaticCell::new();
|
||||
- let unit0 = UNIT0.init(systimer.unit0);
|
||||
- let frozen_unit = FrozenUnit::new(unit0);
|
||||
- let alarm0 = Alarm::new(systimer.comparator0, &frozen_unit);
|
||||
- alarm0.set_period(1u32.secs());
|
||||
+ let alarm0 = systimer.alarm0;
|
||||
+ let mut timer = PeriodicTimer::new(alarm0);
|
||||
+ timer.start(1u64.secs());
|
||||
```
|
||||
|
||||
### TIMG
|
||||
|
||||
Timer group timers have been type erased.
|
||||
|
||||
```diff
|
||||
- timg::Timer<timg::Timer0<crate::peripherals::TIMG0>, Blocking>
|
||||
+ timg::Timer
|
||||
```
|
||||
|
||||
### ETM usage has changed
|
||||
|
||||
Timer dependant ETM events should be created _prior_ to initializing the timer with the chosen driver.
|
||||
|
||||
```diff
|
||||
let task = ...; // ETM task
|
||||
let syst = SystemTimer::new(peripherals.SYSTIMER);
|
||||
let alarm0 = syst.alarm0;
|
||||
- alarm0.load_value(1u64.millis()).unwrap();
|
||||
- alarm0.start();
|
||||
- let event = Event::new(&mut alarm0);
|
||||
+ let event = Event::new(&alarm0);
|
||||
+ let timer = OneShotTimer::new(alarm0);
|
||||
+ timer.schedule(1u64.millis()).unwrap();
|
||||
let _configured_channel = channel0.setup(&event, &task);
|
||||
```
|
||||
|
||||
## PSRAM is now initialized automatically
|
||||
|
||||
Calling `esp_hal::initialize` will now configure PSRAM if either the `quad-psram` or `octal-psram`
|
||||
is enabled. To retrieve the address and size of the initialized external memory, use
|
||||
`esp_hal::psram::psram_raw_parts`, which returns a pointer and a length.
|
||||
|
||||
```diff
|
||||
-let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
-let (start, size) = esp_hal::psram::init_psram(peripherals.PSRAM, psram_config);
|
||||
+let peripherals = esp_hal::init({
|
||||
+ let mut config = esp_hal::Config::default();
|
||||
+ config.psram = psram_config;
|
||||
+ config
|
||||
+});
|
||||
+let (start, size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM, psram);
|
||||
```
|
||||
|
||||
The usage of `esp_alloc::psram_allocator!` remains unchanged.
|
||||
|
||||
## embedded-hal 0.2.\* is not supported anymore.
|
||||
|
||||
As per https://github.com/rust-embedded/embedded-hal/pull/640, our driver no longer implements traits from `embedded-hal 0.2.x`.
|
||||
Analogs of all traits from the above mentioned version are available in `embedded-hal 1.x.x`
|
||||
|
||||
```diff
|
||||
- use embedded_hal_02::can::Frame;
|
||||
+ use embedded_can::Frame;
|
||||
```
|
||||
|
||||
```diff
|
||||
- use embedded_hal_02::digital::v2::OutputPin;
|
||||
- use embedded_hal_02::digital::v2::ToggleableOutputPin;
|
||||
+ use embedded_hal::digital::OutputPin;
|
||||
+ use embedded_hal::digital::StatefulOutputPin;
|
||||
```
|
||||
|
||||
```diff
|
||||
- use embedded_hal_02::serial::{Read, Write};
|
||||
+ use embedded_hal_nb::serial::{Read, Write};
|
||||
```
|
||||
|
||||
You might also want to check the full official `embedded-hal` migration guide:
|
||||
https://github.com/rust-embedded/embedded-hal/blob/master/docs/migrating-from-0.2-to-1.0.md
|
||||
|
||||
## Interrupt related reshuffle
|
||||
|
||||
```diff
|
||||
- use esp_hal::InterruptConfigurable;
|
||||
- use esp_hal::DEFAULT_INTERRUPT_HANDLER;
|
||||
+ use esp_hal::interrupt::InterruptConfigurable;
|
||||
+ use esp_hal::interrupt::DEFAULT_INTERRUPT_HANDLER;
|
||||
```
|
||||
|
||||
## Driver constructors now take a configuration and are fallible
|
||||
|
||||
The old `new_with_config` constructor have been removed, and `new` constructors now always take
|
||||
a configuration structure. They have also been updated to return a `ConfigError` if the configuration
|
||||
is not compatible with the hardware.
|
||||
|
||||
```diff
|
||||
-let mut spi = Spi::new_with_config(
|
||||
+let mut spi = Spi::new(
|
||||
peripherals.SPI2,
|
||||
Config {
|
||||
frequency: 100.kHz(),
|
||||
mode: SpiMode::_0,
|
||||
..Config::default()
|
||||
},
|
||||
-);
|
||||
+)
|
||||
+.unwrap();
|
||||
```
|
||||
|
||||
```diff
|
||||
let mut spi = Spi::new(
|
||||
peripherals.SPI2,
|
||||
+ Config::default(),
|
||||
-);
|
||||
+)
|
||||
+.unwrap();
|
||||
```
|
||||
|
||||
## Peripheral instance type parameters and `new_typed` constructors have been removed
|
||||
|
||||
Call `new` instead and remove the type parameters if you've used them.
|
||||
|
||||
```diff
|
||||
-let mut spi: Spi<'lt, SPI2> = Spi::new_typed(..).unwrap();
|
||||
+let mut spi: Spi<'lt> = Spi::new(..).unwrap();
|
||||
```
|
||||
|
||||
## LCD_CAM configuration changes
|
||||
|
||||
- `cam` now has a `Config` strurct that contains frequency, bit/byte order, VSync filter options.
|
||||
- DPI, I8080: `frequency` has been moved into `Config`.
|
||||
|
||||
```diff
|
||||
+let mut cam_config = cam::Config::default();
|
||||
+cam_config.frequency = 1u32.MHz();
|
||||
cam::Camera::new(
|
||||
lcd_cam.cam,
|
||||
dma_rx_channel,
|
||||
pins,
|
||||
- 1u32.MHz(),
|
||||
+ cam_config,
|
||||
)
|
||||
```
|
||||
|
||||
## SpiDma now requires you specify the transfer length explicitly
|
||||
|
||||
```diff
|
||||
dma_tx_buf.set_length(5 /* or greater */);
|
||||
- spi_dma.write(dma_tx_buf);
|
||||
+ spi_dma.write(5, dma_tx_buf);
|
||||
```
|
||||
|
||||
```diff
|
||||
dma_rx_buf.set_length(5 /* or greater */);
|
||||
- spi_dma.read(dma_rx_buf);
|
||||
+ spi_dma.read(5, dma_rx_buf);
|
||||
```
|
||||
|
||||
```diff
|
||||
dma_rx_buf.set_length(5 /* or greater */);
|
||||
dma_tx_buf.set_length(5 /* or greater */);
|
||||
- spi_dma.transfer(dma_rx_buf, dma_tx_buf);
|
||||
+ spi_dma.transfer(5, dma_rx_buf, 5, dma_tx_buf);
|
||||
```
|
||||
|
||||
## I2C Error changes
|
||||
|
||||
To avoid abbreviations and contractions (as per the esp-hal guidelines), some error variants have changed
|
||||
|
||||
```diff
|
||||
- Error::ExecIncomplete
|
||||
+ Error::ExecutionIncomplete
|
||||
- Error::CommandNrExceeded
|
||||
+ Error::CommandNumberExceeded
|
||||
- Error::ExceedingFifo
|
||||
+ Error::FifoExceeded
|
||||
- Error::TimeOut
|
||||
+ Error::Timeout
|
||||
- Error::InvalidZeroLength
|
||||
+ Error::ZeroLengthInvalid
|
||||
```
|
||||
|
||||
## The crate prelude has been removed
|
||||
|
||||
The reexports that were previously part of the prelude are available through other paths:
|
||||
|
||||
- `nb` is no longer re-exported. Please import the `nb` crate if you need it.
|
||||
- `ExtU64` and `RateExtU32` have been moved to `esp_hal::time`
|
||||
- `Clock` and `CpuClock`: `esp_hal::clock::{Clock, CpuClock}`
|
||||
- The following traits need to be individually imported when needed:
|
||||
- `esp_hal::analog::dac::Instance`
|
||||
- `esp_hal::gpio::Pin`
|
||||
- `esp_hal::ledc::channel::ChannelHW`
|
||||
- `esp_hal::ledc::channel::ChannelIFace`
|
||||
- `esp_hal::ledc::timer::TimerHW`
|
||||
- `esp_hal::ledc::timer::TimerIFace`
|
||||
- `esp_hal::timer::timg::TimerGroupInstance`
|
||||
- `esp_hal::timer::Timer`
|
||||
- `esp_hal::interrupt::InterruptConfigurable`
|
||||
- The `entry` macro can be imported as `esp_hal::entry`, while other macros are found under `esp_hal::macros`
|
||||
|
||||
## `AtCmdConfig` now uses builder-lite pattern
|
||||
|
||||
```diff
|
||||
- uart0.set_at_cmd(AtCmdConfig::new(None, None, None, b'#', None));
|
||||
+ uart0.set_at_cmd(AtCmdConfig::default().with_cmd_char(b'#'));
|
||||
```
|
||||
|
||||
## Crate configuration changes
|
||||
|
||||
To prevent ambiguity between configurations, we had to change the naming format of configuration
|
||||
keys. Before, we used `{prefix}_{key}`, which meant that esp-hal and esp-hal-\* configuration keys
|
||||
were impossible to tell apart. To fix this issue, we are changing the separator from one underscore
|
||||
character to `_CONFIG_`. This also means that users will have to change their `config.toml`
|
||||
configurations to match the new format.
|
||||
|
||||
```diff
|
||||
[env]
|
||||
-ESP_HAL_PLACE_SPI_DRIVER_IN_RAM="true"
|
||||
+ESP_HAL_CONFIG_PLACE_SPI_DRIVER_IN_RAM="true"
|
||||
```
|
||||
|
||||
## UART changes
|
||||
|
||||
The `Config` struct's setters are now prefixed with `with_`. `parity_none`, `parity_even`,
|
||||
`parity_odd` have been replaced by `with_parity` that takes a `Parity` parameter.
|
||||
|
||||
```diff
|
||||
let config = Config::default()
|
||||
- .rx_fifo_full_threshold(30)
|
||||
+ .with_rx_fifo_full_threshold(30)
|
||||
- .parity_even();
|
||||
+ .with_parity(Parity::Even);
|
||||
```
|
||||
|
||||
The `DataBits`, `Parity`, and `StopBits` enum variants are no longer prefixed with the name of the enum.
|
||||
|
||||
e.g.
|
||||
|
||||
```diff
|
||||
- DataBits::DataBits8
|
||||
+ DataBits::_8
|
||||
- Parity::ParityNone
|
||||
+ Parity::None
|
||||
- StopBits::Stop1
|
||||
+ StopBits::_1
|
||||
```
|
||||
|
||||
The previous blocking implementation of `read_bytes` has been removed, and the non-blocking `drain_fifo` has instead been renamed to `read_bytes` in its place.
|
||||
|
||||
Any code which was previously using `read_bytes` to fill a buffer in a blocking manner will now need to implement the necessary logic to block until the buffer is filled in their application instead.
|
||||
|
||||
The `Error` enum variant uses object+verb naming.
|
||||
|
||||
e.g.
|
||||
|
||||
```diff
|
||||
- RxGlichDetected
|
||||
+ GlitchOccurred
|
||||
```
|
||||
|
||||
RX/TX pin assignment moved from constructors to builder functions.
|
||||
|
||||
e.g.
|
||||
|
||||
```diff
|
||||
let mut uart1 = Uart::new(
|
||||
peripherals.UART1,
|
||||
- Config::default(),
|
||||
- peripherals.GPIO1,
|
||||
- peripherals.GPIO2,
|
||||
- ).unwrap();
|
||||
+ Config::default())
|
||||
+ .unwrap()
|
||||
+ .with_rx(peripherals.GPIO1)
|
||||
+ .with_tx(peripherals.GPIO2);
|
||||
```
|
||||
|
||||
## Spi `with_miso` has been split
|
||||
|
||||
Previously, `with_miso` set up the provided pin as an input and output, which was necessary for half duplex.
|
||||
Full duplex does not require this, and it also creates an artificial restriction.
|
||||
|
||||
If you were using half duplex SPI with `with_miso`,
|
||||
you should now use `with_sio1` instead to get the previous behavior.
|
||||
|
||||
## CPU Clocks
|
||||
|
||||
The specific CPU clock variants are renamed from e.g. `Clock80MHz` to `_80MHz`.
|
||||
|
||||
```diff
|
||||
- CpuClock::Clock80MHz
|
||||
+ CpuClock::_80MHz
|
||||
```
|
||||
|
||||
Additionally the enum is marked as non-exhaustive.
|
||||
|
||||
## SPI Changes
|
||||
|
||||
The SPI mode variants are renamed from e.g. `Mode0` to `_0`.
|
||||
|
||||
```diff
|
||||
- Mode::Mode0
|
||||
+ Mode::_0
|
||||
```
|
||||
|
||||
The Address and Command enums have similarly had their variants changed from e.g. `Address1` to `_1Bit` and `Command1` to `_1Bit` respectively:
|
||||
|
||||
```diff
|
||||
- Address::Address1
|
||||
+ Address::_1Bit
|
||||
- Command::Command1
|
||||
+ Command::_1Bit
|
||||
```
|
||||
|
||||
## GPIO Changes
|
||||
|
||||
The GPIO drive strength variants are renamed from e.g. `I5mA` to `_5mA`.
|
||||
|
||||
```diff
|
||||
-DriveStrength::I5mA
|
||||
+DriveStrength::_5mA
|
||||
```
|
||||
|
||||
## ADC Changes
|
||||
|
||||
The ADC attenuation variants are renamed from e.g. `Attenuation0dB` to `_0dB`.
|
||||
|
||||
```diff
|
||||
-Attenuation::Attenuation0dB
|
||||
+Attenuation::_0dB
|
||||
```
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
[](https://crates.io/crates/esp-hal)
|
||||
[](https://docs.esp-rs.org/esp-hal)
|
||||

|
||||

|
||||

|
||||
[](https://matrix.to/#/#esp-rs:matrix.org)
|
||||
|
||||
@ -46,9 +46,13 @@ For help getting started with this HAL, please refer to [The Rust on ESP Book] a
|
||||
[s2-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf
|
||||
[s3-trm]: https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf
|
||||
|
||||
## `unstable` feature
|
||||
|
||||
The stable feature set is designed to remain consistent and reliable. Other parts guarded by the `unstable` feature, however, are still under active development and may undergo breaking changes and are disabled by default.
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
This crate is guaranteed to compile on stable Rust 1.79 and up. It _might_
|
||||
This crate is guaranteed to compile on stable Rust 1.83 and up. It _might_
|
||||
compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
@ -11,16 +11,14 @@ use esp_build::assert_unique_used_features;
|
||||
use esp_config::{generate_config, Value};
|
||||
use esp_metadata::{Chip, Config};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
esp_build::warning! {"
|
||||
WARNING: use --release
|
||||
We *strongly* recommend using release profile when building esp-hal.
|
||||
The dev profile can potentially be one or more orders of magnitude
|
||||
slower than release, and may cause issues with timing-senstive
|
||||
peripherals and/or devices.
|
||||
"}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("cargo:rustc-check-cfg=cfg(is_debug_build)");
|
||||
if let Ok(level) = std::env::var("OPT_LEVEL") {
|
||||
if level == "0" || level == "1" {
|
||||
println!("cargo:rustc-cfg=is_debug_build");
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: update when adding new device support!
|
||||
// Ensure that exactly one chip has been specified:
|
||||
assert_unique_used_features!(
|
||||
|
||||
@ -16,5 +16,5 @@ PROVIDE ( strchr = 0x4000c53c );
|
||||
PROVIDE ( strlcpy = 0x4000c584 );
|
||||
PROVIDE ( strstr = 0x4000c674 );
|
||||
PROVIDE ( strcasecmp = 0x400011cc );
|
||||
PROVIDE ( strdup = 0x4000143c );
|
||||
PROVIDE ( atoi = 0x400566c4 );
|
||||
|
||||
PROVIDE ( memchr = 0x4000c244 );
|
||||
|
||||
@ -13,5 +13,3 @@ PROVIDE ( strchr = 0x40000514 );
|
||||
PROVIDE ( strlcpy = 0x40000524 );
|
||||
PROVIDE ( strstr = 0x400004ac );
|
||||
PROVIDE ( strcasecmp = 0x40000504 );
|
||||
PROVIDE ( strdup = 0x40000510 );
|
||||
PROVIDE ( atoi = 0x40000580 );
|
||||
|
||||
@ -17,5 +17,5 @@ PROVIDE( strchr = 0x400003e0 );
|
||||
PROVIDE( strlcpy = 0x400003f0 );
|
||||
PROVIDE( strstr = 0x40000378 );
|
||||
PROVIDE( strcasecmp = 0x400003d0 );
|
||||
PROVIDE( strdup = 0x400003dc );
|
||||
PROVIDE( atoi = 0x4000044c );
|
||||
|
||||
PROVIDE( memchr = 0x400003c8 );
|
||||
|
||||
@ -15,5 +15,3 @@ PROVIDE(strchr = 0x40000534);
|
||||
PROVIDE(strlcpy = 0x40000544);
|
||||
PROVIDE(strstr = 0x400004cc);
|
||||
PROVIDE(strcasecmp = 0x40000524);
|
||||
PROVIDE(strdup = 0x40000530);
|
||||
PROVIDE(atoi = 0x400005a0);
|
||||
|
||||
@ -15,5 +15,5 @@ PROVIDE( strchr = 0x4000052c );
|
||||
PROVIDE( strlcpy = 0x4000053c );
|
||||
PROVIDE( strstr = 0x400004c4 );
|
||||
PROVIDE( strcasecmp = 0x4000051c );
|
||||
PROVIDE( strdup = 0x40000528 );
|
||||
PROVIDE( atoi = 0x40000598 );
|
||||
|
||||
PROVIDE( memchr = 0x40000514 );
|
||||
|
||||
@ -17,4 +17,3 @@ PROVIDE ( strchr = 0x4001adb0 );
|
||||
PROVIDE ( strlcpy = 0x4001adf8 );
|
||||
PROVIDE ( strstr = 0x4001aee8 );
|
||||
PROVIDE ( strcasecmp = 0x40007b38 );
|
||||
PROVIDE ( strdup = 0x40007d84 );
|
||||
|
||||
@ -19,5 +19,3 @@ PROVIDE( strchr = 0x4000138c );
|
||||
PROVIDE( strlcpy = 0x400013bc );
|
||||
PROVIDE( strstr = 0x40001254 );
|
||||
PROVIDE( strcasecmp = 0x4000135c );
|
||||
PROVIDE( strdup = 0x40001380 );
|
||||
PROVIDE( atoi = 0x400014d0 );
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
use crate::{
|
||||
aes::{Aes, Aes128, Aes192, Aes256, AesFlavour, Endianness, ALIGN_SIZE},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
};
|
||||
use crate::aes::{Aes, Aes128, Aes192, Aes256, AesFlavour, Endianness, Mode, ALIGN_SIZE};
|
||||
|
||||
impl<'d> Aes<'d> {
|
||||
impl Aes<'_> {
|
||||
pub(super) fn init(&mut self) {
|
||||
PeripheralClockControl::enable(PeripheralEnable::Aes);
|
||||
self.write_endianness(
|
||||
Endianness::BigEndian,
|
||||
Endianness::BigEndian,
|
||||
@ -31,8 +27,8 @@ impl<'d> Aes<'d> {
|
||||
.volatile_write_regset(self.aes.text(0).as_ptr(), block, text_len);
|
||||
}
|
||||
|
||||
pub(super) fn write_mode(&mut self, mode: u32) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode) });
|
||||
pub(super) fn write_mode(&self, mode: Mode) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode as _) });
|
||||
}
|
||||
|
||||
/// Configures how the state matrix would be laid out
|
||||
@ -55,7 +51,7 @@ impl<'d> Aes<'d> {
|
||||
self.aes.endian().write(|w| unsafe { w.bits(to_write) });
|
||||
}
|
||||
|
||||
pub(super) fn write_start(&mut self) {
|
||||
pub(super) fn write_start(&self) {
|
||||
self.aes.start().write(|w| w.start().set_bit());
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
use crate::{
|
||||
aes::{Aes, Aes128, Aes256, AesFlavour, ALIGN_SIZE},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
};
|
||||
use crate::aes::{Aes, Aes128, Aes256, AesFlavour, Mode, ALIGN_SIZE};
|
||||
|
||||
impl Aes<'_> {
|
||||
pub(super) fn init(&mut self) {
|
||||
PeripheralClockControl::enable(PeripheralEnable::Aes);
|
||||
self.write_dma(false);
|
||||
}
|
||||
|
||||
@ -29,11 +25,11 @@ impl Aes<'_> {
|
||||
.volatile_write_regset(self.aes.text_in(0).as_ptr(), block, 4);
|
||||
}
|
||||
|
||||
pub(super) fn write_mode(&mut self, mode: u32) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode) });
|
||||
pub(super) fn write_mode(&self, mode: Mode) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode as _) });
|
||||
}
|
||||
|
||||
pub(super) fn write_start(&mut self) {
|
||||
pub(super) fn write_start(&self) {
|
||||
self.aes.trigger().write(|w| w.trigger().set_bit());
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
use crate::{
|
||||
aes::{Aes, Aes128, Aes192, Aes256, AesFlavour, Endianness, ALIGN_SIZE},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
};
|
||||
use crate::aes::{Aes, Aes128, Aes192, Aes256, AesFlavour, Endianness, Mode, ALIGN_SIZE};
|
||||
|
||||
impl<'d> Aes<'d> {
|
||||
impl Aes<'_> {
|
||||
pub(super) fn init(&mut self) {
|
||||
PeripheralClockControl::enable(PeripheralEnable::Aes);
|
||||
self.write_dma(false);
|
||||
self.write_endianness(
|
||||
Endianness::BigEndian,
|
||||
@ -18,10 +14,9 @@ impl<'d> Aes<'d> {
|
||||
}
|
||||
|
||||
fn write_dma(&mut self, enable_dma: bool) {
|
||||
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()),
|
||||
};
|
||||
self.aes
|
||||
.dma_enable()
|
||||
.write(|w| w.dma_enable().bit(enable_dma));
|
||||
}
|
||||
|
||||
pub(super) fn write_key(&mut self, key: &[u8]) {
|
||||
@ -42,8 +37,8 @@ impl<'d> Aes<'d> {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn write_mode(&mut self, mode: u32) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode) });
|
||||
pub(super) fn write_mode(&self, mode: Mode) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode as _) });
|
||||
}
|
||||
|
||||
/// Configures how the state matrix would be laid out.
|
||||
@ -66,7 +61,7 @@ impl<'d> Aes<'d> {
|
||||
self.aes.endian().write(|w| unsafe { w.bits(to_write) });
|
||||
}
|
||||
|
||||
pub(super) fn write_start(&mut self) {
|
||||
pub(super) fn write_start(&self) {
|
||||
self.aes.trigger().write(|w| w.trigger().set_bit());
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,14 @@
|
||||
use crate::{
|
||||
aes::{Aes, Aes128, Aes256, AesFlavour, ALIGN_SIZE},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
};
|
||||
use crate::aes::{Aes, Aes128, Aes256, AesFlavour, Mode, ALIGN_SIZE};
|
||||
|
||||
impl<'d> Aes<'d> {
|
||||
impl Aes<'_> {
|
||||
pub(super) fn init(&mut self) {
|
||||
PeripheralClockControl::enable(PeripheralEnable::Aes);
|
||||
self.write_dma(false);
|
||||
}
|
||||
|
||||
fn write_dma(&mut self, enable_dma: bool) {
|
||||
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()),
|
||||
};
|
||||
self.aes
|
||||
.dma_enable()
|
||||
.write(|w| w.dma_enable().bit(enable_dma));
|
||||
}
|
||||
|
||||
pub(super) fn write_key(&mut self, key: &[u8]) {
|
||||
@ -34,11 +29,11 @@ impl<'d> Aes<'d> {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn write_mode(&mut self, mode: u32) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode) });
|
||||
pub(super) fn write_mode(&self, mode: Mode) {
|
||||
self.aes.mode().write(|w| unsafe { w.bits(mode as _) });
|
||||
}
|
||||
|
||||
pub(super) fn write_start(&mut self) {
|
||||
pub(super) fn write_start(&self) {
|
||||
self.aes.trigger().write(|w| w.trigger().set_bit());
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,7 @@ use crate::{
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::AES,
|
||||
reg_access::{AlignmentHelper, NativeEndianess},
|
||||
system::GenericPeripheralGuard,
|
||||
};
|
||||
|
||||
#[cfg_attr(esp32, path = "esp32.rs")]
|
||||
@ -136,6 +137,7 @@ pub enum Mode {
|
||||
pub struct Aes<'d> {
|
||||
aes: PeripheralRef<'d, AES>,
|
||||
alignment_helper: AlignmentHelper<NativeEndianess>,
|
||||
_guard: GenericPeripheralGuard<{ crate::system::Peripheral::Aes as u8 }>,
|
||||
}
|
||||
|
||||
impl<'d> Aes<'d> {
|
||||
@ -143,12 +145,12 @@ impl<'d> Aes<'d> {
|
||||
pub fn new(aes: impl Peripheral<P = AES> + 'd) -> Self {
|
||||
crate::into_ref!(aes);
|
||||
|
||||
crate::system::PeripheralClockControl::reset(crate::system::Peripheral::Aes);
|
||||
crate::system::PeripheralClockControl::enable(crate::system::Peripheral::Aes);
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
let mut ret = Self {
|
||||
aes,
|
||||
alignment_helper: AlignmentHelper::native_endianess(),
|
||||
_guard: guard,
|
||||
};
|
||||
ret.init();
|
||||
|
||||
@ -162,17 +164,13 @@ impl<'d> Aes<'d> {
|
||||
{
|
||||
// Convert from into Key enum
|
||||
self.write_key(key.into().as_slice());
|
||||
self.set_mode(mode as u8);
|
||||
self.write_mode(mode);
|
||||
self.set_block(block);
|
||||
self.start();
|
||||
while !(self.is_idle()) {}
|
||||
self.block(block);
|
||||
}
|
||||
|
||||
fn set_mode(&mut self, mode: u8) {
|
||||
self.write_mode(mode as u32);
|
||||
}
|
||||
|
||||
fn is_idle(&mut self) -> bool {
|
||||
self.read_idle()
|
||||
}
|
||||
@ -229,7 +227,7 @@ pub enum Endianness {
|
||||
/// transfer, which can significantly speed up operations when dealing with
|
||||
/// large data volumes. It supports various cipher modes such as ECB, CBC, OFB,
|
||||
/// CTR, CFB8, and CFB128.
|
||||
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
|
||||
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s2, esp32s3))]
|
||||
pub mod dma {
|
||||
use crate::{
|
||||
aes::{Key, Mode},
|
||||
@ -239,16 +237,19 @@ pub mod dma {
|
||||
ChannelRx,
|
||||
ChannelTx,
|
||||
DescriptorChain,
|
||||
DmaChannelConvert,
|
||||
DmaChannelFor,
|
||||
DmaDescriptor,
|
||||
DmaEligible,
|
||||
DmaPeripheral,
|
||||
DmaTransferRxTx,
|
||||
PeripheralDmaChannel,
|
||||
PeripheralRxChannel,
|
||||
PeripheralTxChannel,
|
||||
ReadBuffer,
|
||||
Rx,
|
||||
Tx,
|
||||
WriteBuffer,
|
||||
},
|
||||
peripheral::Peripheral,
|
||||
peripherals::AES,
|
||||
Blocking,
|
||||
};
|
||||
@ -256,6 +257,7 @@ pub mod dma {
|
||||
const ALIGN_SIZE: usize = core::mem::size_of::<u32>();
|
||||
|
||||
/// Specifies the block cipher modes available for AES operations.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CipherMode {
|
||||
/// Electronic Codebook Mode
|
||||
Ecb = 0,
|
||||
@ -276,7 +278,7 @@ pub mod dma {
|
||||
/// The underlying [`Aes`](super::Aes) driver
|
||||
pub aes: super::Aes<'d>,
|
||||
|
||||
channel: Channel<'d, Blocking, <AES as DmaEligible>::Dma>,
|
||||
channel: Channel<'d, Blocking, PeripheralDmaChannel<AES>>,
|
||||
rx_chain: DescriptorChain,
|
||||
tx_chain: DescriptorChain,
|
||||
}
|
||||
@ -285,16 +287,18 @@ pub mod dma {
|
||||
/// Enable DMA for the current instance of the AES driver
|
||||
pub fn with_dma<CH>(
|
||||
self,
|
||||
channel: Channel<'d, Blocking, CH>,
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
) -> AesDma<'d>
|
||||
where
|
||||
CH: DmaChannelConvert<<AES as DmaEligible>::Dma>,
|
||||
CH: DmaChannelFor<AES>,
|
||||
{
|
||||
let channel = Channel::new(channel.map(|ch| ch.degrade()));
|
||||
channel.runtime_ensure_compatible(&self.aes);
|
||||
AesDma {
|
||||
aes: self,
|
||||
channel: channel.degrade(),
|
||||
channel,
|
||||
rx_chain: DescriptorChain::new(rx_descriptors),
|
||||
tx_chain: DescriptorChain::new(tx_descriptors),
|
||||
}
|
||||
@ -324,7 +328,7 @@ pub mod dma {
|
||||
}
|
||||
|
||||
impl<'d> DmaSupportTx for AesDma<'d> {
|
||||
type TX = ChannelTx<'d, Blocking, <AES as DmaEligible>::Dma>;
|
||||
type TX = ChannelTx<'d, Blocking, PeripheralTxChannel<AES>>;
|
||||
|
||||
fn tx(&mut self) -> &mut Self::TX {
|
||||
&mut self.channel.tx
|
||||
@ -336,7 +340,7 @@ pub mod dma {
|
||||
}
|
||||
|
||||
impl<'d> DmaSupportRx for AesDma<'d> {
|
||||
type RX = ChannelRx<'d, Blocking, <AES as DmaEligible>::Dma>;
|
||||
type RX = ChannelRx<'d, Blocking, PeripheralRxChannel<AES>>;
|
||||
|
||||
fn rx(&mut self) -> &mut Self::RX {
|
||||
&mut self.channel.rx
|
||||
@ -417,9 +421,6 @@ pub mod dma {
|
||||
// AES has to be restarted after each calculation
|
||||
self.reset_aes();
|
||||
|
||||
self.channel.tx.is_done();
|
||||
self.channel.rx.is_done();
|
||||
|
||||
unsafe {
|
||||
self.tx_chain
|
||||
.fill_for_tx(false, write_buffer_ptr, write_buffer_len)?;
|
||||
@ -437,19 +438,18 @@ pub mod dma {
|
||||
}
|
||||
self.enable_dma(true);
|
||||
self.enable_interrupt();
|
||||
self.set_mode(mode);
|
||||
self.aes.write_mode(mode);
|
||||
self.set_cipher_mode(cipher_mode);
|
||||
self.write_key(key.into());
|
||||
|
||||
// TODO: verify 16?
|
||||
self.set_num_block(16);
|
||||
self.set_num_block((write_buffer_len as u32).div_ceil(16));
|
||||
|
||||
self.start_transform();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(esp32c3, esp32s3))]
|
||||
#[cfg(any(esp32c3, esp32s2, esp32s3))]
|
||||
fn reset_aes(&self) {
|
||||
unsafe {
|
||||
let s = crate::peripherals::SYSTEM::steal();
|
||||
@ -488,9 +488,9 @@ pub mod dma {
|
||||
self.aes
|
||||
.aes
|
||||
.block_mode()
|
||||
.modify(|_, w| unsafe { w.bits(mode as u32) });
|
||||
.modify(|_, w| unsafe { w.block_mode().bits(mode as u8) });
|
||||
|
||||
if self.aes.aes.block_mode().read().block_mode().bits() == CipherMode::Ctr as u8 {
|
||||
if mode == CipherMode::Ctr {
|
||||
self.aes
|
||||
.aes
|
||||
.inc_sel()
|
||||
@ -498,15 +498,8 @@ pub mod dma {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_mode(&self, mode: Mode) {
|
||||
self.aes
|
||||
.aes
|
||||
.mode()
|
||||
.modify(|_, w| unsafe { w.mode().bits(mode as u8) });
|
||||
}
|
||||
|
||||
fn start_transform(&self) {
|
||||
self.aes.aes.trigger().write(|w| w.trigger().set_bit());
|
||||
self.aes.write_start();
|
||||
}
|
||||
|
||||
fn finish_transform(&self) {
|
||||
|
||||
@ -146,22 +146,22 @@ mod impls {
|
||||
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c3/curve_fitting_coefficients.c>
|
||||
#[cfg(esp32c3)]
|
||||
CURVES_COEFFS1 [
|
||||
Attenuation0dB => [
|
||||
_0dB => [
|
||||
-0.225966470500043,
|
||||
-0.0007265418501948,
|
||||
0.0000109410402681,
|
||||
],
|
||||
Attenuation2p5dB => [
|
||||
_2p5dB => [
|
||||
0.4229623392600516,
|
||||
-0.0000731527490903,
|
||||
0.0000088166562521,
|
||||
],
|
||||
Attenuation6dB => [
|
||||
_6dB => [
|
||||
-1.017859239236435,
|
||||
-0.0097159265299153,
|
||||
0.0000149794028038,
|
||||
],
|
||||
Attenuation11dB => [
|
||||
_11dB => [
|
||||
-1.4912262772850453,
|
||||
-0.0228549975564099,
|
||||
0.0000356391935717,
|
||||
@ -173,22 +173,22 @@ mod impls {
|
||||
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c6/curve_fitting_coefficients.c>
|
||||
#[cfg(esp32c6)]
|
||||
CURVES_COEFFS1 [
|
||||
Attenuation0dB => [
|
||||
_0dB => [
|
||||
-0.0487166399931449,
|
||||
0.0006436483033201,
|
||||
0.0000030410131806,
|
||||
],
|
||||
Attenuation2p5dB => [
|
||||
_2p5dB => [
|
||||
-0.8665498165817785,
|
||||
0.0015239070452946,
|
||||
0.0000013818878844,
|
||||
],
|
||||
Attenuation6dB => [
|
||||
_6dB => [
|
||||
-1.2277821756674387,
|
||||
0.0022275554717885,
|
||||
0.0000005924302667,
|
||||
],
|
||||
Attenuation11dB => [
|
||||
_11dB => [
|
||||
-0.3801417550380255,
|
||||
-0.0006020352420772,
|
||||
0.0000012442478488,
|
||||
@ -198,22 +198,22 @@ mod impls {
|
||||
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
|
||||
#[cfg(esp32s3)]
|
||||
CURVES_COEFFS1 [
|
||||
Attenuation0dB => [
|
||||
_0dB => [
|
||||
-2.7856531419538344,
|
||||
-0.0050871540569528,
|
||||
0.0000097982495890,
|
||||
],
|
||||
Attenuation2p5dB => [
|
||||
_2p5dB => [
|
||||
-2.9831022915028695,
|
||||
-0.0049393185868806,
|
||||
0.0000101379430548,
|
||||
],
|
||||
Attenuation6dB => [
|
||||
_6dB => [
|
||||
-2.3285545746296417,
|
||||
-0.0147640181047414,
|
||||
0.0000208385525314,
|
||||
],
|
||||
Attenuation11dB => [
|
||||
_11dB => [
|
||||
-0.644403418269478,
|
||||
-0.0644334888647536,
|
||||
0.0001297891447611,
|
||||
@ -225,22 +225,22 @@ mod impls {
|
||||
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
|
||||
#[cfg(esp32s3)]
|
||||
CURVES_COEFFS2 [
|
||||
Attenuation0dB => [
|
||||
_0dB => [
|
||||
-2.5668651654328927,
|
||||
0.0001353548869615,
|
||||
0.0000036615265189,
|
||||
],
|
||||
Attenuation2p5dB => [
|
||||
_2p5dB => [
|
||||
-2.3690184690298404,
|
||||
-0.0066319894226185,
|
||||
0.0000118964995959,
|
||||
],
|
||||
Attenuation6dB => [
|
||||
_6dB => [
|
||||
-0.9452499397020617,
|
||||
-0.0200996773954387,
|
||||
0.00000259011467956,
|
||||
],
|
||||
Attenuation11dB => [
|
||||
_11dB => [
|
||||
1.2247719764336924,
|
||||
-0.0755717904943462,
|
||||
0.0001478791187119,
|
||||
|
||||
@ -7,7 +7,9 @@ use crate::{
|
||||
pub(super) const NUM_ATTENS: usize = 10;
|
||||
|
||||
/// The sampling/readout resolution of the ADC.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum Resolution {
|
||||
/// 9-bit resolution
|
||||
Resolution9Bit = 0b00,
|
||||
@ -327,7 +329,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, ADC1> Adc<'d, ADC1> {
|
||||
impl<ADC1> Adc<'_, ADC1> {
|
||||
/// Enable the Hall sensor
|
||||
pub fn enable_hall_sensor() {
|
||||
unsafe { &*RTC_IO::ptr() }
|
||||
@ -343,19 +345,6 @@ impl<'d, ADC1> Adc<'d, ADC1> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, ADCI, PIN> embedded_hal_02::adc::OneShot<ADCI, u16, super::AdcPin<PIN, ADCI>>
|
||||
for Adc<'d, ADCI>
|
||||
where
|
||||
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8> + super::AdcChannel,
|
||||
ADCI: RegisterAccess,
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn read(&mut self, pin: &mut super::AdcPin<PIN, ADCI>) -> nb::Result<u16, Self::Error> {
|
||||
self.read_oneshot(pin)
|
||||
}
|
||||
}
|
||||
|
||||
mod adc_implementation {
|
||||
crate::analog::adc::impl_adc_interface! {
|
||||
ADC1 [
|
||||
|
||||
@ -16,11 +16,7 @@
|
||||
//! basic calibration, curve fitting or linear interpolation. The calibration
|
||||
//! schemes can be used to improve the accuracy of the ADC readings.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! The ADC driver implements the `embedded-hal@0.2.x` ADC traits.
|
||||
//!
|
||||
//! ## Example
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Read an analog signal from a pin
|
||||
//!
|
||||
@ -40,7 +36,7 @@
|
||||
//! let mut adc1_config = AdcConfig::new();
|
||||
//! let mut pin = adc1_config.enable_pin(
|
||||
//! analog_pin,
|
||||
//! Attenuation::Attenuation11dB,
|
||||
//! Attenuation::_11dB,
|
||||
//! );
|
||||
//! let mut adc1 = Adc::new(peripherals.ADC1, adc1_config);
|
||||
//!
|
||||
@ -74,23 +70,26 @@ mod implementation;
|
||||
/// The effective measurement range for a given attenuation is dependent on the
|
||||
/// device being targeted. Please refer to "ADC Characteristics" section of your
|
||||
/// device's datasheet for more information.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum Attenuation {
|
||||
/// 0dB attenuation
|
||||
Attenuation0dB = 0b00,
|
||||
_0dB = 0b00,
|
||||
/// 2.5dB attenuation
|
||||
#[cfg(not(esp32c2))]
|
||||
Attenuation2p5dB = 0b01,
|
||||
_2p5dB = 0b01,
|
||||
/// 6dB attenuation
|
||||
#[cfg(not(esp32c2))]
|
||||
Attenuation6dB = 0b10,
|
||||
_6dB = 0b10,
|
||||
/// 11dB attenuation
|
||||
Attenuation11dB = 0b11,
|
||||
_11dB = 0b11,
|
||||
}
|
||||
|
||||
/// Calibration source of the ADC.
|
||||
#[cfg(not(esp32))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum AdcCalSource {
|
||||
/// Use Ground as the calibration source
|
||||
Gnd,
|
||||
@ -108,17 +107,6 @@ pub struct AdcPin<PIN, ADCI, CS = ()> {
|
||||
_phantom: PhantomData<ADCI>,
|
||||
}
|
||||
|
||||
impl<PIN, ADCI, CS> embedded_hal_02::adc::Channel<ADCI> for AdcPin<PIN, ADCI, CS>
|
||||
where
|
||||
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8>,
|
||||
{
|
||||
type ID = u8;
|
||||
|
||||
fn channel() -> Self::ID {
|
||||
PIN::channel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the ADC.
|
||||
pub struct AdcConfig<ADCI> {
|
||||
#[cfg_attr(not(esp32), allow(unused))]
|
||||
@ -256,12 +244,6 @@ macro_rules! impl_adc_interface {
|
||||
impl $crate::analog::adc::AdcChannel for crate::gpio::GpioPin<$pin> {
|
||||
const CHANNEL: u8 = $channel;
|
||||
}
|
||||
|
||||
impl embedded_hal_02::adc::Channel<crate::peripherals::$adc> for crate::gpio::GpioPin<$pin> {
|
||||
type ID = u8;
|
||||
|
||||
fn channel() -> u8 { $channel }
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use crate::efuse::Efuse;
|
||||
use crate::{
|
||||
peripheral::PeripheralRef,
|
||||
peripherals::APB_SARADC,
|
||||
system::{Peripheral, PeripheralClockControl},
|
||||
system::{GenericPeripheralGuard, Peripheral},
|
||||
};
|
||||
|
||||
mod calibration;
|
||||
@ -101,7 +101,9 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
|
||||
/// The sampling/readout resolution of the ADC.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum Resolution {
|
||||
/// 12-bit resolution
|
||||
#[default]
|
||||
@ -396,6 +398,7 @@ pub struct Adc<'d, ADCI> {
|
||||
_adc: PeripheralRef<'d, ADCI>,
|
||||
attenuations: [Option<Attenuation>; NUM_ATTENS],
|
||||
active_channel: Option<u8>,
|
||||
_guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
|
||||
}
|
||||
|
||||
impl<'d, ADCI> Adc<'d, ADCI>
|
||||
@ -408,8 +411,7 @@ where
|
||||
adc_instance: impl crate::peripheral::Peripheral<P = ADCI> + 'd,
|
||||
config: AdcConfig<ADCI>,
|
||||
) -> Self {
|
||||
PeripheralClockControl::reset(Peripheral::ApbSarAdc);
|
||||
PeripheralClockControl::enable(Peripheral::ApbSarAdc);
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
unsafe { &*APB_SARADC::PTR }.ctrl().modify(|_, w| unsafe {
|
||||
w.start_force().set_bit();
|
||||
@ -422,6 +424,7 @@ where
|
||||
_adc: adc_instance.into_ref(),
|
||||
attenuations: config.attenuations,
|
||||
active_channel: None,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -530,20 +533,6 @@ impl super::AdcCalEfuse for crate::peripherals::ADC2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<ADCI, PIN, CS> embedded_hal_02::adc::OneShot<ADCI, u16, super::AdcPin<PIN, ADCI, CS>>
|
||||
for Adc<'_, ADCI>
|
||||
where
|
||||
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8> + super::AdcChannel,
|
||||
ADCI: RegisterAccess,
|
||||
CS: super::AdcCalScheme<ADCI>,
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn read(&mut self, pin: &mut super::AdcPin<PIN, ADCI, CS>) -> nb::Result<u16, Self::Error> {
|
||||
self.read_oneshot(pin)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(esp32c2)]
|
||||
mod adc_implementation {
|
||||
crate::analog::adc::impl_adc_interface! {
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::efuse::Efuse;
|
||||
use crate::{
|
||||
peripheral::PeripheralRef,
|
||||
peripherals::{APB_SARADC, SENS},
|
||||
system::{Peripheral, PeripheralClockControl},
|
||||
system::{GenericPeripheralGuard, Peripheral},
|
||||
};
|
||||
|
||||
mod calibration;
|
||||
@ -73,7 +73,9 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
|
||||
/// The sampling/readout resolution of the ADC.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum Resolution {
|
||||
/// 13-bit resolution
|
||||
#[default]
|
||||
@ -389,6 +391,7 @@ pub struct Adc<'d, ADC> {
|
||||
_adc: PeripheralRef<'d, ADC>,
|
||||
active_channel: Option<u8>,
|
||||
last_init_code: u16,
|
||||
_guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
|
||||
}
|
||||
|
||||
impl<'d, ADCI> Adc<'d, ADCI>
|
||||
@ -401,9 +404,7 @@ where
|
||||
adc_instance: impl crate::peripheral::Peripheral<P = ADCI> + 'd,
|
||||
config: AdcConfig<ADCI>,
|
||||
) -> Self {
|
||||
PeripheralClockControl::reset(Peripheral::ApbSarAdc);
|
||||
PeripheralClockControl::enable(Peripheral::ApbSarAdc);
|
||||
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
let sensors = unsafe { &*SENS::ptr() };
|
||||
|
||||
// Set attenuation for pins
|
||||
@ -472,6 +473,7 @@ where
|
||||
_adc: adc_instance.into_ref(),
|
||||
active_channel: None,
|
||||
last_init_code: 0,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -590,20 +592,6 @@ impl super::AdcCalEfuse for crate::peripherals::ADC2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, ADCI, PIN, CS> embedded_hal_02::adc::OneShot<ADCI, u16, AdcPin<PIN, ADCI, CS>>
|
||||
for Adc<'d, ADCI>
|
||||
where
|
||||
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8> + AdcChannel,
|
||||
ADCI: RegisterAccess,
|
||||
CS: AdcCalScheme<ADCI>,
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn read(&mut self, pin: &mut AdcPin<PIN, ADCI, CS>) -> nb::Result<u16, Self::Error> {
|
||||
self.read_oneshot(pin)
|
||||
}
|
||||
}
|
||||
|
||||
mod adc_implementation {
|
||||
crate::analog::adc::impl_adc_interface! {
|
||||
ADC1 [
|
||||
|
||||
@ -24,10 +24,9 @@
|
||||
//! - This driver has only blocking API
|
||||
|
||||
use crate::{
|
||||
interrupt::InterruptHandler,
|
||||
interrupt::{InterruptConfigurable, InterruptHandler},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::{Interrupt, ASSIST_DEBUG},
|
||||
InterruptConfigurable,
|
||||
};
|
||||
|
||||
/// The debug assist driver instance.
|
||||
@ -392,7 +391,7 @@ impl DebugAssist<'_> {
|
||||
}
|
||||
|
||||
#[cfg(all(assist_debug_region_monitor, multi_core))]
|
||||
impl<'d> DebugAssist<'d> {
|
||||
impl DebugAssist<'_> {
|
||||
/// Enable region monitoring of read/write performed by the secondary CPU in
|
||||
/// a certain memory region0. Whenever the bus reads or writes in the
|
||||
/// specified memory region, an interrupt will be triggered.
|
||||
|
||||
30
esp-hal/src/asynch.rs
Normal file
30
esp-hal/src/asynch.rs
Normal file
@ -0,0 +1,30 @@
|
||||
//! Asynchronous utilities.
|
||||
use core::task::Waker;
|
||||
|
||||
use embassy_sync::waitqueue::GenericAtomicWaker;
|
||||
|
||||
use crate::sync::RawMutex;
|
||||
|
||||
/// Utility struct to register and wake a waker.
|
||||
pub struct AtomicWaker {
|
||||
waker: GenericAtomicWaker<RawMutex>,
|
||||
}
|
||||
|
||||
impl AtomicWaker {
|
||||
/// Create a new `AtomicWaker`.
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
waker: GenericAtomicWaker::new(RawMutex::new()),
|
||||
}
|
||||
}
|
||||
|
||||
delegate::delegate! {
|
||||
to self.waker {
|
||||
/// Register a waker. Overwrites the previous waker, if any.
|
||||
pub fn register(&self, w: &Waker);
|
||||
/// Wake the registered waker, if any.
|
||||
pub fn wake(&self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,7 @@ pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock
|
||||
|
||||
// Configure 320M PLL
|
||||
match xtal_freq {
|
||||
XtalClock::RtcXtalFreq40M => {
|
||||
XtalClock::_40M => {
|
||||
div_ref = 0;
|
||||
div7_0 = 32;
|
||||
div10_8 = 0;
|
||||
@ -73,7 +73,7 @@ pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock
|
||||
bw = 3;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreq26M => {
|
||||
XtalClock::_26M => {
|
||||
div_ref = 12;
|
||||
div7_0 = 224;
|
||||
div10_8 = 4;
|
||||
@ -82,7 +82,7 @@ pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock
|
||||
bw = 1;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreqOther(_) => {
|
||||
XtalClock::Other(_) => {
|
||||
div_ref = 12;
|
||||
div7_0 = 224;
|
||||
div10_8 = 4;
|
||||
@ -102,7 +102,7 @@ pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock
|
||||
|
||||
// Configure 480M PLL
|
||||
match xtal_freq {
|
||||
XtalClock::RtcXtalFreq40M => {
|
||||
XtalClock::_40M => {
|
||||
div_ref = 0;
|
||||
div7_0 = 28;
|
||||
div10_8 = 0;
|
||||
@ -111,7 +111,7 @@ pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock
|
||||
bw = 3;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreq26M => {
|
||||
XtalClock::_26M => {
|
||||
div_ref = 12;
|
||||
div7_0 = 144;
|
||||
div10_8 = 4;
|
||||
@ -120,7 +120,7 @@ pub(crate) fn esp32_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClock
|
||||
bw = 1;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreqOther(_) => {
|
||||
XtalClock::Other(_) => {
|
||||
div_ref = 12;
|
||||
div7_0 = 224;
|
||||
div10_8 = 4;
|
||||
@ -220,14 +220,14 @@ pub(crate) fn set_cpu_freq(cpu_freq_mhz: crate::clock::CpuClock) {
|
||||
let per_conf;
|
||||
|
||||
match cpu_freq_mhz {
|
||||
crate::clock::CpuClock::Clock160MHz => {
|
||||
crate::clock::CpuClock::_160MHz => {
|
||||
per_conf = CPU_160M;
|
||||
}
|
||||
crate::clock::CpuClock::Clock240MHz => {
|
||||
crate::clock::CpuClock::_240MHz => {
|
||||
dbias = dig_dbias_240_m;
|
||||
per_conf = CPU_240M;
|
||||
}
|
||||
crate::clock::CpuClock::Clock80MHz => {
|
||||
crate::clock::CpuClock::_80MHz => {
|
||||
per_conf = CPU_80M;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ pub(crate) fn esp32c2_rtc_bbpll_configure(xtal_freq: XtalClock, _pll_freq: PllCl
|
||||
|
||||
// Configure 480M PLL
|
||||
match xtal_freq {
|
||||
XtalClock::RtcXtalFreq26M => {
|
||||
XtalClock::_26M => {
|
||||
div_ref = 12;
|
||||
div7_0 = 236;
|
||||
dr1 = 4;
|
||||
@ -74,7 +74,7 @@ pub(crate) fn esp32c2_rtc_bbpll_configure(xtal_freq: XtalClock, _pll_freq: PllCl
|
||||
dcur = 0;
|
||||
dbias = 2;
|
||||
}
|
||||
XtalClock::RtcXtalFreq40M | XtalClock::RtcXtalFreqOther(_) => {
|
||||
XtalClock::_40M | XtalClock::Other(_) => {
|
||||
div_ref = 0;
|
||||
div7_0 = 8;
|
||||
dr1 = 0;
|
||||
@ -150,8 +150,8 @@ pub(crate) fn esp32c2_rtc_freq_to_pll_mhz(cpu_clock_speed: CpuClock) {
|
||||
.modify(|_, w| w.pre_div_cnt().bits(0).soc_clk_sel().bits(1));
|
||||
system_control.cpu_per_conf().modify(|_, w| {
|
||||
w.cpuperiod_sel().bits(match cpu_clock_speed {
|
||||
CpuClock::Clock80MHz => 0,
|
||||
CpuClock::Clock120MHz => 1,
|
||||
CpuClock::_80MHz => 0,
|
||||
CpuClock::_120MHz => 1,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClo
|
||||
|
||||
// Configure 480M PLL
|
||||
match xtal_freq {
|
||||
XtalClock::RtcXtalFreq40M => {
|
||||
XtalClock::_40M => {
|
||||
div_ref = 0;
|
||||
div7_0 = 8;
|
||||
dr1 = 0;
|
||||
@ -80,7 +80,7 @@ pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClo
|
||||
dbias = 2;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreq32M => {
|
||||
XtalClock::_32M => {
|
||||
div_ref = 1;
|
||||
div7_0 = 26;
|
||||
dr1 = 1;
|
||||
@ -90,7 +90,7 @@ pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClo
|
||||
dbias = 2;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreqOther(_) => {
|
||||
XtalClock::Other(_) => {
|
||||
div_ref = 0;
|
||||
div7_0 = 8;
|
||||
dr1 = 0;
|
||||
@ -110,7 +110,7 @@ pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClo
|
||||
|
||||
// Configure 320M PLL
|
||||
match xtal_freq {
|
||||
XtalClock::RtcXtalFreq40M => {
|
||||
XtalClock::_40M => {
|
||||
div_ref = 0;
|
||||
div7_0 = 4;
|
||||
dr1 = 0;
|
||||
@ -120,7 +120,7 @@ pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClo
|
||||
dbias = 2;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreq32M => {
|
||||
XtalClock::_32M => {
|
||||
div_ref = 1;
|
||||
div7_0 = 6;
|
||||
dr1 = 0;
|
||||
@ -130,7 +130,7 @@ pub(crate) fn esp32c3_rtc_bbpll_configure(xtal_freq: XtalClock, pll_freq: PllClo
|
||||
dbias = 2;
|
||||
}
|
||||
|
||||
XtalClock::RtcXtalFreqOther(_) => {
|
||||
XtalClock::Other(_) => {
|
||||
div_ref = 0;
|
||||
div7_0 = 4;
|
||||
dr1 = 0;
|
||||
@ -211,8 +211,8 @@ pub(crate) fn esp32c3_rtc_freq_to_pll_mhz(cpu_clock_speed: CpuClock) {
|
||||
.modify(|_, w| w.pre_div_cnt().bits(0).soc_clk_sel().bits(1));
|
||||
system_control.cpu_per_conf().modify(|_, w| {
|
||||
w.cpuperiod_sel().bits(match cpu_clock_speed {
|
||||
CpuClock::Clock80MHz => 0,
|
||||
CpuClock::Clock160MHz => 1,
|
||||
CpuClock::_80MHz => 0,
|
||||
CpuClock::_160MHz => 1,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@ -23,17 +23,17 @@ pub(crate) fn set_cpu_clock(cpu_clock_speed: CpuClock) {
|
||||
.set_bit()
|
||||
.cpuperiod_sel()
|
||||
.bits(match cpu_clock_speed {
|
||||
CpuClock::Clock80MHz => 0,
|
||||
CpuClock::Clock160MHz => 1,
|
||||
CpuClock::Clock240MHz => 2,
|
||||
CpuClock::_80MHz => 0,
|
||||
CpuClock::_160MHz => 1,
|
||||
CpuClock::_240MHz => 2,
|
||||
})
|
||||
});
|
||||
|
||||
rtc_cntl.reg().modify(|_, w| {
|
||||
w.dig_reg_dbias_wak().bits(match cpu_clock_speed {
|
||||
CpuClock::Clock80MHz => DIG_DBIAS_80M_160M,
|
||||
CpuClock::Clock160MHz => DIG_DBIAS_80M_160M,
|
||||
CpuClock::Clock240MHz => DIG_DBIAS_240M,
|
||||
CpuClock::_80MHz => DIG_DBIAS_80M_160M,
|
||||
CpuClock::_160MHz => DIG_DBIAS_80M_160M,
|
||||
CpuClock::_240MHz => DIG_DBIAS_240M,
|
||||
} as u8)
|
||||
});
|
||||
|
||||
|
||||
@ -17,9 +17,9 @@ pub(crate) fn set_cpu_clock(cpu_clock_speed: CpuClock) {
|
||||
.set_bit()
|
||||
.cpuperiod_sel()
|
||||
.bits(match cpu_clock_speed {
|
||||
CpuClock::Clock80MHz => 0,
|
||||
CpuClock::Clock160MHz => 1,
|
||||
CpuClock::Clock240MHz => 2,
|
||||
CpuClock::_80MHz => 0,
|
||||
CpuClock::_160MHz => 1,
|
||||
CpuClock::_240MHz => 2,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@ -28,36 +28,18 @@
|
||||
//! ### Frozen Clock Frequencies
|
||||
//!
|
||||
//! Once the clock configuration is applied, the clock frequencies become
|
||||
//! `frozen` and cannot be changed. The `Clocks` struct is returned as part of
|
||||
//! the `System` struct, providing read-only access to the configured clock
|
||||
//! frequencies.
|
||||
//! `frozen` and cannot be changed.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Initialize With Different Clock Frequencies
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! // Initialize with the highest possible frequency for this chip
|
||||
//! let peripherals = esp_hal::init({
|
||||
//! let mut config = esp_hal::Config::default();
|
||||
//! config.cpu_clock = CpuClock::max();
|
||||
//! config
|
||||
//! });
|
||||
//! use esp_hal::clock::CpuClock;
|
||||
//!
|
||||
//! // Initialize with custom clock frequency
|
||||
//! // let peripherals = esp_hal::init({
|
||||
//! // let mut config = esp_hal::Config::default();
|
||||
#![cfg_attr(
|
||||
not(any(esp32c2, esp32h2)),
|
||||
doc = "// config.cpu_clock = CpuClock::Clock160MHz;"
|
||||
)]
|
||||
#![cfg_attr(esp32c2, doc = "// config.cpu_clock = CpuClock::Clock120MHz;")]
|
||||
#![cfg_attr(esp32h2, doc = "// config.cpu_clock = CpuClock::Clock96MHz;")]
|
||||
//! // config
|
||||
//! // });
|
||||
//! //
|
||||
//! // Initialize with default clock frequency for this chip
|
||||
//! // let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
//! // Initialize with the highest possible frequency for this chip
|
||||
//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
//! let peripherals = esp_hal::init(config);
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
@ -76,6 +58,7 @@ use crate::rtc_cntl::RtcClock;
|
||||
pub(crate) mod clocks_ll;
|
||||
|
||||
/// Clock properties
|
||||
#[doc(hidden)]
|
||||
pub trait Clock {
|
||||
/// Frequency of the clock in [Hertz](fugit::HertzU32), using [fugit] types.
|
||||
fn frequency(&self) -> HertzU32;
|
||||
@ -94,36 +77,41 @@ pub trait Clock {
|
||||
/// CPU clock speed
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(
|
||||
clippy::enum_variant_names,
|
||||
reason = "MHz suffix indicates physical unit."
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub enum CpuClock {
|
||||
/// 80MHz CPU clock
|
||||
#[cfg(not(esp32h2))]
|
||||
Clock80MHz = 80,
|
||||
_80MHz = 80,
|
||||
|
||||
/// 96MHz CPU clock
|
||||
#[cfg(esp32h2)]
|
||||
Clock96MHz = 96,
|
||||
_96MHz = 96,
|
||||
|
||||
/// 120MHz CPU clock
|
||||
#[cfg(esp32c2)]
|
||||
Clock120MHz = 120,
|
||||
_120MHz = 120,
|
||||
|
||||
/// 160MHz CPU clock
|
||||
#[cfg(not(any(esp32c2, esp32h2)))]
|
||||
Clock160MHz = 160,
|
||||
_160MHz = 160,
|
||||
|
||||
/// 240MHz CPU clock
|
||||
#[cfg(xtensa)]
|
||||
Clock240MHz = 240,
|
||||
_240MHz = 240,
|
||||
}
|
||||
|
||||
impl Default for CpuClock {
|
||||
fn default() -> Self {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(esp32h2)] {
|
||||
Self::Clock96MHz
|
||||
Self::_96MHz
|
||||
} else {
|
||||
// FIXME: I don't think this is correct in general?
|
||||
Self::Clock80MHz
|
||||
Self::_80MHz
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,13 +122,13 @@ impl CpuClock {
|
||||
pub const fn max() -> Self {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(esp32c2)] {
|
||||
Self::Clock120MHz
|
||||
Self::_120MHz
|
||||
} else if #[cfg(any(esp32c3, esp32c6))] {
|
||||
Self::Clock160MHz
|
||||
Self::_160MHz
|
||||
} else if #[cfg(esp32h2)] {
|
||||
Self::Clock96MHz
|
||||
Self::_96MHz
|
||||
} else {
|
||||
Self::Clock240MHz
|
||||
Self::_240MHz
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,32 +141,33 @@ impl Clock for CpuClock {
|
||||
}
|
||||
|
||||
/// XTAL clock speed
|
||||
#[instability::unstable]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub enum XtalClock {
|
||||
/// 26MHz XTAL clock
|
||||
#[cfg(any(esp32, esp32c2))]
|
||||
RtcXtalFreq26M,
|
||||
_26M,
|
||||
/// 32MHz XTAL clock
|
||||
#[cfg(any(esp32c3, esp32h2, esp32s3))]
|
||||
RtcXtalFreq32M,
|
||||
_32M,
|
||||
/// 40MHz XTAL clock
|
||||
#[cfg(not(esp32h2))]
|
||||
RtcXtalFreq40M,
|
||||
_40M,
|
||||
/// Other XTAL clock
|
||||
RtcXtalFreqOther(u32),
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
impl Clock for XtalClock {
|
||||
fn frequency(&self) -> HertzU32 {
|
||||
match self {
|
||||
#[cfg(any(esp32, esp32c2))]
|
||||
XtalClock::RtcXtalFreq26M => HertzU32::MHz(26),
|
||||
XtalClock::_26M => HertzU32::MHz(26),
|
||||
#[cfg(any(esp32c3, esp32h2, esp32s3))]
|
||||
XtalClock::RtcXtalFreq32M => HertzU32::MHz(32),
|
||||
XtalClock::_32M => HertzU32::MHz(32),
|
||||
#[cfg(not(esp32h2))]
|
||||
XtalClock::RtcXtalFreq40M => HertzU32::MHz(40),
|
||||
XtalClock::RtcXtalFreqOther(mhz) => HertzU32::MHz(*mhz),
|
||||
XtalClock::_40M => HertzU32::MHz(40),
|
||||
XtalClock::Other(mhz) => HertzU32::MHz(*mhz),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,6 +254,7 @@ impl Clock for ApbClock {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
#[doc(hidden)]
|
||||
pub struct Clocks {
|
||||
/// CPU clock frequency
|
||||
pub cpu_clock: HertzU32,
|
||||
@ -326,7 +316,8 @@ impl Clocks {
|
||||
///
|
||||
/// This function will run the frequency estimation if called before
|
||||
/// [`crate::init()`].
|
||||
pub fn xtal_freq() -> HertzU32 {
|
||||
#[cfg(systimer)]
|
||||
pub(crate) fn xtal_freq() -> HertzU32 {
|
||||
if let Some(clocks) = Self::try_get() {
|
||||
clocks.xtal_clock
|
||||
} else {
|
||||
@ -339,9 +330,9 @@ impl Clocks {
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
if RtcClock::estimate_xtal_frequency() > 33 {
|
||||
XtalClock::RtcXtalFreq40M
|
||||
XtalClock::_40M
|
||||
} else {
|
||||
XtalClock::RtcXtalFreq26M
|
||||
XtalClock::_26M
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,9 +342,9 @@ impl Clocks {
|
||||
|
||||
if cpu_clock_speed != CpuClock::default() {
|
||||
let pll_freq = match cpu_clock_speed {
|
||||
CpuClock::Clock80MHz => PllClock::Pll320MHz,
|
||||
CpuClock::Clock160MHz => PllClock::Pll320MHz,
|
||||
CpuClock::Clock240MHz => PllClock::Pll480MHz,
|
||||
CpuClock::_80MHz => PllClock::Pll320MHz,
|
||||
CpuClock::_160MHz => PllClock::Pll320MHz,
|
||||
CpuClock::_240MHz => PllClock::Pll480MHz,
|
||||
};
|
||||
|
||||
clocks_ll::esp32_rtc_update_to_xtal(xtal_freq, 1);
|
||||
@ -379,9 +370,9 @@ impl Clocks {
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
if RtcClock::estimate_xtal_frequency() > 33 {
|
||||
XtalClock::RtcXtalFreq40M
|
||||
XtalClock::_40M
|
||||
} else {
|
||||
XtalClock::RtcXtalFreq26M
|
||||
XtalClock::_26M
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,7 +410,7 @@ impl Clocks {
|
||||
#[cfg(esp32c3)]
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
XtalClock::RtcXtalFreq40M
|
||||
XtalClock::_40M
|
||||
}
|
||||
|
||||
/// Configure the CPU clock speed.
|
||||
@ -455,7 +446,7 @@ impl Clocks {
|
||||
#[cfg(esp32c6)]
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
XtalClock::RtcXtalFreq40M
|
||||
XtalClock::_40M
|
||||
}
|
||||
|
||||
/// Configure the CPU clock speed.
|
||||
@ -492,7 +483,7 @@ impl Clocks {
|
||||
#[cfg(esp32h2)]
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
XtalClock::RtcXtalFreq32M
|
||||
XtalClock::_32M
|
||||
}
|
||||
|
||||
/// Configure the CPU clock speed.
|
||||
@ -531,7 +522,7 @@ impl Clocks {
|
||||
#[cfg(esp32s2)]
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
XtalClock::RtcXtalFreq40M
|
||||
XtalClock::_40M
|
||||
}
|
||||
|
||||
/// Configure the CPU clock speed.
|
||||
@ -553,7 +544,7 @@ impl Clocks {
|
||||
#[cfg(esp32s3)]
|
||||
impl Clocks {
|
||||
fn measure_xtal_frequency() -> XtalClock {
|
||||
XtalClock::RtcXtalFreq40M
|
||||
XtalClock::_40M
|
||||
}
|
||||
|
||||
/// Configure the CPU clock speed.
|
||||
|
||||
@ -22,10 +22,12 @@
|
||||
//! ### Custom initialization
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! let mut config = esp_hal::Config::default();
|
||||
//! config.cpu_clock = CpuClock::max();
|
||||
//! config.watchdog.rwdt =
|
||||
//! esp_hal::config::WatchdogStatus::Enabled(fugit::MicrosDurationU64::millis(1000 as u64));
|
||||
//! use esp_hal::clock::CpuClock;
|
||||
//!
|
||||
//! let config =
|
||||
//! esp_hal::Config::default().with_cpu_clock(CpuClock::max()).
|
||||
//! with_watchdog(esp_hal::config::WatchdogConfig::default().
|
||||
//! with_rwdt(esp_hal::config::WatchdogStatus::Enabled(fugit::MicrosDurationU64::millis(1000u64))));
|
||||
//! let peripherals = esp_hal::init(config);
|
||||
//! # }
|
||||
//! ```
|
||||
@ -42,7 +44,7 @@ pub enum WatchdogStatus {
|
||||
|
||||
/// Watchdog configuration.
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
#[derive(Default, procmacros::BuilderLite)]
|
||||
pub struct WatchdogConfig {
|
||||
#[cfg(not(any(esp32, esp32s2)))]
|
||||
/// Enable the super watchdog timer, which has a trigger time of slightly
|
||||
|
||||
@ -13,8 +13,7 @@
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! This module implements the blocking [DelayMs] and [DelayUs] traits from
|
||||
//! [embedded-hal], both 0.2.x and 1.x.x.
|
||||
//! This module implements the blocking [DelayNs] trait from [embedded-hal].
|
||||
//!
|
||||
//! ## Examples
|
||||
//! ### Delay for 1 second
|
||||
@ -28,8 +27,7 @@
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [DelayMs]: embedded_hal_02::blocking::delay::DelayMs
|
||||
//! [DelayUs]: embedded_hal_02::blocking::delay::DelayUs
|
||||
//! [DelayNs]: https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/trait.DelayNs.html
|
||||
//! [embedded-hal]: https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/index.html
|
||||
//! [now]: crate::time::now
|
||||
|
||||
@ -43,24 +41,6 @@ pub use fugit::MicrosDurationU64;
|
||||
#[non_exhaustive]
|
||||
pub struct Delay;
|
||||
|
||||
impl<T> embedded_hal_02::blocking::delay::DelayMs<T> for Delay
|
||||
where
|
||||
T: Into<u32>,
|
||||
{
|
||||
fn delay_ms(&mut self, ms: T) {
|
||||
self.delay_millis(ms.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_02::blocking::delay::DelayUs<T> for Delay
|
||||
where
|
||||
T: Into<u32>,
|
||||
{
|
||||
fn delay_us(&mut self, us: T) {
|
||||
self.delay_micros(us.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::delay::DelayNs for Delay {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
self.delay_nanos(ns);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,109 +14,83 @@
|
||||
//!
|
||||
//! <em>PS: Note that the number of DMA channels is chip-specific.</em>
|
||||
|
||||
use critical_section::CriticalSection;
|
||||
|
||||
use crate::{
|
||||
dma::*,
|
||||
peripheral::PeripheralRef,
|
||||
interrupt::Priority,
|
||||
macros::handler,
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::Interrupt,
|
||||
system::{Peripheral, PeripheralClockControl},
|
||||
Blocking,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait GdmaChannel {
|
||||
fn number(&self) -> u8;
|
||||
/// An arbitrary GDMA channel
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnyGdmaChannel(u8);
|
||||
|
||||
fn async_handler_out(&self) -> Option<InterruptHandler> {
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
impl Peripheral for AnyGdmaChannel {
|
||||
type P = Self;
|
||||
|
||||
fn peripheral_interrupt_out(&self) -> Option<Interrupt> {
|
||||
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<InterruptHandler> {
|
||||
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<Interrupt> {
|
||||
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!(),
|
||||
}
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// An arbitrary GDMA channel
|
||||
#[non_exhaustive]
|
||||
pub struct AnyGdmaChannel(u8);
|
||||
|
||||
impl crate::private::Sealed for AnyGdmaChannel {}
|
||||
impl DmaChannel for AnyGdmaChannel {
|
||||
type Rx = ChannelRxImpl<Self>;
|
||||
type Tx = ChannelTxImpl<Self>;
|
||||
}
|
||||
type Rx = AnyGdmaRxChannel;
|
||||
type Tx = AnyGdmaTxChannel;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[doc(hidden)]
|
||||
pub struct SpecificGdmaChannel<const N: u8> {}
|
||||
|
||||
impl GdmaChannel for AnyGdmaChannel {
|
||||
fn number(&self) -> u8 {
|
||||
self.0
|
||||
fn set_priority(&self, priority: DmaPriority) {
|
||||
AnyGdmaRxChannel(self.0).set_priority(priority);
|
||||
AnyGdmaTxChannel(self.0).set_priority(priority);
|
||||
}
|
||||
}
|
||||
impl<const N: u8> GdmaChannel for SpecificGdmaChannel<N> {
|
||||
fn number(&self) -> u8 {
|
||||
N
|
||||
|
||||
unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
|
||||
(AnyGdmaRxChannel(self.0), AnyGdmaTxChannel(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[doc(hidden)]
|
||||
pub struct ChannelTxImpl<C: GdmaChannel>(C);
|
||||
/// An arbitrary GDMA RX channel
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnyGdmaRxChannel(u8);
|
||||
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
impl Peripheral for AnyGdmaRxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<AnyGdmaRxChannel> for AnyGdmaRxChannel {
|
||||
fn degrade(self) -> AnyGdmaRxChannel {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An arbitrary GDMA TX channel
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnyGdmaTxChannel(u8);
|
||||
|
||||
impl Peripheral for AnyGdmaTxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<AnyGdmaTxChannel> for AnyGdmaTxChannel {
|
||||
fn degrade(self) -> AnyGdmaTxChannel {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
use crate::asynch::AtomicWaker;
|
||||
|
||||
static TX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
|
||||
static RX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
|
||||
@ -129,51 +103,53 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> crate::private::Sealed for ChannelTxImpl<C> {}
|
||||
impl crate::private::Sealed for AnyGdmaTxChannel {}
|
||||
impl DmaTxChannel for AnyGdmaTxChannel {}
|
||||
|
||||
impl<C: GdmaChannel> ChannelTxImpl<C> {
|
||||
impl AnyGdmaTxChannel {
|
||||
#[inline(always)]
|
||||
fn ch(&self) -> &crate::peripherals::dma::ch::CH {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.ch(self.0.number() as usize)
|
||||
dma.ch(self.0 as usize)
|
||||
}
|
||||
|
||||
#[cfg(any(esp32c2, esp32c3))]
|
||||
#[inline(always)]
|
||||
fn int(&self) -> &crate::peripherals::dma::int_ch::INT_CH {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.int_ch(self.0.number() as usize)
|
||||
dma.int_ch(self.0 as usize)
|
||||
}
|
||||
#[inline(always)]
|
||||
#[cfg(any(esp32c6, esp32h2))]
|
||||
fn int(&self) -> &crate::peripherals::dma::out_int_ch::OUT_INT_CH {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.out_int_ch(self.0.number() as usize)
|
||||
dma.out_int_ch(self.0 as usize)
|
||||
}
|
||||
#[cfg(esp32s3)]
|
||||
#[inline(always)]
|
||||
fn int(&self) -> &crate::peripherals::dma::ch::out_int::OUT_INT {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.ch(self.0.number() as usize).out_int()
|
||||
}
|
||||
|
||||
fn degrade(self) -> ChannelTxImpl<AnyGdmaChannel> {
|
||||
ChannelTxImpl(AnyGdmaChannel(self.0.number()))
|
||||
dma.ch(self.0 as usize).out_int()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> RegisterAccess for ChannelTxImpl<C> {
|
||||
impl RegisterAccess for AnyGdmaTxChannel {
|
||||
fn reset(&self) {
|
||||
let conf0 = self.ch().out_conf0();
|
||||
conf0.modify(|_, w| w.out_rst().set_bit());
|
||||
conf0.modify(|_, w| w.out_rst().clear_bit());
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, burst_mode: bool) {
|
||||
self.ch().out_conf0().modify(|_, w| {
|
||||
w.out_data_burst_en().bit(burst_mode);
|
||||
w.outdscr_burst_en().bit(burst_mode)
|
||||
});
|
||||
fn set_burst_mode(&self, burst_mode: BurstConfig) {
|
||||
self.ch()
|
||||
.out_conf0()
|
||||
.modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
|
||||
}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
self.ch()
|
||||
.out_conf0()
|
||||
.modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_priority(&self, priority: DmaPriority) {
|
||||
@ -224,9 +200,14 @@ impl<C: GdmaChannel> RegisterAccess for ChannelTxImpl<C> {
|
||||
.out_conf1()
|
||||
.modify(|_, w| unsafe { w.out_ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> TxRegisterAccess for ChannelTxImpl<C> {
|
||||
impl TxRegisterAccess for AnyGdmaTxChannel {
|
||||
fn set_auto_write_back(&self, enable: bool) {
|
||||
self.ch()
|
||||
.out_conf0()
|
||||
@ -242,15 +223,37 @@ impl<C: GdmaChannel> TxRegisterAccess for ChannelTxImpl<C> {
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
self.0.async_handler_out()
|
||||
match self.0 {
|
||||
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(&self) -> Option<Interrupt> {
|
||||
self.0.peripheral_interrupt_out()
|
||||
match self.0 {
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> InterruptAccess<DmaTxInterrupt> for ChannelTxImpl<C> {
|
||||
impl InterruptAccess<DmaTxInterrupt> for AnyGdmaTxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
|
||||
self.int().ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
@ -320,13 +323,13 @@ impl<C: GdmaChannel> InterruptAccess<DmaTxInterrupt> for ChannelTxImpl<C> {
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'static AtomicWaker {
|
||||
&TX_WAKERS[self.0.number() as usize]
|
||||
&TX_WAKERS[self.0 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)
|
||||
TX_IS_ASYNC[self.0 as usize].load(portable_atomic::Ordering::Acquire)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -336,63 +339,61 @@ impl<C: GdmaChannel> InterruptAccess<DmaTxInterrupt> for ChannelTxImpl<C> {
|
||||
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);
|
||||
TX_IS_ASYNC[self.0 as usize].store(_is_async, portable_atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[doc(hidden)]
|
||||
pub struct ChannelRxImpl<C: GdmaChannel>(C);
|
||||
impl crate::private::Sealed for AnyGdmaRxChannel {}
|
||||
impl DmaRxChannel for AnyGdmaRxChannel {}
|
||||
|
||||
impl<C: GdmaChannel> crate::private::Sealed for ChannelRxImpl<C> {}
|
||||
|
||||
impl<C: GdmaChannel> ChannelRxImpl<C> {
|
||||
impl AnyGdmaRxChannel {
|
||||
#[inline(always)]
|
||||
fn ch(&self) -> &crate::peripherals::dma::ch::CH {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.ch(self.0.number() as usize)
|
||||
dma.ch(self.0 as usize)
|
||||
}
|
||||
|
||||
#[cfg(any(esp32c2, esp32c3))]
|
||||
#[inline(always)]
|
||||
fn int(&self) -> &crate::peripherals::dma::int_ch::INT_CH {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.int_ch(self.0.number() as usize)
|
||||
dma.int_ch(self.0 as usize)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg(any(esp32c6, esp32h2))]
|
||||
fn int(&self) -> &crate::peripherals::dma::in_int_ch::IN_INT_CH {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.in_int_ch(self.0.number() as usize)
|
||||
dma.in_int_ch(self.0 as usize)
|
||||
}
|
||||
|
||||
#[cfg(esp32s3)]
|
||||
#[inline(always)]
|
||||
fn int(&self) -> &crate::peripherals::dma::ch::in_int::IN_INT {
|
||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||
dma.ch(self.0.number() as usize).in_int()
|
||||
}
|
||||
|
||||
fn degrade(self) -> ChannelRxImpl<AnyGdmaChannel> {
|
||||
ChannelRxImpl(AnyGdmaChannel(self.0.number()))
|
||||
dma.ch(self.0 as usize).in_int()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> RegisterAccess for ChannelRxImpl<C> {
|
||||
impl RegisterAccess for AnyGdmaRxChannel {
|
||||
fn reset(&self) {
|
||||
let conf0 = self.ch().in_conf0();
|
||||
conf0.modify(|_, w| w.in_rst().set_bit());
|
||||
conf0.modify(|_, w| w.in_rst().clear_bit());
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, burst_mode: bool) {
|
||||
self.ch().in_conf0().modify(|_, w| {
|
||||
w.in_data_burst_en().bit(burst_mode);
|
||||
w.indscr_burst_en().bit(burst_mode)
|
||||
});
|
||||
fn set_burst_mode(&self, burst_mode: BurstConfig) {
|
||||
self.ch()
|
||||
.in_conf0()
|
||||
.modify(|_, w| w.in_data_burst_en().bit(burst_mode.is_burst_enabled()));
|
||||
}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
self.ch()
|
||||
.in_conf0()
|
||||
.modify(|_, w| w.indscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_priority(&self, priority: DmaPriority) {
|
||||
@ -441,9 +442,14 @@ impl<C: GdmaChannel> RegisterAccess for ChannelRxImpl<C> {
|
||||
.in_conf1()
|
||||
.modify(|_, w| unsafe { w.in_ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> RxRegisterAccess for ChannelRxImpl<C> {
|
||||
impl RxRegisterAccess for AnyGdmaRxChannel {
|
||||
fn set_mem2mem_mode(&self, value: bool) {
|
||||
self.ch()
|
||||
.in_conf0()
|
||||
@ -451,15 +457,37 @@ impl<C: GdmaChannel> RxRegisterAccess for ChannelRxImpl<C> {
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
self.0.async_handler_in()
|
||||
match self.0 {
|
||||
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(&self) -> Option<Interrupt> {
|
||||
self.0.peripheral_interrupt_in()
|
||||
match self.0 {
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GdmaChannel> InterruptAccess<DmaRxInterrupt> for ChannelRxImpl<C> {
|
||||
impl InterruptAccess<DmaRxInterrupt> for AnyGdmaRxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
|
||||
self.int().ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
@ -537,13 +565,13 @@ impl<C: GdmaChannel> InterruptAccess<DmaRxInterrupt> for ChannelRxImpl<C> {
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'static AtomicWaker {
|
||||
&RX_WAKERS[self.0.number() as usize]
|
||||
&RX_WAKERS[self.0 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)
|
||||
RX_IS_ASYNC[self.0 as usize].load(portable_atomic::Ordering::Acquire)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -553,17 +581,13 @@ impl<C: GdmaChannel> InterruptAccess<DmaRxInterrupt> for ChannelRxImpl<C> {
|
||||
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);
|
||||
RX_IS_ASYNC[self.0 as usize].store(_is_async, portable_atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Channel can be created from this
|
||||
#[non_exhaustive]
|
||||
pub struct ChannelCreator<const N: u8> {}
|
||||
|
||||
impl<CH: DmaChannel, M: Mode> Channel<'_, M, CH> {
|
||||
impl<CH: DmaChannel, Dm: DriverMode> Channel<'_, Dm, CH> {
|
||||
/// Asserts that the channel is compatible with the given peripheral.
|
||||
pub fn runtime_ensure_compatible<P: DmaEligible>(&self, _peripheral: &PeripheralRef<'_, P>) {
|
||||
// No runtime checks; GDMA channels are compatible with any peripheral
|
||||
@ -571,17 +595,56 @@ impl<CH: DmaChannel, M: Mode> Channel<'_, M, CH> {
|
||||
}
|
||||
|
||||
macro_rules! impl_channel {
|
||||
($num:literal, $interrupt_in:ident, $async_handler:path $(, $interrupt_out:ident , $async_handler_out:path)? ) => {
|
||||
($num:literal, $interrupt_in:ident $(, $interrupt_out:ident)? ) => {
|
||||
paste::paste! {
|
||||
/// A description of a specific GDMA channel
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct [<DmaChannel $num>] {}
|
||||
|
||||
impl crate::private::Sealed for [<DmaChannel $num>] {}
|
||||
impl $crate::private::Sealed for [<DmaChannel $num>] {}
|
||||
|
||||
impl Peripheral for [<DmaChannel $num>] {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self::steal()
|
||||
}
|
||||
}
|
||||
|
||||
impl [<DmaChannel $num>] {
|
||||
/// Unsafely constructs a new DMA channel.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that only a single instance is used.
|
||||
pub unsafe fn steal() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl [<DmaChannel $num>] {
|
||||
fn handler_in() -> Option<InterruptHandler> {
|
||||
Some($async_handler)
|
||||
$crate::if_set! {
|
||||
$({
|
||||
// $interrupt_out is present, meaning we have split handlers
|
||||
#[handler(priority = Priority::max())]
|
||||
fn interrupt_handler_in() {
|
||||
$crate::ignore!($interrupt_out);
|
||||
super::asynch::handle_in_interrupt::<[< DmaChannel $num >]>();
|
||||
}
|
||||
Some(interrupt_handler_in)
|
||||
})?,
|
||||
{
|
||||
#[handler(priority = Priority::max())]
|
||||
fn interrupt_handler() {
|
||||
super::asynch::handle_in_interrupt::<[< DmaChannel $num >]>();
|
||||
super::asynch::handle_out_interrupt::<[< DmaChannel $num >]>();
|
||||
}
|
||||
Some(interrupt_handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn isr_in() -> Option<Interrupt> {
|
||||
@ -589,7 +652,17 @@ macro_rules! impl_channel {
|
||||
}
|
||||
|
||||
fn handler_out() -> Option<InterruptHandler> {
|
||||
$crate::if_set! { $(Some($async_handler_out))?, None }
|
||||
$crate::if_set! {
|
||||
$({
|
||||
#[handler(priority = Priority::max())]
|
||||
fn interrupt_handler_out() {
|
||||
$crate::ignore!($interrupt_out);
|
||||
super::asynch::handle_out_interrupt::<[< DmaChannel $num >]>();
|
||||
}
|
||||
Some(interrupt_handler_out)
|
||||
})?,
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn isr_out() -> Option<Interrupt> {
|
||||
@ -598,77 +671,74 @@ macro_rules! impl_channel {
|
||||
}
|
||||
|
||||
impl DmaChannel for [<DmaChannel $num>] {
|
||||
type Rx = ChannelRxImpl<SpecificGdmaChannel<$num>>;
|
||||
type Tx = ChannelTxImpl<SpecificGdmaChannel<$num>>;
|
||||
type Rx = AnyGdmaRxChannel;
|
||||
type Tx = AnyGdmaTxChannel;
|
||||
|
||||
fn set_priority(&self, priority: DmaPriority) {
|
||||
AnyGdmaChannel($num).set_priority(priority);
|
||||
}
|
||||
|
||||
unsafe fn split_internal(self, _: $crate::private::Internal) -> (Self::Rx, Self::Tx) {
|
||||
(AnyGdmaRxChannel($num), AnyGdmaTxChannel($num))
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<AnyGdmaChannel> for [<DmaChannel $num>] {
|
||||
fn degrade_rx(rx: Self::Rx) -> ChannelRxImpl<AnyGdmaChannel> {
|
||||
rx.degrade()
|
||||
fn degrade(self) -> AnyGdmaChannel {
|
||||
AnyGdmaChannel($num)
|
||||
}
|
||||
fn degrade_tx(tx: Self::Tx) -> ChannelTxImpl<AnyGdmaChannel> {
|
||||
tx.degrade()
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<AnyGdmaRxChannel> for [<DmaChannel $num>] {
|
||||
fn degrade(self) -> AnyGdmaRxChannel {
|
||||
AnyGdmaRxChannel($num)
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<AnyGdmaTxChannel> for [<DmaChannel $num>] {
|
||||
fn degrade(self) -> AnyGdmaTxChannel {
|
||||
AnyGdmaTxChannel($num)
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelExt for [<DmaChannel $num>] {
|
||||
fn rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt> {
|
||||
ChannelRxImpl(SpecificGdmaChannel::<$num> {})
|
||||
AnyGdmaRxChannel($num)
|
||||
}
|
||||
|
||||
fn tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
|
||||
ChannelTxImpl(SpecificGdmaChannel::<$num> {})
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelCreator<$num> {
|
||||
/// Configure the channel for use with blocking APIs
|
||||
pub fn configure<'a>(
|
||||
self,
|
||||
burst_mode: bool,
|
||||
priority: DmaPriority,
|
||||
) -> Channel<'a, Blocking, [<DmaChannel $num>]> {
|
||||
let mut this = Channel {
|
||||
tx: ChannelTx::new(ChannelTxImpl(SpecificGdmaChannel::<$num> {})),
|
||||
rx: ChannelRx::new(ChannelRxImpl(SpecificGdmaChannel::<$num> {})),
|
||||
};
|
||||
|
||||
this.configure(burst_mode, priority);
|
||||
|
||||
this
|
||||
AnyGdmaTxChannel($num)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use super::asynch::interrupt as asynch_handler;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(esp32c2)] {
|
||||
const CHANNEL_COUNT: usize = 1;
|
||||
impl_channel!(0, DMA_CH0, asynch_handler::interrupt_handler_ch0);
|
||||
impl_channel!(0, DMA_CH0);
|
||||
} else if #[cfg(esp32c3)] {
|
||||
const CHANNEL_COUNT: usize = 3;
|
||||
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);
|
||||
impl_channel!(0, DMA_CH0);
|
||||
impl_channel!(1, DMA_CH1);
|
||||
impl_channel!(2, DMA_CH2);
|
||||
} else if #[cfg(any(esp32c6, esp32h2))] {
|
||||
const CHANNEL_COUNT: usize = 3;
|
||||
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!(0, DMA_IN_CH0, DMA_OUT_CH0);
|
||||
impl_channel!(1, DMA_IN_CH1, DMA_OUT_CH1);
|
||||
impl_channel!(2, DMA_IN_CH2, DMA_OUT_CH2);
|
||||
} else if #[cfg(esp32s3)] {
|
||||
const CHANNEL_COUNT: usize = 5;
|
||||
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);
|
||||
impl_channel!(0, DMA_IN_CH0, DMA_OUT_CH0);
|
||||
impl_channel!(1, DMA_IN_CH1, DMA_OUT_CH1);
|
||||
impl_channel!(2, DMA_IN_CH2, DMA_OUT_CH2);
|
||||
impl_channel!(3, DMA_IN_CH3, DMA_OUT_CH3);
|
||||
impl_channel!(4, DMA_IN_CH4, DMA_OUT_CH4);
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_dma_eligible! {
|
||||
crate::dma::impl_dma_eligible! {
|
||||
AnyGdmaChannel {
|
||||
#[cfg(spi2)]
|
||||
SPI2 => Spi2,
|
||||
@ -712,7 +782,7 @@ crate::impl_dma_eligible! {
|
||||
}
|
||||
|
||||
#[cfg(any(esp32c6, esp32h2))]
|
||||
crate::impl_dma_eligible! {
|
||||
crate::dma::impl_dma_eligible! {
|
||||
AnyGdmaChannel {
|
||||
MEM2MEM4 => Mem2Mem4,
|
||||
MEM2MEM5 => Mem2Mem5,
|
||||
@ -725,51 +795,10 @@ crate::impl_dma_eligible! {
|
||||
}
|
||||
}
|
||||
|
||||
/// GDMA Peripheral
|
||||
///
|
||||
/// This offers the available DMA channels.
|
||||
pub struct Dma<'d> {
|
||||
_inner: PeripheralRef<'d, crate::peripherals::DMA>,
|
||||
/// Channel 0
|
||||
pub channel0: ChannelCreator<0>,
|
||||
/// Channel 1
|
||||
#[cfg(not(esp32c2))]
|
||||
pub channel1: ChannelCreator<1>,
|
||||
/// Channel 2
|
||||
#[cfg(not(esp32c2))]
|
||||
pub channel2: ChannelCreator<2>,
|
||||
/// Channel 3
|
||||
#[cfg(esp32s3)]
|
||||
pub channel3: ChannelCreator<3>,
|
||||
/// Channel 4
|
||||
#[cfg(esp32s3)]
|
||||
pub channel4: ChannelCreator<4>,
|
||||
}
|
||||
|
||||
impl<'d> Dma<'d> {
|
||||
/// Create a DMA instance.
|
||||
pub fn new(
|
||||
dma: impl crate::peripheral::Peripheral<P = crate::peripherals::DMA> + 'd,
|
||||
) -> Dma<'d> {
|
||||
crate::into_ref!(dma);
|
||||
|
||||
PeripheralClockControl::enable(Peripheral::Gdma);
|
||||
pub(super) fn init_dma(_cs: CriticalSection<'_>) {
|
||||
let dma = unsafe { crate::soc::peripherals::DMA::steal() };
|
||||
dma.misc_conf().modify(|_, w| w.ahbm_rst_inter().set_bit());
|
||||
dma.misc_conf()
|
||||
.modify(|_, w| w.ahbm_rst_inter().clear_bit());
|
||||
dma.misc_conf().modify(|_, w| w.clk_en().set_bit());
|
||||
|
||||
Dma {
|
||||
_inner: dma,
|
||||
channel0: ChannelCreator {},
|
||||
#[cfg(not(esp32c2))]
|
||||
channel1: ChannelCreator {},
|
||||
#[cfg(not(esp32c2))]
|
||||
channel2: ChannelCreator {},
|
||||
#[cfg(esp32s3)]
|
||||
channel3: ChannelCreator {},
|
||||
#[cfg(esp32s3)]
|
||||
channel4: ChannelCreator {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
#[cfg(esp32s3)]
|
||||
use crate::dma::DmaExtMemBKSize;
|
||||
use crate::{
|
||||
dma::{
|
||||
dma_private::{DmaSupport, DmaSupportRx},
|
||||
AnyGdmaChannel,
|
||||
AnyGdmaRxChannel,
|
||||
Channel,
|
||||
ChannelRx,
|
||||
DescriptorChain,
|
||||
@ -18,9 +17,10 @@ use crate::{
|
||||
Tx,
|
||||
WriteBuffer,
|
||||
},
|
||||
peripheral::Peripheral,
|
||||
Async,
|
||||
Blocking,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
/// DMA Memory to Memory pseudo-Peripheral
|
||||
@ -28,11 +28,11 @@ use crate::{
|
||||
/// This is a pseudo-peripheral that allows for memory to memory transfers.
|
||||
/// It is not a real peripheral, but a way to use the DMA engine for memory
|
||||
/// to memory transfers.
|
||||
pub struct Mem2Mem<'d, M>
|
||||
pub struct Mem2Mem<'d, Dm>
|
||||
where
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
channel: Channel<'d, M, AnyGdmaChannel>,
|
||||
channel: Channel<'d, Dm, AnyGdmaChannel>,
|
||||
rx_chain: DescriptorChain,
|
||||
tx_chain: DescriptorChain,
|
||||
peripheral: DmaPeripheral,
|
||||
@ -40,16 +40,14 @@ where
|
||||
|
||||
impl<'d> Mem2Mem<'d, Blocking> {
|
||||
/// Create a new Mem2Mem instance.
|
||||
pub fn new<CH, DM>(
|
||||
channel: Channel<'d, DM, CH>,
|
||||
pub fn new<CH>(
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
peripheral: impl DmaEligible,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
) -> Result<Self, DmaError>
|
||||
where
|
||||
CH: DmaChannelConvert<AnyGdmaChannel>,
|
||||
DM: Mode,
|
||||
Channel<'d, Blocking, CH>: From<Channel<'d, DM, CH>>,
|
||||
{
|
||||
unsafe {
|
||||
Self::new_unsafe(
|
||||
@ -63,8 +61,8 @@ impl<'d> Mem2Mem<'d, Blocking> {
|
||||
}
|
||||
|
||||
/// Create a new Mem2Mem instance with specific chunk size.
|
||||
pub fn new_with_chunk_size<CH, DM>(
|
||||
channel: Channel<'d, DM, CH>,
|
||||
pub fn new_with_chunk_size<CH>(
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
peripheral: impl DmaEligible,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
@ -72,8 +70,6 @@ impl<'d> Mem2Mem<'d, Blocking> {
|
||||
) -> Result<Self, DmaError>
|
||||
where
|
||||
CH: DmaChannelConvert<AnyGdmaChannel>,
|
||||
DM: Mode,
|
||||
Channel<'d, Blocking, CH>: From<Channel<'d, DM, CH>>,
|
||||
{
|
||||
unsafe {
|
||||
Self::new_unsafe(
|
||||
@ -92,8 +88,8 @@ impl<'d> Mem2Mem<'d, Blocking> {
|
||||
///
|
||||
/// 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<CH, DM>(
|
||||
channel: Channel<'d, DM, CH>,
|
||||
pub unsafe fn new_unsafe<CH>(
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
peripheral: DmaPeripheral,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
@ -101,8 +97,6 @@ impl<'d> Mem2Mem<'d, Blocking> {
|
||||
) -> Result<Self, DmaError>
|
||||
where
|
||||
CH: DmaChannelConvert<AnyGdmaChannel>,
|
||||
DM: Mode,
|
||||
Channel<'d, Blocking, CH>: From<Channel<'d, DM, CH>>,
|
||||
{
|
||||
if !(1..=4092).contains(&chunk_size) {
|
||||
return Err(DmaError::InvalidChunkSize);
|
||||
@ -111,7 +105,7 @@ impl<'d> Mem2Mem<'d, Blocking> {
|
||||
return Err(DmaError::OutOfDescriptors);
|
||||
}
|
||||
Ok(Mem2Mem {
|
||||
channel: Channel::<Blocking, _>::from(channel).degrade(),
|
||||
channel: Channel::new(channel.map(|ch| ch.degrade())),
|
||||
peripheral,
|
||||
rx_chain: DescriptorChain::new_with_chunk_size(rx_descriptors, chunk_size),
|
||||
tx_chain: DescriptorChain::new_with_chunk_size(tx_descriptors, chunk_size),
|
||||
@ -129,9 +123,9 @@ impl<'d> Mem2Mem<'d, Blocking> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Mem2Mem<'_, M>
|
||||
impl<Dm> Mem2Mem<'_, Dm>
|
||||
where
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Start a memory to memory transfer.
|
||||
pub fn start_transfer<'t, TXBUF, RXBUF>(
|
||||
@ -156,30 +150,15 @@ where
|
||||
.prepare_transfer_without_start(self.peripheral, &self.rx_chain)?;
|
||||
self.channel.rx.set_mem2mem_mode(true);
|
||||
}
|
||||
#[cfg(esp32s3)]
|
||||
{
|
||||
let align = match unsafe { crate::soc::cache_get_dcache_line_size() } {
|
||||
16 => DmaExtMemBKSize::Size16,
|
||||
32 => DmaExtMemBKSize::Size32,
|
||||
64 => DmaExtMemBKSize::Size64,
|
||||
_ => panic!("unsupported cache line size"),
|
||||
};
|
||||
if crate::soc::is_valid_psram_address(tx_ptr as usize) {
|
||||
self.channel.tx.set_ext_mem_block_size(align);
|
||||
}
|
||||
if crate::soc::is_valid_psram_address(rx_ptr as usize) {
|
||||
self.channel.rx.set_ext_mem_block_size(align);
|
||||
}
|
||||
}
|
||||
self.channel.tx.start_transfer()?;
|
||||
self.channel.rx.start_transfer()?;
|
||||
Ok(DmaTransferRx::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<MODE> DmaSupport for Mem2Mem<'_, MODE>
|
||||
impl<Dm> DmaSupport for Mem2Mem<'_, Dm>
|
||||
where
|
||||
MODE: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
|
||||
while !self.channel.rx.is_done() {}
|
||||
@ -190,11 +169,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, M> DmaSupportRx for Mem2Mem<'d, M>
|
||||
impl<'d, Dm> DmaSupportRx for Mem2Mem<'d, Dm>
|
||||
where
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
type RX = ChannelRx<'d, M, AnyGdmaChannel>;
|
||||
type RX = ChannelRx<'d, Dm, AnyGdmaRxChannel>;
|
||||
|
||||
fn rx(&mut self) -> &mut Self::RX {
|
||||
&mut self.channel.rx
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
532
esp-hal/src/dma/pdma/crypto.rs
Normal file
532
esp-hal/src/dma/pdma/crypto.rs
Normal file
@ -0,0 +1,532 @@
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{
|
||||
asynch::AtomicWaker,
|
||||
dma::*,
|
||||
interrupt::Priority,
|
||||
peripheral::Peripheral,
|
||||
peripherals::Interrupt,
|
||||
};
|
||||
|
||||
pub(super) type CryptoRegisterBlock = crate::peripherals::crypto_dma::RegisterBlock;
|
||||
|
||||
/// The RX half of a Crypto DMA channel.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CryptoDmaRxChannel(pub(crate) CryptoDmaChannel);
|
||||
|
||||
impl crate::private::Sealed for CryptoDmaRxChannel {}
|
||||
impl DmaRxChannel for CryptoDmaRxChannel {}
|
||||
impl Peripheral for CryptoDmaRxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0.clone_unchecked())
|
||||
}
|
||||
}
|
||||
|
||||
/// The TX half of a Crypto DMA channel.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CryptoDmaTxChannel(pub(crate) CryptoDmaChannel);
|
||||
|
||||
impl crate::private::Sealed for CryptoDmaTxChannel {}
|
||||
impl DmaTxChannel for CryptoDmaTxChannel {}
|
||||
impl Peripheral for CryptoDmaTxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0.clone_unchecked())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterAccess for CryptoDmaTxChannel {
|
||||
fn reset(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block.conf().modify(|_, w| {
|
||||
w.out_rst().set_bit();
|
||||
w.ahbm_rst().set_bit();
|
||||
w.ahbm_fifo_rst().set_bit()
|
||||
});
|
||||
register_block.conf().modify(|_, w| {
|
||||
w.out_rst().clear_bit();
|
||||
w.ahbm_rst().clear_bit();
|
||||
w.ahbm_fifo_rst().clear_bit()
|
||||
});
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, burst_mode: BurstConfig) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.conf()
|
||||
.modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
|
||||
}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.conf()
|
||||
.modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_peripheral(&self, peripheral: u8) {
|
||||
use esp32s2::crypto_dma::aes_sha_select::SELECT;
|
||||
let peripheral = match peripheral {
|
||||
p if p == DmaPeripheral::Aes as u8 => SELECT::Aes,
|
||||
p if p == DmaPeripheral::Sha as u8 => SELECT::Sha,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.aes_sha_select()
|
||||
.modify(|_, w| w.select().variant(peripheral));
|
||||
}
|
||||
|
||||
fn set_link_addr(&self, address: u32) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.out_link()
|
||||
.modify(|_, w| unsafe { w.outlink_addr().bits(address) });
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.out_link()
|
||||
.modify(|_, w| w.outlink_start().set_bit());
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.out_link()
|
||||
.modify(|_, w| w.outlink_stop().set_bit());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.out_link()
|
||||
.modify(|_, w| w.outlink_restart().set_bit());
|
||||
}
|
||||
|
||||
fn set_check_owner(&self, check_owner: Option<bool>) {
|
||||
if check_owner == Some(true) {
|
||||
panic!("Crypto DMA does not support checking descriptor ownership");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
|
||||
self.0.is_compatible_with(peripheral)
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.conf1()
|
||||
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl TxRegisterAccess for CryptoDmaTxChannel {
|
||||
fn set_auto_write_back(&self, enable: bool) {
|
||||
// there is no `auto_wrback` for SPI
|
||||
assert!(!enable);
|
||||
}
|
||||
|
||||
fn last_dscr_address(&self) -> usize {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.out_eof_des_addr()
|
||||
.read()
|
||||
.out_eof_des_addr()
|
||||
.bits() as usize
|
||||
}
|
||||
|
||||
fn peripheral_interrupt(&self) -> Option<Interrupt> {
|
||||
None
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptAccess<DmaTxInterrupt> for CryptoDmaTxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.int_ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
match interrupt {
|
||||
DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
|
||||
DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable),
|
||||
DmaTxInterrupt::Eof => w.out_eof().bit(enable),
|
||||
DmaTxInterrupt::Done => w.out_done().bit(enable),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let register_block = self.0.register_block();
|
||||
let int_ena = register_block.int_ena().read();
|
||||
if int_ena.out_total_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::TotalEof;
|
||||
}
|
||||
if int_ena.out_dscr_err().bit_is_set() {
|
||||
result |= DmaTxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_ena.out_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Eof;
|
||||
}
|
||||
if int_ena.out_done().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block.int_clr().write(|w| {
|
||||
for interrupt in interrupts.into() {
|
||||
match interrupt {
|
||||
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
|
||||
DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(),
|
||||
DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
|
||||
DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let register_block = self.0.register_block();
|
||||
let int_raw = register_block.int_raw().read();
|
||||
if int_raw.out_total_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::TotalEof;
|
||||
}
|
||||
if int_raw.out_dscr_err().bit_is_set() {
|
||||
result |= DmaTxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_raw.out_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Eof;
|
||||
}
|
||||
if int_raw.out_done().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'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 CryptoDmaRxChannel {
|
||||
fn reset(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block.conf().modify(|_, w| {
|
||||
w.in_rst().set_bit();
|
||||
w.ahbm_rst().set_bit();
|
||||
w.ahbm_fifo_rst().set_bit()
|
||||
});
|
||||
register_block.conf().modify(|_, w| {
|
||||
w.in_rst().clear_bit();
|
||||
w.ahbm_rst().clear_bit();
|
||||
w.ahbm_fifo_rst().clear_bit()
|
||||
});
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.conf()
|
||||
.modify(|_, w| w.indscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_peripheral(&self, peripheral: u8) {
|
||||
use esp32s2::crypto_dma::aes_sha_select::SELECT;
|
||||
let peripheral = match peripheral {
|
||||
p if p == DmaPeripheral::Aes as u8 => SELECT::Aes,
|
||||
p if p == DmaPeripheral::Sha as u8 => SELECT::Sha,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.aes_sha_select()
|
||||
.modify(|_, w| w.select().variant(peripheral));
|
||||
}
|
||||
|
||||
fn set_link_addr(&self, address: u32) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.in_link()
|
||||
.modify(|_, w| unsafe { w.inlink_addr().bits(address) });
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.in_link()
|
||||
.modify(|_, w| w.inlink_start().set_bit());
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.in_link()
|
||||
.modify(|_, w| w.inlink_stop().set_bit());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.in_link()
|
||||
.modify(|_, w| w.inlink_restart().set_bit());
|
||||
}
|
||||
|
||||
fn set_check_owner(&self, check_owner: Option<bool>) {
|
||||
if check_owner == Some(true) {
|
||||
panic!("Crypto DMA does not support checking descriptor ownership");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
|
||||
self.0.is_compatible_with(peripheral)
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block
|
||||
.conf1()
|
||||
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl RxRegisterAccess for CryptoDmaRxChannel {
|
||||
fn peripheral_interrupt(&self) -> Option<Interrupt> {
|
||||
// We don't know if the channel is used by AES or SHA, so interrupt handler
|
||||
// setup is the responsibility of the peripheral driver.
|
||||
None
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptAccess<DmaRxInterrupt> for CryptoDmaRxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.int_ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
match interrupt {
|
||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
|
||||
DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
|
||||
DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable),
|
||||
DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable),
|
||||
DmaRxInterrupt::Done => w.in_done().bit(enable),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let register_block = self.0.register_block();
|
||||
let int_ena = register_block.int_ena().read();
|
||||
if int_ena.in_dscr_err().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_ena.in_dscr_err().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorEmpty;
|
||||
}
|
||||
if int_ena.in_suc_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::SuccessfulEof;
|
||||
}
|
||||
if int_ena.in_err_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::ErrorEof;
|
||||
}
|
||||
if int_ena.in_done().bit_is_set() {
|
||||
result |= DmaRxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||
let register_block = self.0.register_block();
|
||||
register_block.int_clr().write(|w| {
|
||||
for interrupt in interrupts.into() {
|
||||
match interrupt {
|
||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
|
||||
DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
|
||||
DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(),
|
||||
DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(),
|
||||
DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let register_block = self.0.register_block();
|
||||
let int_raw = register_block.int_raw().read();
|
||||
if int_raw.in_dscr_err().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_raw.in_dscr_empty().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorEmpty;
|
||||
}
|
||||
if int_raw.in_suc_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::SuccessfulEof;
|
||||
}
|
||||
if int_raw.in_err_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::ErrorEof;
|
||||
}
|
||||
if int_raw.in_done().bit_is_set() {
|
||||
result |= DmaRxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'static AtomicWaker {
|
||||
self.0.rx_waker()
|
||||
}
|
||||
|
||||
fn is_async(&self) -> bool {
|
||||
self.0.rx_async_flag().load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn set_async(&self, _is_async: bool) {
|
||||
self.0.rx_async_flag().store(_is_async, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = "DMA channel suitable for CRYPTO"]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CryptoDmaChannel {}
|
||||
|
||||
impl crate::private::Sealed for CryptoDmaChannel {}
|
||||
|
||||
impl Peripheral for CryptoDmaChannel {
|
||||
type P = Self;
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self::steal()
|
||||
}
|
||||
}
|
||||
impl CryptoDmaChannel {
|
||||
#[doc = r" Unsafely constructs a new DMA channel."]
|
||||
#[doc = r""]
|
||||
#[doc = r" # Safety"]
|
||||
#[doc = r""]
|
||||
#[doc = r" The caller must ensure that only a single instance is used."]
|
||||
pub unsafe fn steal() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
impl DmaChannel for CryptoDmaChannel {
|
||||
type Rx = CryptoDmaRxChannel;
|
||||
type Tx = CryptoDmaTxChannel;
|
||||
unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
|
||||
(CryptoDmaRxChannel(Self {}), CryptoDmaTxChannel(Self {}))
|
||||
}
|
||||
}
|
||||
impl DmaChannelExt for CryptoDmaChannel {
|
||||
fn rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt> {
|
||||
CryptoDmaRxChannel(Self {})
|
||||
}
|
||||
fn tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
|
||||
CryptoDmaTxChannel(Self {})
|
||||
}
|
||||
}
|
||||
impl PdmaChannel for CryptoDmaChannel {
|
||||
type RegisterBlock = CryptoRegisterBlock;
|
||||
fn register_block(&self) -> &Self::RegisterBlock {
|
||||
unsafe { &*crate::peripherals::CRYPTO_DMA::PTR }
|
||||
}
|
||||
fn tx_waker(&self) -> &'static AtomicWaker {
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
&WAKER
|
||||
}
|
||||
fn rx_waker(&self) -> &'static AtomicWaker {
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
&WAKER
|
||||
}
|
||||
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
|
||||
let compatible_peripherals = [DmaPeripheral::Aes, DmaPeripheral::Sha];
|
||||
compatible_peripherals.contains(&peripheral)
|
||||
}
|
||||
fn peripheral_interrupt(&self) -> Interrupt {
|
||||
unreachable!("Crypto DMA has separate interrupts specific to AES and SHA")
|
||||
}
|
||||
fn async_handler(&self) -> InterruptHandler {
|
||||
pub(crate) extern "C" fn __esp_hal_internal_interrupt_handler() {
|
||||
super::asynch::handle_in_interrupt::<CryptoDmaChannel>();
|
||||
super::asynch::handle_out_interrupt::<CryptoDmaChannel>();
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub(crate) static interrupt_handler: crate::interrupt::InterruptHandler =
|
||||
crate::interrupt::InterruptHandler::new(
|
||||
__esp_hal_internal_interrupt_handler,
|
||||
Priority::max(),
|
||||
);
|
||||
interrupt_handler
|
||||
}
|
||||
fn rx_async_flag(&self) -> &'static AtomicBool {
|
||||
static FLAG: AtomicBool = AtomicBool::new(false);
|
||||
&FLAG
|
||||
}
|
||||
fn tx_async_flag(&self) -> &'static AtomicBool {
|
||||
static FLAG: AtomicBool = AtomicBool::new(false);
|
||||
&FLAG
|
||||
}
|
||||
}
|
||||
impl DmaChannelConvert<CryptoDmaRxChannel> for CryptoDmaChannel {
|
||||
fn degrade(self) -> CryptoDmaRxChannel {
|
||||
CryptoDmaRxChannel(self)
|
||||
}
|
||||
}
|
||||
impl DmaChannelConvert<CryptoDmaTxChannel> for CryptoDmaChannel {
|
||||
fn degrade(self) -> CryptoDmaTxChannel {
|
||||
CryptoDmaTxChannel(self)
|
||||
}
|
||||
}
|
||||
440
esp-hal/src/dma/pdma/i2s.rs
Normal file
440
esp-hal/src/dma/pdma/i2s.rs
Normal file
@ -0,0 +1,440 @@
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{asynch::AtomicWaker, dma::*, peripheral::Peripheral, peripherals::Interrupt};
|
||||
|
||||
pub(super) type I2sRegisterBlock = crate::peripherals::i2s0::RegisterBlock;
|
||||
|
||||
/// The RX half of an arbitrary I2S DMA channel.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnyI2sDmaRxChannel(pub(crate) AnyI2sDmaChannel);
|
||||
|
||||
impl crate::private::Sealed for AnyI2sDmaRxChannel {}
|
||||
impl DmaRxChannel for AnyI2sDmaRxChannel {}
|
||||
impl Peripheral for AnyI2sDmaRxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0.clone_unchecked())
|
||||
}
|
||||
}
|
||||
|
||||
/// The TX half of an arbitrary I2S DMA channel.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnyI2sDmaTxChannel(pub(crate) AnyI2sDmaChannel);
|
||||
|
||||
impl crate::private::Sealed for AnyI2sDmaTxChannel {}
|
||||
impl DmaTxChannel for AnyI2sDmaTxChannel {}
|
||||
impl Peripheral for AnyI2sDmaTxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0.clone_unchecked())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterAccess for AnyI2sDmaTxChannel {
|
||||
fn reset(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.lc_conf().modify(|_, w| w.out_rst().set_bit());
|
||||
reg_block.lc_conf().modify(|_, w| w.out_rst().clear_bit());
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, burst_mode: BurstConfig) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.lc_conf()
|
||||
.modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
|
||||
}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.lc_conf()
|
||||
.modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_link_addr(&self, address: u32) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.out_link()
|
||||
.modify(|_, w| unsafe { w.outlink_addr().bits(address) });
|
||||
}
|
||||
|
||||
fn set_peripheral(&self, _peripheral: u8) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.out_link()
|
||||
.modify(|_, w| w.outlink_start().set_bit());
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.out_link()
|
||||
.modify(|_, w| w.outlink_stop().set_bit());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.out_link()
|
||||
.modify(|_, w| w.outlink_restart().set_bit());
|
||||
}
|
||||
|
||||
fn set_check_owner(&self, check_owner: Option<bool>) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.lc_conf()
|
||||
.modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true)));
|
||||
}
|
||||
|
||||
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
|
||||
self.0.is_compatible_with(peripheral)
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||
let spi = self.0.register_block();
|
||||
spi.lc_conf()
|
||||
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
matches!(self.0, AnyI2sDmaChannel(AnyI2sDmaChannelInner::I2s0(_)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TxRegisterAccess for AnyI2sDmaTxChannel {
|
||||
fn set_auto_write_back(&self, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.lc_conf()
|
||||
.modify(|_, w| w.out_auto_wrback().bit(enable));
|
||||
}
|
||||
|
||||
fn last_dscr_address(&self) -> usize {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.out_eof_des_addr()
|
||||
.read()
|
||||
.out_eof_des_addr()
|
||||
.bits() as usize
|
||||
}
|
||||
|
||||
fn peripheral_interrupt(&self) -> Option<Interrupt> {
|
||||
None
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptAccess<DmaTxInterrupt> for AnyI2sDmaTxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.int_ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
match interrupt {
|
||||
DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
|
||||
DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable),
|
||||
DmaTxInterrupt::Eof => w.out_eof().bit(enable),
|
||||
DmaTxInterrupt::Done => w.out_done().bit(enable),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let reg_block = self.0.register_block();
|
||||
let int_ena = reg_block.int_ena().read();
|
||||
if int_ena.out_total_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::TotalEof;
|
||||
}
|
||||
if int_ena.out_dscr_err().bit_is_set() {
|
||||
result |= DmaTxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_ena.out_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Eof;
|
||||
}
|
||||
if int_ena.out_done().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let reg_block = self.0.register_block();
|
||||
let int_raw = reg_block.int_raw().read();
|
||||
if int_raw.out_total_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::TotalEof;
|
||||
}
|
||||
if int_raw.out_dscr_err().bit_is_set() {
|
||||
result |= DmaTxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_raw.out_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Eof;
|
||||
}
|
||||
if int_raw.out_done().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.int_clr().write(|w| {
|
||||
for interrupt in interrupts.into() {
|
||||
match interrupt {
|
||||
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
|
||||
DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(),
|
||||
DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
|
||||
DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'static AtomicWaker {
|
||||
self.0.tx_waker()
|
||||
}
|
||||
|
||||
fn is_async(&self) -> bool {
|
||||
self.0.tx_async_flag().load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn set_async(&self, _is_async: bool) {
|
||||
self.0.tx_async_flag().store(_is_async, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterAccess for AnyI2sDmaRxChannel {
|
||||
fn reset(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.lc_conf().modify(|_, w| w.in_rst().set_bit());
|
||||
reg_block.lc_conf().modify(|_, w| w.in_rst().clear_bit());
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.lc_conf()
|
||||
.modify(|_, w| w.indscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_link_addr(&self, address: u32) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.in_link()
|
||||
.modify(|_, w| unsafe { w.inlink_addr().bits(address) });
|
||||
}
|
||||
|
||||
fn set_peripheral(&self, _peripheral: u8) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.in_link()
|
||||
.modify(|_, w| w.inlink_start().set_bit());
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.in_link().modify(|_, w| w.inlink_stop().set_bit());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.in_link()
|
||||
.modify(|_, w| w.inlink_restart().set_bit());
|
||||
}
|
||||
|
||||
fn set_check_owner(&self, check_owner: Option<bool>) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block
|
||||
.lc_conf()
|
||||
.modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true)));
|
||||
}
|
||||
|
||||
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
|
||||
self.0.is_compatible_with(peripheral)
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||
let spi = self.0.register_block();
|
||||
spi.lc_conf()
|
||||
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
matches!(self.0, AnyI2sDmaChannel(AnyI2sDmaChannelInner::I2s0(_)))
|
||||
}
|
||||
}
|
||||
|
||||
impl RxRegisterAccess for AnyI2sDmaRxChannel {
|
||||
fn peripheral_interrupt(&self) -> Option<Interrupt> {
|
||||
Some(self.0.peripheral_interrupt())
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
Some(self.0.async_handler())
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptAccess<DmaRxInterrupt> for AnyI2sDmaRxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.int_ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
match interrupt {
|
||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
|
||||
DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
|
||||
DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable),
|
||||
DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable),
|
||||
DmaRxInterrupt::Done => w.in_done().bit(enable),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let reg_block = self.0.register_block();
|
||||
let int_ena = reg_block.int_ena().read();
|
||||
if int_ena.in_dscr_err().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_ena.in_dscr_empty().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorEmpty;
|
||||
}
|
||||
if int_ena.in_suc_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::SuccessfulEof;
|
||||
}
|
||||
if int_ena.in_err_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::ErrorEof;
|
||||
}
|
||||
if int_ena.in_done().bit_is_set() {
|
||||
result |= DmaRxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let reg_block = self.0.register_block();
|
||||
let int_raw = reg_block.int_raw().read();
|
||||
if int_raw.in_dscr_err().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_raw.in_dscr_empty().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorEmpty;
|
||||
}
|
||||
if int_raw.in_suc_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::SuccessfulEof;
|
||||
}
|
||||
if int_raw.in_err_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::ErrorEof;
|
||||
}
|
||||
if int_raw.in_done().bit_is_set() {
|
||||
result |= DmaRxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.int_clr().write(|w| {
|
||||
for interrupt in interrupts.into() {
|
||||
match interrupt {
|
||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
|
||||
DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
|
||||
DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(),
|
||||
DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(),
|
||||
DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'static AtomicWaker {
|
||||
self.0.rx_waker()
|
||||
}
|
||||
|
||||
fn is_async(&self) -> bool {
|
||||
self.0.rx_async_flag().load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn set_async(&self, _is_async: bool) {
|
||||
self.0.rx_async_flag().store(_is_async, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
crate::any_peripheral! {
|
||||
/// An I2S-compatible type-erased DMA channel.
|
||||
pub peripheral AnyI2sDmaChannel {
|
||||
I2s0(I2s0DmaChannel),
|
||||
#[cfg(i2s1)]
|
||||
I2s1(I2s1DmaChannel),
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannel for AnyI2sDmaChannel {
|
||||
type Rx = AnyI2sDmaRxChannel;
|
||||
type Tx = AnyI2sDmaTxChannel;
|
||||
|
||||
unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
|
||||
(
|
||||
AnyI2sDmaRxChannel(unsafe { self.clone_unchecked() }),
|
||||
AnyI2sDmaTxChannel(unsafe { self.clone_unchecked() }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PdmaChannel for AnyI2sDmaChannel {
|
||||
type RegisterBlock = I2sRegisterBlock;
|
||||
|
||||
delegate::delegate! {
|
||||
to match &self.0 {
|
||||
AnyI2sDmaChannelInner::I2s0(channel) => channel,
|
||||
#[cfg(i2s1)]
|
||||
AnyI2sDmaChannelInner::I2s1(channel) => channel,
|
||||
} {
|
||||
fn register_block(&self) -> &I2sRegisterBlock;
|
||||
fn tx_waker(&self) -> &'static AtomicWaker;
|
||||
fn rx_waker(&self) -> &'static AtomicWaker;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
212
esp-hal/src/dma/pdma/mod.rs
Normal file
212
esp-hal/src/dma/pdma/mod.rs
Normal file
@ -0,0 +1,212 @@
|
||||
//! # Direct Memory Access
|
||||
//!
|
||||
//! ## Overview
|
||||
//! The `pdma` module is part of the DMA driver of `ESP32` and `ESP32-S2`.
|
||||
//!
|
||||
//! This module provides efficient direct data transfer capabilities between
|
||||
//! peripherals and memory without involving the CPU. It enables bidirectional
|
||||
//! data transfers through DMA channels, making it particularly useful for
|
||||
//! high-speed data transfers, such as [SPI] and [I2S] communication.
|
||||
//!
|
||||
//! [SPI]: ../spi/index.html
|
||||
//! [I2S]: ../i2s/index.html
|
||||
|
||||
use critical_section::CriticalSection;
|
||||
use portable_atomic::AtomicBool;
|
||||
|
||||
use crate::{
|
||||
asynch::AtomicWaker,
|
||||
dma::*,
|
||||
interrupt::Priority,
|
||||
macros::handler,
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::Interrupt,
|
||||
};
|
||||
|
||||
#[cfg(esp32s2)]
|
||||
mod crypto;
|
||||
mod i2s;
|
||||
mod spi;
|
||||
|
||||
#[cfg(esp32s2)]
|
||||
pub use crypto::*;
|
||||
pub use i2s::*;
|
||||
pub use spi::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait PdmaChannel: crate::private::Sealed {
|
||||
type RegisterBlock;
|
||||
|
||||
fn register_block(&self) -> &Self::RegisterBlock;
|
||||
fn tx_waker(&self) -> &'static AtomicWaker;
|
||||
fn rx_waker(&self) -> &'static AtomicWaker;
|
||||
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;
|
||||
}
|
||||
|
||||
macro_rules! impl_pdma_channel {
|
||||
($peri:ident, $register_block:ident, $instance:ident, $int:ident, [$($compatible:ident),*]) => {
|
||||
paste::paste! {
|
||||
#[doc = concat!("DMA channel suitable for ", stringify!([< $instance:upper >]))]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct [<$instance DmaChannel>] {}
|
||||
|
||||
impl $crate::private::Sealed for [<$instance DmaChannel>] {}
|
||||
|
||||
impl Peripheral for [<$instance DmaChannel>] {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self::steal()
|
||||
}
|
||||
}
|
||||
|
||||
impl [<$instance DmaChannel>] {
|
||||
/// Unsafely constructs a new DMA channel.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that only a single instance is used.
|
||||
pub unsafe fn steal() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannel for [<$instance DmaChannel>] {
|
||||
type Rx = [<$peri DmaRxChannel>];
|
||||
type Tx = [<$peri DmaTxChannel>];
|
||||
|
||||
unsafe fn split_internal(self, _: $crate::private::Internal) -> (Self::Rx, Self::Tx) {
|
||||
([<$peri DmaRxChannel>](Self {}.into()), [<$peri DmaTxChannel>](Self {}.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelExt for [<$instance DmaChannel>] {
|
||||
fn rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt> {
|
||||
[<$peri DmaRxChannel>](Self {}.into())
|
||||
}
|
||||
fn tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
|
||||
[<$peri DmaTxChannel>](Self {}.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PdmaChannel for [<$instance DmaChannel>] {
|
||||
type RegisterBlock = $register_block;
|
||||
|
||||
fn register_block(&self) -> &Self::RegisterBlock {
|
||||
unsafe { &*crate::peripherals::[< $instance:upper >]::PTR }
|
||||
}
|
||||
fn tx_waker(&self) -> &'static AtomicWaker {
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
&WAKER
|
||||
}
|
||||
fn rx_waker(&self) -> &'static AtomicWaker {
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
&WAKER
|
||||
}
|
||||
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
|
||||
let compatible_peripherals = [$(DmaPeripheral::$compatible),*];
|
||||
compatible_peripherals.contains(&peripheral)
|
||||
}
|
||||
|
||||
fn peripheral_interrupt(&self) -> Interrupt {
|
||||
Interrupt::$int
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> InterruptHandler {
|
||||
#[handler(priority = Priority::max())]
|
||||
pub(crate) fn interrupt_handler() {
|
||||
super::asynch::handle_in_interrupt::<[< $instance DmaChannel >]>();
|
||||
super::asynch::handle_out_interrupt::<[< $instance DmaChannel >]>();
|
||||
}
|
||||
|
||||
interrupt_handler
|
||||
}
|
||||
fn rx_async_flag(&self) -> &'static AtomicBool {
|
||||
static FLAG: AtomicBool = AtomicBool::new(false);
|
||||
&FLAG
|
||||
}
|
||||
fn tx_async_flag(&self) -> &'static AtomicBool {
|
||||
static FLAG: AtomicBool = AtomicBool::new(false);
|
||||
&FLAG
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<[<$peri DmaChannel>]> for [<$instance DmaChannel>] {
|
||||
fn degrade(self) -> [<$peri DmaChannel>] {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<[<$peri DmaRxChannel>]> for [<$instance DmaChannel>] {
|
||||
fn degrade(self) -> [<$peri DmaRxChannel>] {
|
||||
[<$peri DmaRxChannel>](self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannelConvert<[<$peri DmaTxChannel>]> for [<$instance DmaChannel>] {
|
||||
fn degrade(self) -> [<$peri DmaTxChannel>] {
|
||||
[<$peri DmaTxChannel>](self.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pdma_channel!(AnySpi, SpiRegisterBlock, Spi2, SPI2_DMA, [Spi2]);
|
||||
impl_pdma_channel!(AnySpi, SpiRegisterBlock, Spi3, SPI3_DMA, [Spi3]);
|
||||
|
||||
impl_pdma_channel!(AnyI2s, I2sRegisterBlock, I2s0, I2S0, [I2s0]);
|
||||
#[cfg(i2s1)]
|
||||
impl_pdma_channel!(AnyI2s, I2sRegisterBlock, I2s1, I2S1, [I2s1]);
|
||||
|
||||
// Specific peripherals use specific channels. Note that this may be overly
|
||||
// restrictive (ESP32 allows configuring 2 SPI DMA channels between 3 different
|
||||
// peripherals), but for the current set of restrictions this is sufficient.
|
||||
crate::dma::impl_dma_eligible!([Spi2DmaChannel] SPI2 => Spi2);
|
||||
crate::dma::impl_dma_eligible!([Spi3DmaChannel] SPI3 => Spi3);
|
||||
crate::dma::impl_dma_eligible!([I2s0DmaChannel] I2S0 => I2s0);
|
||||
#[cfg(i2s1)]
|
||||
crate::dma::impl_dma_eligible!([I2s1DmaChannel] I2S1 => I2s1);
|
||||
#[cfg(esp32s2)]
|
||||
crate::dma::impl_dma_eligible!([CryptoDmaChannel] AES => Aes);
|
||||
#[cfg(esp32s2)]
|
||||
crate::dma::impl_dma_eligible!([CryptoDmaChannel] SHA => Sha);
|
||||
|
||||
pub(super) fn init_dma(_cs: CriticalSection<'_>) {
|
||||
#[cfg(esp32)]
|
||||
{
|
||||
// (only) on ESP32 we need to configure DPORT for the SPI DMA channels
|
||||
// This assignes the DMA channels to the SPI peripherals, which is more
|
||||
// restrictive than necessary but we currently support the same
|
||||
// number of SPI peripherals as SPI DMA channels so it's not a big
|
||||
// deal.
|
||||
let dport = unsafe { crate::peripherals::DPORT::steal() };
|
||||
dport
|
||||
.spi_dma_chan_sel()
|
||||
.modify(|_, w| unsafe { w.spi2_dma_chan_sel().bits(1).spi3_dma_chan_sel().bits(2) });
|
||||
}
|
||||
}
|
||||
|
||||
impl<CH, Dm> Channel<'_, Dm, CH>
|
||||
where
|
||||
CH: DmaChannel,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Asserts that the channel is compatible with the given peripheral.
|
||||
pub fn runtime_ensure_compatible(&self, peripheral: &PeripheralRef<'_, impl DmaEligible>) {
|
||||
assert!(
|
||||
self.tx
|
||||
.tx_impl
|
||||
.is_compatible_with(peripheral.dma_peripheral()),
|
||||
"This DMA channel is not compatible with {:?}",
|
||||
peripheral.dma_peripheral()
|
||||
);
|
||||
}
|
||||
}
|
||||
418
esp-hal/src/dma/pdma/spi.rs
Normal file
418
esp-hal/src/dma/pdma/spi.rs
Normal file
@ -0,0 +1,418 @@
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{asynch::AtomicWaker, dma::*, peripheral::Peripheral, peripherals::Interrupt};
|
||||
|
||||
pub(super) type SpiRegisterBlock = crate::peripherals::spi2::RegisterBlock;
|
||||
|
||||
/// The RX half of an arbitrary SPI DMA channel.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnySpiDmaRxChannel(pub(crate) AnySpiDmaChannel);
|
||||
|
||||
impl crate::private::Sealed for AnySpiDmaRxChannel {}
|
||||
impl DmaRxChannel for AnySpiDmaRxChannel {}
|
||||
impl Peripheral for AnySpiDmaRxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0.clone_unchecked())
|
||||
}
|
||||
}
|
||||
|
||||
/// The TX half of an arbitrary SPI DMA channel.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AnySpiDmaTxChannel(pub(crate) AnySpiDmaChannel);
|
||||
|
||||
impl crate::private::Sealed for AnySpiDmaTxChannel {}
|
||||
impl DmaTxChannel for AnySpiDmaTxChannel {}
|
||||
impl Peripheral for AnySpiDmaTxChannel {
|
||||
type P = Self;
|
||||
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
Self(self.0.clone_unchecked())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterAccess for AnySpiDmaTxChannel {
|
||||
fn reset(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf().modify(|_, w| w.out_rst().set_bit());
|
||||
spi.dma_conf().modify(|_, w| w.out_rst().clear_bit());
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, burst_mode: BurstConfig) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf()
|
||||
.modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
|
||||
}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf()
|
||||
.modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_peripheral(&self, _peripheral: u8) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
fn set_link_addr(&self, address: u32) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_out_link()
|
||||
.modify(|_, w| unsafe { w.outlink_addr().bits(address) });
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_out_link()
|
||||
.modify(|_, w| w.outlink_start().set_bit());
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_out_link().modify(|_, w| w.outlink_stop().set_bit());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_out_link()
|
||||
.modify(|_, w| w.outlink_restart().set_bit());
|
||||
}
|
||||
|
||||
fn set_check_owner(&self, check_owner: Option<bool>) {
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf()
|
||||
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
matches!(self.0, AnySpiDmaChannel(AnySpiDmaChannelInner::Spi2(_)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TxRegisterAccess for AnySpiDmaTxChannel {
|
||||
fn set_auto_write_back(&self, enable: bool) {
|
||||
// there is no `auto_wrback` for SPI
|
||||
assert!(!enable);
|
||||
}
|
||||
|
||||
fn last_dscr_address(&self) -> usize {
|
||||
let spi = self.0.register_block();
|
||||
spi.out_eof_des_addr().read().dma_out_eof_des_addr().bits() as usize
|
||||
}
|
||||
|
||||
fn peripheral_interrupt(&self) -> Option<Interrupt> {
|
||||
None
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptAccess<DmaTxInterrupt> for AnySpiDmaTxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.dma_int_ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
match interrupt {
|
||||
DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
|
||||
DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().bit(enable),
|
||||
DmaTxInterrupt::Eof => w.out_eof().bit(enable),
|
||||
DmaTxInterrupt::Done => w.out_done().bit(enable),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let spi = self.0.register_block();
|
||||
let int_ena = spi.dma_int_ena().read();
|
||||
if int_ena.out_total_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::TotalEof;
|
||||
}
|
||||
if int_ena.outlink_dscr_error().bit_is_set() {
|
||||
result |= DmaTxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_ena.out_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Eof;
|
||||
}
|
||||
if int_ena.out_done().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_int_clr().write(|w| {
|
||||
for interrupt in interrupts.into() {
|
||||
match interrupt {
|
||||
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
|
||||
DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().clear_bit_by_one(),
|
||||
DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
|
||||
DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let spi = self.0.register_block();
|
||||
let int_raw = spi.dma_int_raw().read();
|
||||
if int_raw.out_total_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::TotalEof;
|
||||
}
|
||||
if int_raw.outlink_dscr_error().bit_is_set() {
|
||||
result |= DmaTxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_raw.out_eof().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Eof;
|
||||
}
|
||||
if int_raw.out_done().bit_is_set() {
|
||||
result |= DmaTxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'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 AnySpiDmaRxChannel {
|
||||
fn reset(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf().modify(|_, w| w.in_rst().set_bit());
|
||||
spi.dma_conf().modify(|_, w| w.in_rst().clear_bit());
|
||||
}
|
||||
|
||||
fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
|
||||
|
||||
fn set_descr_burst_mode(&self, burst_mode: bool) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf()
|
||||
.modify(|_, w| w.indscr_burst_en().bit(burst_mode));
|
||||
}
|
||||
|
||||
fn set_peripheral(&self, _peripheral: u8) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
fn set_link_addr(&self, address: u32) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_in_link()
|
||||
.modify(|_, w| unsafe { w.inlink_addr().bits(address) });
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_in_link().modify(|_, w| w.inlink_start().set_bit());
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_in_link().modify(|_, w| w.inlink_stop().set_bit());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_in_link()
|
||||
.modify(|_, w| w.inlink_restart().set_bit());
|
||||
}
|
||||
|
||||
fn set_check_owner(&self, check_owner: Option<bool>) {
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_conf()
|
||||
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
|
||||
}
|
||||
|
||||
#[cfg(psram_dma)]
|
||||
fn can_access_psram(&self) -> bool {
|
||||
matches!(self.0, AnySpiDmaChannel(AnySpiDmaChannelInner::Spi2(_)))
|
||||
}
|
||||
}
|
||||
|
||||
impl RxRegisterAccess for AnySpiDmaRxChannel {
|
||||
fn peripheral_interrupt(&self) -> Option<Interrupt> {
|
||||
Some(self.0.peripheral_interrupt())
|
||||
}
|
||||
|
||||
fn async_handler(&self) -> Option<InterruptHandler> {
|
||||
Some(self.0.async_handler())
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptAccess<DmaRxInterrupt> for AnySpiDmaRxChannel {
|
||||
fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
|
||||
let reg_block = self.0.register_block();
|
||||
reg_block.dma_int_ena().modify(|_, w| {
|
||||
for interrupt in interrupts {
|
||||
match interrupt {
|
||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
|
||||
DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
|
||||
DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().bit(enable),
|
||||
DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().bit(enable),
|
||||
DmaRxInterrupt::Done => w.in_done().bit(enable),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let spi = self.0.register_block();
|
||||
let int_ena = spi.dma_int_ena().read();
|
||||
if int_ena.inlink_dscr_error().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_ena.inlink_dscr_empty().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorEmpty;
|
||||
}
|
||||
if int_ena.in_suc_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::SuccessfulEof;
|
||||
}
|
||||
if int_ena.in_err_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::ErrorEof;
|
||||
}
|
||||
if int_ena.in_done().bit_is_set() {
|
||||
result |= DmaRxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||
let spi = self.0.register_block();
|
||||
spi.dma_int_clr().modify(|_, w| {
|
||||
for interrupt in interrupts.into() {
|
||||
match interrupt {
|
||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
|
||||
DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
|
||||
DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().clear_bit_by_one(),
|
||||
DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().clear_bit_by_one(),
|
||||
DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
|
||||
};
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
|
||||
let mut result = EnumSet::new();
|
||||
|
||||
let spi = self.0.register_block();
|
||||
let int_raw = spi.dma_int_raw().read();
|
||||
if int_raw.inlink_dscr_error().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorError;
|
||||
}
|
||||
if int_raw.inlink_dscr_empty().bit_is_set() {
|
||||
result |= DmaRxInterrupt::DescriptorEmpty;
|
||||
}
|
||||
if int_raw.in_suc_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::SuccessfulEof;
|
||||
}
|
||||
if int_raw.in_err_eof().bit_is_set() {
|
||||
result |= DmaRxInterrupt::ErrorEof;
|
||||
}
|
||||
if int_raw.in_done().bit_is_set() {
|
||||
result |= DmaRxInterrupt::Done;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn waker(&self) -> &'static AtomicWaker {
|
||||
self.0.rx_waker()
|
||||
}
|
||||
|
||||
fn is_async(&self) -> bool {
|
||||
self.0.rx_async_flag().load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn set_async(&self, _is_async: bool) {
|
||||
self.0.rx_async_flag().store(_is_async, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
crate::any_peripheral! {
|
||||
/// An SPI-compatible type-erased DMA channel.
|
||||
pub peripheral AnySpiDmaChannel {
|
||||
Spi2(Spi2DmaChannel),
|
||||
Spi3(Spi3DmaChannel),
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaChannel for AnySpiDmaChannel {
|
||||
type Rx = AnySpiDmaRxChannel;
|
||||
type Tx = AnySpiDmaTxChannel;
|
||||
|
||||
unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
|
||||
(
|
||||
AnySpiDmaRxChannel(unsafe { self.clone_unchecked() }),
|
||||
AnySpiDmaTxChannel(unsafe { self.clone_unchecked() }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PdmaChannel for AnySpiDmaChannel {
|
||||
type RegisterBlock = SpiRegisterBlock;
|
||||
|
||||
delegate::delegate! {
|
||||
to match &self.0 {
|
||||
AnySpiDmaChannelInner::Spi2(channel) => channel,
|
||||
AnySpiDmaChannelInner::Spi3(channel) => channel,
|
||||
} {
|
||||
fn register_block(&self) -> &SpiRegisterBlock;
|
||||
fn tx_waker(&self) -> &'static AtomicWaker;
|
||||
fn rx_waker(&self) -> &'static AtomicWaker;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,19 +28,19 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
interrupt::InterruptHandler,
|
||||
interrupt::{InterruptConfigurable, InterruptHandler},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::{Interrupt, ECC},
|
||||
reg_access::{AlignmentHelper, SocDependentEndianess},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
InterruptConfigurable,
|
||||
system::{self, GenericPeripheralGuard},
|
||||
};
|
||||
|
||||
/// The ECC Accelerator driver instance
|
||||
pub struct Ecc<'d, DM: crate::Mode> {
|
||||
pub struct Ecc<'d, Dm: crate::DriverMode> {
|
||||
ecc: PeripheralRef<'d, ECC>,
|
||||
alignment_helper: AlignmentHelper<SocDependentEndianess>,
|
||||
phantom: PhantomData<DM>,
|
||||
phantom: PhantomData<Dm>,
|
||||
_guard: GenericPeripheralGuard<{ system::Peripheral::Ecc as u8 }>,
|
||||
}
|
||||
|
||||
/// ECC interface error
|
||||
@ -102,13 +102,13 @@ impl<'d> Ecc<'d, crate::Blocking> {
|
||||
pub fn new(ecc: impl Peripheral<P = ECC> + 'd) -> Self {
|
||||
crate::into_ref!(ecc);
|
||||
|
||||
PeripheralClockControl::reset(PeripheralEnable::Ecc);
|
||||
PeripheralClockControl::enable(PeripheralEnable::Ecc);
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
Self {
|
||||
ecc,
|
||||
alignment_helper: AlignmentHelper::default(),
|
||||
phantom: PhantomData,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,7 @@ impl InterruptConfigurable for Ecc<'_, crate::Blocking> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<DM: crate::Mode> Ecc<'_, DM> {
|
||||
impl<Dm: crate::DriverMode> Ecc<'_, Dm> {
|
||||
/// Resets the ECC peripheral.
|
||||
pub fn reset(&mut self) {
|
||||
self.ecc.mult_conf().reset()
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
//! For more information, please refer to the
|
||||
#, "/api-reference/peripherals/etm.html)")]
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Control LED by the button via ETM
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::gpio::etm::{Channels, InputConfig, OutputConfig};
|
||||
@ -56,10 +58,53 @@
|
||||
//! loop {}
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Control LED by the systimer via ETM
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::gpio::etm::{Channels, InputConfig, OutputConfig};
|
||||
//! # use esp_hal::etm::Etm;
|
||||
//! # use esp_hal::gpio::Pull;
|
||||
//! # use esp_hal::gpio::Level;
|
||||
//! # use esp_hal::timer::systimer::{etm::Event, SystemTimer};
|
||||
//! # use esp_hal::timer::PeriodicTimer;
|
||||
//! # use fugit::ExtU32;
|
||||
//!
|
||||
//! let syst = SystemTimer::new(peripherals.SYSTIMER);
|
||||
//! let mut alarm0 = syst.alarm0;
|
||||
//! let mut timer = PeriodicTimer::new(&mut alarm0);
|
||||
//! timer.start(1u64.secs());
|
||||
//!
|
||||
//! let mut led = peripherals.GPIO1;
|
||||
//!
|
||||
//! // setup ETM
|
||||
//! let gpio_ext = Channels::new(peripherals.GPIO_SD);
|
||||
//! let led_task = gpio_ext.channel0_task.toggle(
|
||||
//! &mut led,
|
||||
//! OutputConfig {
|
||||
//! open_drain: false,
|
||||
//! pull: Pull::None,
|
||||
//! initial_state: Level::Low,
|
||||
//! },
|
||||
//! );
|
||||
//!
|
||||
//! let timer_event = Event::new(&mut alarm0);
|
||||
//!
|
||||
//! let etm = Etm::new(peripherals.SOC_ETM);
|
||||
//! let channel0 = etm.channel0;
|
||||
//!
|
||||
//! // make sure the configured channel doesn't get dropped - dropping it will
|
||||
//! // disable the channel
|
||||
//! let _configured_channel = channel0.setup(&timer_event, &led_task);
|
||||
//!
|
||||
//! // the LED is controlled by the timer without involving the CPU
|
||||
//! loop {}
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use crate::{
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
system::PeripheralClockControl,
|
||||
system::GenericPeripheralGuard,
|
||||
};
|
||||
|
||||
/// Unconfigured EtmChannel.
|
||||
@ -76,6 +121,7 @@ impl<const C: u8> EtmChannel<C> {
|
||||
T: EtmTask,
|
||||
{
|
||||
let etm = unsafe { crate::peripherals::SOC_ETM::steal() };
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
etm.ch(C as usize)
|
||||
.evt_id()
|
||||
@ -92,6 +138,7 @@ impl<const C: u8> EtmChannel<C> {
|
||||
EtmConfiguredChannel {
|
||||
_event: event,
|
||||
_task: task,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,6 +164,7 @@ where
|
||||
{
|
||||
_event: &'a E,
|
||||
_task: &'a T,
|
||||
_guard: GenericPeripheralGuard<{ crate::system::Peripheral::Etm as u8 }>,
|
||||
}
|
||||
|
||||
impl<E, T, const C: u8> Drop for EtmConfiguredChannel<'_, E, T, C>
|
||||
@ -125,7 +173,7 @@ where
|
||||
T: EtmTask,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
debug!("drop {}", C);
|
||||
debug!("Drop ETM channel {}", C);
|
||||
disable_channel(C);
|
||||
}
|
||||
}
|
||||
@ -149,12 +197,9 @@ macro_rules! create_etm {
|
||||
pub fn new(peripheral: impl Peripheral<P = crate::peripherals::SOC_ETM> + 'd) -> Self {
|
||||
crate::into_ref!(peripheral);
|
||||
|
||||
PeripheralClockControl::reset(crate::system::Peripheral::Etm);
|
||||
PeripheralClockControl::enable(crate::system::Peripheral::Etm);
|
||||
|
||||
Self {
|
||||
_peripheral: peripheral,
|
||||
$([< channel $num >]: EtmChannel {},)+
|
||||
$([< channel $num >]: EtmChannel { },)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,21 +28,21 @@ use crate::{
|
||||
///
|
||||
/// Peripheral drivers are encouraged to accept types that implement this and
|
||||
/// [`PeripheralOutput`] as arguments instead of pin types.
|
||||
pub trait PeripheralInput: Into<InputConnection> + 'static {}
|
||||
pub trait PeripheralInput: Into<InputConnection> + 'static + crate::private::Sealed {}
|
||||
|
||||
/// A signal that can be connected to a peripheral input and/or output.
|
||||
///
|
||||
/// Peripheral drivers are encouraged to accept types that implement this and
|
||||
/// [`PeripheralInput`] as arguments instead of pin types.
|
||||
pub trait PeripheralOutput: Into<OutputConnection> + 'static {}
|
||||
pub trait PeripheralOutput: Into<OutputConnection> + 'static + crate::private::Sealed {}
|
||||
|
||||
// Pins
|
||||
impl<P: InputPin> PeripheralInput for P {}
|
||||
impl<P: OutputPin> PeripheralOutput for P {}
|
||||
|
||||
// Pin drivers
|
||||
impl<P: InputPin> PeripheralInput for Flex<'static, P> {}
|
||||
impl<P: OutputPin> PeripheralOutput for Flex<'static, P> {}
|
||||
impl PeripheralInput for Flex<'static> {}
|
||||
impl PeripheralOutput for Flex<'static> {}
|
||||
|
||||
// Placeholders
|
||||
impl PeripheralInput for NoPin {}
|
||||
@ -212,6 +212,7 @@ fn disconnect_peripheral_output_from_pin(pin: &mut AnyPin, signal: gpio::OutputS
|
||||
/// A configurable input signal between a peripheral and a GPIO pin.
|
||||
///
|
||||
/// Multiple input signals can be connected to one pin.
|
||||
#[instability::unstable]
|
||||
pub struct InputSignal {
|
||||
pin: AnyPin,
|
||||
is_inverted: bool,
|
||||
@ -226,11 +227,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<Flex<'static, P>> for InputSignal
|
||||
where
|
||||
P: InputPin,
|
||||
{
|
||||
fn from(input: Flex<'static, P>) -> Self {
|
||||
impl From<Flex<'static>> for InputSignal {
|
||||
fn from(input: Flex<'static>) -> Self {
|
||||
Self::new(input.degrade())
|
||||
}
|
||||
}
|
||||
@ -301,7 +299,7 @@ impl InputSignal {
|
||||
#[doc(hidden)]
|
||||
to self.pin {
|
||||
pub fn pull_direction(&self, pull: Pull, _internal: private::Internal);
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::InputSignal)];
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
|
||||
pub fn init_input(&self, pull: Pull, _internal: private::Internal);
|
||||
pub fn is_input_high(&self, _internal: private::Internal) -> bool;
|
||||
pub fn enable_input(&mut self, on: bool, _internal: private::Internal);
|
||||
@ -339,7 +337,7 @@ impl DirectInputSignal {
|
||||
delegate::delegate! {
|
||||
to self.pin {
|
||||
fn pull_direction(&self, pull: Pull, _internal: private::Internal);
|
||||
fn input_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::InputSignal)];
|
||||
fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
|
||||
fn init_input(&self, pull: Pull, _internal: private::Internal);
|
||||
fn is_input_high(&self, _internal: private::Internal) -> bool;
|
||||
fn enable_input(&mut self, on: bool, _internal: private::Internal);
|
||||
@ -350,6 +348,7 @@ impl DirectInputSignal {
|
||||
/// A configurable output signal between a peripheral and a GPIO pin.
|
||||
///
|
||||
/// Multiple pins can be connected to one output signal.
|
||||
#[instability::unstable]
|
||||
pub struct OutputSignal {
|
||||
pin: AnyPin,
|
||||
is_inverted: bool,
|
||||
@ -364,11 +363,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<Flex<'static, P>> for OutputSignal
|
||||
where
|
||||
P: OutputPin,
|
||||
{
|
||||
fn from(input: Flex<'static, P>) -> Self {
|
||||
impl From<Flex<'static>> for OutputSignal {
|
||||
fn from(input: Flex<'static>) -> Self {
|
||||
Self::new(input.degrade())
|
||||
}
|
||||
}
|
||||
@ -433,12 +429,12 @@ impl OutputSignal {
|
||||
#[doc(hidden)]
|
||||
to self.pin {
|
||||
pub fn pull_direction(&self, pull: Pull, _internal: private::Internal);
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::InputSignal)];
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
|
||||
pub fn init_input(&self, pull: Pull, _internal: private::Internal);
|
||||
pub fn is_input_high(&self, _internal: private::Internal) -> bool;
|
||||
pub fn enable_input(&mut self, on: bool, _internal: private::Internal);
|
||||
|
||||
pub fn output_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::OutputSignal)];
|
||||
pub fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
|
||||
pub fn set_to_open_drain_output(&mut self, _internal: private::Internal);
|
||||
pub fn set_to_push_pull_output(&mut self, _internal: private::Internal);
|
||||
pub fn enable_output(&mut self, on: bool, _internal: private::Internal);
|
||||
@ -481,12 +477,12 @@ impl DirectOutputSignal {
|
||||
delegate::delegate! {
|
||||
to self.pin {
|
||||
fn pull_direction(&self, pull: Pull, _internal: private::Internal);
|
||||
fn input_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::InputSignal)];
|
||||
fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
|
||||
fn init_input(&self, pull: Pull, _internal: private::Internal);
|
||||
fn is_input_high(&self, _internal: private::Internal) -> bool;
|
||||
fn enable_input(&mut self, on: bool, _internal: private::Internal);
|
||||
|
||||
fn output_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::OutputSignal)];
|
||||
fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
|
||||
fn set_to_open_drain_output(&mut self, _internal: private::Internal);
|
||||
fn set_to_push_pull_output(&mut self, _internal: private::Internal);
|
||||
fn enable_output(&mut self, on: bool, _internal: private::Internal);
|
||||
@ -512,6 +508,7 @@ enum InputConnectionInner {
|
||||
/// This is mainly intended for internal use, but it can be used to connect
|
||||
/// peripherals within the MCU without external hardware.
|
||||
#[derive(Clone)]
|
||||
#[doc(hidden)] // FIXME: replace with `#[unstable]` when we can mark delegated methods https://github.com/Kobzol/rust-delegate/issues/77
|
||||
pub struct InputConnection(InputConnectionInner);
|
||||
|
||||
impl Peripheral for InputConnection {
|
||||
@ -576,11 +573,8 @@ impl From<OutputConnection> for InputConnection {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<Flex<'static, P>> for InputConnection
|
||||
where
|
||||
P: InputPin,
|
||||
{
|
||||
fn from(pin: Flex<'static, P>) -> Self {
|
||||
impl From<Flex<'static>> for InputConnection {
|
||||
fn from(pin: Flex<'static>) -> Self {
|
||||
pin.peripheral_input().into()
|
||||
}
|
||||
}
|
||||
@ -598,7 +592,7 @@ impl InputConnection {
|
||||
pub fn pull_direction(&self, pull: Pull, _internal: private::Internal);
|
||||
pub fn init_input(&self, pull: Pull, _internal: private::Internal);
|
||||
pub fn is_input_high(&self, _internal: private::Internal) -> bool;
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::InputSignal)];
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -623,6 +617,7 @@ enum OutputConnectionInner {
|
||||
///
|
||||
/// This is mainly intended for internal use, but it can be used to connect
|
||||
/// peripherals within the MCU without external hardware.
|
||||
#[doc(hidden)] // FIXME: replace with `#[unstable]` when we can mark delegated methods https://github.com/Kobzol/rust-delegate/issues/77
|
||||
pub struct OutputConnection(OutputConnectionInner);
|
||||
|
||||
impl Sealed for OutputConnection {}
|
||||
@ -668,11 +663,8 @@ impl From<OutputSignal> for OutputConnection {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<Flex<'static, P>> for OutputConnection
|
||||
where
|
||||
P: OutputPin,
|
||||
{
|
||||
fn from(pin: Flex<'static, P>) -> Self {
|
||||
impl From<Flex<'static>> for OutputConnection {
|
||||
fn from(pin: Flex<'static>) -> Self {
|
||||
pin.into_peripheral_output().into()
|
||||
}
|
||||
}
|
||||
@ -692,10 +684,10 @@ impl OutputConnection {
|
||||
OutputConnectionInner::Constant(level) => level,
|
||||
} {
|
||||
pub fn is_input_high(&self, _internal: private::Internal) -> bool;
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::InputSignal)];
|
||||
pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
|
||||
|
||||
pub fn is_set_high(&self, _internal: private::Internal) -> bool;
|
||||
pub fn output_signals(&self, _internal: private::Internal) -> &[(AlternateFunction, gpio::OutputSignal)];
|
||||
pub fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
|
||||
}
|
||||
#[doc(hidden)]
|
||||
to match &mut self.0 {
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
//! the peripherals in LP system during chip Deep-sleep, and wake up the
|
||||
//! chip from Deep-sleep.
|
||||
//!
|
||||
//! # Example
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ## Configure a LP Pin as Output
|
||||
//!
|
||||
@ -42,6 +42,7 @@ pub struct LowPowerOutput<'d, const PIN: u8> {
|
||||
|
||||
impl<'d, const PIN: u8> LowPowerOutput<'d, PIN> {
|
||||
/// Create a new output pin for use by the low-power core
|
||||
#[instability::unstable]
|
||||
pub fn new<P>(_pin: impl Peripheral<P = P> + 'd) -> Self
|
||||
where
|
||||
P: OutputPin + RtcPin,
|
||||
@ -77,6 +78,7 @@ pub struct LowPowerInput<'d, const PIN: u8> {
|
||||
|
||||
impl<'d, const PIN: u8> LowPowerInput<'d, PIN> {
|
||||
/// Create a new input pin for use by the low-power core
|
||||
#[instability::unstable]
|
||||
pub fn new<P>(_pin: impl Peripheral<P = P> + 'd) -> Self
|
||||
where
|
||||
P: InputPin + RtcPin,
|
||||
@ -121,6 +123,7 @@ pub struct LowPowerOutputOpenDrain<'d, const PIN: u8> {
|
||||
|
||||
impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> {
|
||||
/// Create a new output pin for use by the low-power core
|
||||
#[instability::unstable]
|
||||
pub fn new<P>(_pin: impl Peripheral<P = P> + 'd) -> Self
|
||||
where
|
||||
P: InputPin + OutputPin + RtcPin,
|
||||
@ -191,7 +194,6 @@ pub(crate) fn init_low_power_pin(pin: u8) {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! lp_gpio {
|
||||
(
|
||||
$($gpionum:literal)+
|
||||
@ -262,3 +264,5 @@ macro_rules! lp_gpio {
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use lp_gpio;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,7 @@ impl Level {
|
||||
pub(crate) fn input_signals(
|
||||
&self,
|
||||
_: private::Internal,
|
||||
) -> &[(AlternateFunction, InputSignal)] {
|
||||
) -> &'static [(AlternateFunction, InputSignal)] {
|
||||
&[]
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ impl Level {
|
||||
pub(crate) fn output_signals(
|
||||
&self,
|
||||
_: private::Internal,
|
||||
) -> &[(AlternateFunction, OutputSignal)] {
|
||||
) -> &'static [(AlternateFunction, OutputSignal)] {
|
||||
&[]
|
||||
}
|
||||
|
||||
@ -85,25 +85,6 @@ impl crate::peripheral::Peripheral for NoPin {
|
||||
|
||||
impl private::Sealed for NoPin {}
|
||||
|
||||
impl embedded_hal_02::digital::v2::OutputPin for NoPin {
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl embedded_hal_02::digital::v2::StatefulOutputPin for NoPin {
|
||||
fn is_set_high(&self) -> Result<bool, Self::Error> {
|
||||
Ok(false)
|
||||
}
|
||||
fn is_set_low(&self) -> Result<bool, Self::Error> {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::digital::ErrorType for NoPin {
|
||||
type Error = core::convert::Infallible;
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
//! the peripherals in RTC system during chip Deep-sleep, and wake up the
|
||||
//! chip from Deep-sleep.
|
||||
//!
|
||||
//! ## Example
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Configure a ULP Pin as Output
|
||||
//!
|
||||
|
||||
@ -40,7 +40,7 @@ use crate::{
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::HMAC,
|
||||
reg_access::{AlignmentHelper, SocDependentEndianess},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
system::{GenericPeripheralGuard, Peripheral as PeripheralEnable},
|
||||
};
|
||||
|
||||
/// Provides an interface for interacting with the HMAC hardware peripheral.
|
||||
@ -51,6 +51,7 @@ pub struct Hmac<'d> {
|
||||
alignment_helper: AlignmentHelper<SocDependentEndianess>,
|
||||
byte_written: usize,
|
||||
next_command: NextCommand,
|
||||
_guard: GenericPeripheralGuard<{ PeripheralEnable::Hmac as u8 }>,
|
||||
}
|
||||
|
||||
/// HMAC interface error
|
||||
@ -66,6 +67,7 @@ pub enum Error {
|
||||
/// user. It can also deliver to other peripherals.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum HmacPurpose {
|
||||
/// HMAC is used to re-enable JTAG after soft-disabling it.
|
||||
ToJtag = 6,
|
||||
@ -107,14 +109,14 @@ impl<'d> Hmac<'d> {
|
||||
pub fn new(hmac: impl Peripheral<P = HMAC> + 'd) -> Self {
|
||||
crate::into_ref!(hmac);
|
||||
|
||||
PeripheralClockControl::reset(PeripheralEnable::Hmac);
|
||||
PeripheralClockControl::enable(PeripheralEnable::Hmac);
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
Self {
|
||||
hmac,
|
||||
alignment_helper: AlignmentHelper::default(),
|
||||
byte_written: 64,
|
||||
next_command: NextCommand::None,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
//!
|
||||
//! The I2C driver implements a number of third-party traits, with the
|
||||
//! intention of making the HAL inter-compatible with various device drivers
|
||||
//! from the community. This includes the [`embedded-hal`] for both 0.2.x and
|
||||
//! 1.0.x versions.
|
||||
//! from the community, including the [`embedded-hal`].
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
@ -26,6 +25,7 @@
|
||||
//! peripherals.I2C0,
|
||||
//! Config::default(),
|
||||
//! )
|
||||
//! .unwrap()
|
||||
//! .with_sda(peripherals.GPIO1)
|
||||
//! .with_scl(peripherals.GPIO2);
|
||||
//!
|
||||
@ -35,7 +35,7 @@
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//! [`embedded-hal`]: https://crates.io/crates/embedded-hal
|
||||
//! [`embedded-hal`]: https://docs.rs/embedded-hal/latest/embedded_hal/index.html
|
||||
|
||||
use core::marker::PhantomData;
|
||||
#[cfg(not(esp32))]
|
||||
@ -44,27 +44,27 @@ use core::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
#[cfg(any(doc, feature = "unstable"))]
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embedded_hal::i2c::Operation as EhalOperation;
|
||||
use fugit::HertzU32;
|
||||
|
||||
use crate::{
|
||||
asynch::AtomicWaker,
|
||||
clock::Clocks,
|
||||
gpio::{interconnect::PeripheralOutput, InputSignal, OutputSignal, Pull},
|
||||
interrupt::InterruptHandler,
|
||||
interrupt::{InterruptConfigurable, InterruptHandler},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::{
|
||||
i2c0::{RegisterBlock, COMD},
|
||||
Interrupt,
|
||||
},
|
||||
private,
|
||||
system::PeripheralClockControl,
|
||||
system::{PeripheralClockControl, PeripheralGuard},
|
||||
Async,
|
||||
Blocking,
|
||||
Cpu,
|
||||
InterruptConfigurable,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@ -86,36 +86,74 @@ const I2C_CHUNK_SIZE: usize = 254;
|
||||
const MAX_ITERATIONS: u32 = 1_000_000;
|
||||
|
||||
/// I2C-specific transmission errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// The transmission exceeded the FIFO size.
|
||||
ExceedingFifo,
|
||||
FifoExceeded,
|
||||
/// The acknowledgment check failed.
|
||||
AckCheckFailed,
|
||||
/// A timeout occurred during transmission.
|
||||
TimeOut,
|
||||
Timeout,
|
||||
/// The arbitration for the bus was lost.
|
||||
ArbitrationLost,
|
||||
/// The execution of the I2C command was incomplete.
|
||||
ExecIncomplete,
|
||||
ExecutionIncomplete,
|
||||
/// The number of commands issued exceeded the limit.
|
||||
CommandNrExceeded,
|
||||
CommandNumberExceeded,
|
||||
/// Zero length read or write operation.
|
||||
InvalidZeroLength,
|
||||
ZeroLengthInvalid,
|
||||
}
|
||||
|
||||
impl core::error::Error for Error {}
|
||||
|
||||
impl core::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Error::FifoExceeded => write!(f, "The transmission exceeded the FIFO size"),
|
||||
Error::AckCheckFailed => write!(f, "The acknowledgment check failed"),
|
||||
Error::Timeout => write!(f, "A timeout occurred during transmission"),
|
||||
Error::ArbitrationLost => write!(f, "The arbitration for the bus was lost"),
|
||||
Error::ExecutionIncomplete => {
|
||||
write!(f, "The execution of the I2C command was incomplete")
|
||||
}
|
||||
Error::CommandNumberExceeded => {
|
||||
write!(f, "The number of commands issued exceeded the limit")
|
||||
}
|
||||
Error::ZeroLengthInvalid => write!(f, "Zero length read or write operation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// I2C-specific configuration errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ConfigError {}
|
||||
#[non_exhaustive]
|
||||
pub enum ConfigError {
|
||||
/// Provided bus frequency is invalid for the current configuration.
|
||||
FrequencyInvalid,
|
||||
}
|
||||
|
||||
impl core::error::Error for ConfigError {}
|
||||
|
||||
impl core::fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ConfigError::FrequencyInvalid => write!(
|
||||
f,
|
||||
"Provided bus frequency is invalid for the current configuration"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
// This enum is used to keep track of the last/next operation that was/will be
|
||||
// performed in an embedded-hal(-async) I2c::transaction. It is used to
|
||||
// determine whether a START condition should be issued at the start of the
|
||||
// current operation and whether a read needs an ack or a nack for the final
|
||||
// byte.
|
||||
#[derive(PartialEq)]
|
||||
enum OpKind {
|
||||
Write,
|
||||
Read,
|
||||
@ -124,6 +162,7 @@ enum OpKind {
|
||||
/// I2C operation.
|
||||
///
|
||||
/// Several operations can be combined as part of a transaction.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, strum::Display)]
|
||||
pub enum Operation<'a> {
|
||||
/// Write data from the provided buffer.
|
||||
Write(&'a [u8]),
|
||||
@ -175,7 +214,7 @@ impl embedded_hal::i2c::Error for Error {
|
||||
use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
|
||||
|
||||
match self {
|
||||
Self::ExceedingFifo => ErrorKind::Overrun,
|
||||
Self::FifoExceeded => ErrorKind::Overrun,
|
||||
Self::ArbitrationLost => ErrorKind::ArbitrationLoss,
|
||||
Self::AckCheckFailed => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown),
|
||||
_ => ErrorKind::Other,
|
||||
@ -219,6 +258,7 @@ enum Ack {
|
||||
Ack = 0,
|
||||
Nack = 1,
|
||||
}
|
||||
|
||||
impl From<u32> for Ack {
|
||||
fn from(ack: u32) -> Self {
|
||||
match ack {
|
||||
@ -228,6 +268,7 @@ impl From<u32> for Ack {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ack> for u32 {
|
||||
fn from(ack: Ack) -> u32 {
|
||||
ack as u32
|
||||
@ -235,8 +276,9 @@ impl From<Ack> for u32 {
|
||||
}
|
||||
|
||||
/// I2C driver configuration
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, procmacros::BuilderLite)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct Config {
|
||||
/// The I2C clock frequency.
|
||||
pub frequency: HertzU32,
|
||||
@ -258,6 +300,13 @@ pub struct Config {
|
||||
pub timeout: Option<u32>,
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Config {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.frequency.to_Hz().hash(state); // `HertzU32` doesn't implement `Hash`
|
||||
self.timeout.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
use fugit::RateExtU32;
|
||||
@ -269,13 +318,18 @@ impl Default for Config {
|
||||
}
|
||||
|
||||
/// I2C driver
|
||||
pub struct I2c<'d, DM: Mode, T = AnyI2c> {
|
||||
i2c: PeripheralRef<'d, T>,
|
||||
phantom: PhantomData<DM>,
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct I2c<'d, Dm: DriverMode> {
|
||||
i2c: PeripheralRef<'d, AnyI2c>,
|
||||
phantom: PhantomData<Dm>,
|
||||
config: Config,
|
||||
guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<T: Instance, DM: Mode> SetConfig for I2c<'_, DM, T> {
|
||||
#[cfg(any(doc, feature = "unstable"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
impl<Dm: DriverMode> SetConfig for I2c<'_, Dm> {
|
||||
type Config = Config;
|
||||
type ConfigError = ConfigError;
|
||||
|
||||
@ -284,52 +338,11 @@ impl<T: Instance, DM: Mode> SetConfig for I2c<'_, DM, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_02::blocking::i2c::Read for I2c<'_, Blocking, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(address, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_02::blocking::i2c::Write for I2c<'_, Blocking, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(addr, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_02::blocking::i2c::WriteRead for I2c<'_, Blocking, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn write_read(
|
||||
&mut self,
|
||||
address: u8,
|
||||
bytes: &[u8],
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
self.write_read(address, bytes, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, DM: Mode> embedded_hal::i2c::ErrorType for I2c<'_, DM, T> {
|
||||
impl<Dm: DriverMode> embedded_hal::i2c::ErrorType for I2c<'_, Dm> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<T, DM: Mode> embedded_hal::i2c::I2c for I2c<'_, DM, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
impl<Dm: DriverMode> embedded_hal::i2c::I2c for I2c<'_, Dm> {
|
||||
fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
@ -340,10 +353,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T, DM: Mode> I2c<'d, DM, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
impl<'d, Dm: DriverMode> I2c<'d, Dm> {
|
||||
fn driver(&self) -> Driver<'_> {
|
||||
Driver {
|
||||
info: self.i2c.info(),
|
||||
@ -352,8 +362,9 @@ where
|
||||
}
|
||||
|
||||
fn internal_recover(&self) {
|
||||
PeripheralClockControl::reset(self.driver().info.peripheral);
|
||||
PeripheralClockControl::disable(self.driver().info.peripheral);
|
||||
PeripheralClockControl::enable(self.driver().info.peripheral);
|
||||
PeripheralClockControl::reset(self.driver().info.peripheral);
|
||||
|
||||
// We know the configuration is valid, we can ignore the result.
|
||||
_ = self.driver().setup(&self.config);
|
||||
@ -454,44 +465,37 @@ impl<'d> I2c<'d, Blocking> {
|
||||
/// Create a new I2C instance
|
||||
/// This will enable the peripheral but the peripheral won't get
|
||||
/// automatically disabled when this gets dropped.
|
||||
pub fn new(i2c: impl Peripheral<P = impl Instance> + 'd, config: Config) -> Self {
|
||||
Self::new_typed(i2c.map_into(), config)
|
||||
}
|
||||
}
|
||||
pub fn new(
|
||||
i2c: impl Peripheral<P = impl Instance> + 'd,
|
||||
config: Config,
|
||||
) -> Result<Self, ConfigError> {
|
||||
crate::into_mapped_ref!(i2c);
|
||||
|
||||
impl<'d, T> I2c<'d, Blocking, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
/// Create a new I2C instance
|
||||
/// This will enable the peripheral but the peripheral won't get
|
||||
/// automatically disabled when this gets dropped.
|
||||
pub fn new_typed(i2c: impl Peripheral<P = T> + 'd, config: Config) -> Self {
|
||||
crate::into_ref!(i2c);
|
||||
let guard = PeripheralGuard::new(i2c.info().peripheral);
|
||||
|
||||
let i2c = I2c {
|
||||
i2c,
|
||||
phantom: PhantomData,
|
||||
config,
|
||||
guard,
|
||||
};
|
||||
|
||||
PeripheralClockControl::reset(i2c.driver().info.peripheral);
|
||||
PeripheralClockControl::enable(i2c.driver().info.peripheral);
|
||||
i2c.driver().setup(&i2c.config)?;
|
||||
|
||||
unwrap!(i2c.driver().setup(&i2c.config));
|
||||
i2c
|
||||
Ok(i2c)
|
||||
}
|
||||
|
||||
// TODO: missing interrupt APIs
|
||||
|
||||
/// Configures the I2C peripheral to operate in asynchronous mode.
|
||||
pub fn into_async(mut self) -> I2c<'d, Async, T> {
|
||||
pub fn into_async(mut self) -> I2c<'d, Async> {
|
||||
self.set_interrupt_handler(self.driver().info.async_handler);
|
||||
|
||||
I2c {
|
||||
i2c: self.i2c,
|
||||
phantom: PhantomData,
|
||||
config: self.config,
|
||||
guard: self.guard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,12 +560,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> private::Sealed for I2c<'_, Blocking, T> where T: Instance {}
|
||||
impl private::Sealed for I2c<'_, Blocking> {}
|
||||
|
||||
impl<T> InterruptConfigurable for I2c<'_, Blocking, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
impl InterruptConfigurable for I2c<'_, Blocking> {
|
||||
fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
|
||||
let interrupt = self.driver().info.interrupt;
|
||||
for core in Cpu::other() {
|
||||
@ -601,10 +602,6 @@ impl<'a> I2cFuture<'a> {
|
||||
|
||||
w.arbitration_lost().set_bit();
|
||||
w.time_out().set_bit();
|
||||
|
||||
#[cfg(esp32)]
|
||||
w.ack_err().set_bit();
|
||||
#[cfg(not(esp32))]
|
||||
w.nack().set_bit();
|
||||
|
||||
w
|
||||
@ -632,19 +629,13 @@ impl<'a> I2cFuture<'a> {
|
||||
}
|
||||
|
||||
if r.time_out().bit_is_set() {
|
||||
return Err(Error::TimeOut);
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
|
||||
#[cfg(not(esp32))]
|
||||
if r.nack().bit_is_set() {
|
||||
return Err(Error::AckCheckFailed);
|
||||
}
|
||||
|
||||
#[cfg(esp32)]
|
||||
if r.ack_err().bit_is_set() {
|
||||
return Err(Error::AckCheckFailed);
|
||||
}
|
||||
|
||||
#[cfg(not(esp32))]
|
||||
if r.trans_complete().bit_is_set()
|
||||
&& self
|
||||
@ -683,18 +674,16 @@ impl core::future::Future for I2cFuture<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T> I2c<'d, Async, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
impl<'d> I2c<'d, Async> {
|
||||
/// Configure the I2C peripheral to operate in blocking mode.
|
||||
pub fn into_blocking(self) -> I2c<'d, Blocking, T> {
|
||||
pub fn into_blocking(self) -> I2c<'d, Blocking> {
|
||||
crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt);
|
||||
|
||||
I2c {
|
||||
i2c: self.i2c,
|
||||
phantom: PhantomData,
|
||||
config: self.config,
|
||||
guard: self.guard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -816,10 +805,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::i2c::I2c for I2c<'_, Async, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
impl embedded_hal_async::i2c::I2c for I2c<'_, Async> {
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
@ -899,7 +885,7 @@ fn configure_clock(
|
||||
scl_start_hold_time: u32,
|
||||
scl_stop_hold_time: u32,
|
||||
timeout: Option<u32>,
|
||||
) {
|
||||
) -> Result<(), ConfigError> {
|
||||
unsafe {
|
||||
// divider
|
||||
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))]
|
||||
@ -913,10 +899,14 @@ fn configure_clock(
|
||||
.scl_low_period()
|
||||
.write(|w| w.scl_low_period().bits(scl_low_period as u16));
|
||||
|
||||
#[cfg(not(esp32))]
|
||||
let scl_wait_high_period = scl_wait_high_period
|
||||
.try_into()
|
||||
.map_err(|_| ConfigError::FrequencyInvalid)?;
|
||||
|
||||
register_block.scl_high_period().write(|w| {
|
||||
#[cfg(not(esp32))] // ESP32 does not have a wait_high field
|
||||
w.scl_wait_high_period()
|
||||
.bits(scl_wait_high_period.try_into().unwrap());
|
||||
w.scl_wait_high_period().bits(scl_wait_high_period);
|
||||
w.scl_high_period().bits(scl_high_period as u16)
|
||||
});
|
||||
|
||||
@ -961,9 +951,12 @@ fn configure_clock(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Peripheral data describing a particular I2C instance.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct Info {
|
||||
/// Pointer to the register block for this I2C instance.
|
||||
@ -1000,6 +993,14 @@ impl Info {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Info {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.register_block == other.register_block
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for Info {}
|
||||
|
||||
#[allow(dead_code)] // Some versions don't need `state`
|
||||
struct Driver<'a> {
|
||||
info: &'a Info,
|
||||
@ -1047,7 +1048,7 @@ impl Driver<'_> {
|
||||
let clock = clocks.xtal_clock.convert();
|
||||
}
|
||||
}
|
||||
self.set_frequency(clock, config.frequency, config.timeout);
|
||||
self.set_frequency(clock, config.frequency, config.timeout)?;
|
||||
|
||||
self.update_config();
|
||||
|
||||
@ -1091,7 +1092,12 @@ impl Driver<'_> {
|
||||
/// Sets the frequency of the I2C interface by calculating and applying the
|
||||
/// associated timings - corresponds to i2c_ll_cal_bus_clk and
|
||||
/// i2c_ll_set_bus_timing in ESP-IDF
|
||||
fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) {
|
||||
fn set_frequency(
|
||||
&self,
|
||||
source_clk: HertzU32,
|
||||
bus_freq: HertzU32,
|
||||
timeout: Option<u32>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let source_clk = source_clk.raw();
|
||||
let bus_freq = bus_freq.raw();
|
||||
|
||||
@ -1158,14 +1164,21 @@ impl Driver<'_> {
|
||||
scl_start_hold_time,
|
||||
scl_stop_hold_time,
|
||||
timeout,
|
||||
);
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(esp32s2)]
|
||||
/// Sets the frequency of the I2C interface by calculating and applying the
|
||||
/// associated timings - corresponds to i2c_ll_cal_bus_clk and
|
||||
/// i2c_ll_set_bus_timing in ESP-IDF
|
||||
fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) {
|
||||
fn set_frequency(
|
||||
&self,
|
||||
source_clk: HertzU32,
|
||||
bus_freq: HertzU32,
|
||||
timeout: Option<u32>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let source_clk = source_clk.raw();
|
||||
let bus_freq = bus_freq.raw();
|
||||
|
||||
@ -1208,14 +1221,21 @@ impl Driver<'_> {
|
||||
scl_start_hold_time,
|
||||
scl_stop_hold_time,
|
||||
timeout.map(|to_bus| (to_bus * 2 * half_cycle).min(0xFF_FFFF)),
|
||||
);
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))]
|
||||
/// Sets the frequency of the I2C interface by calculating and applying the
|
||||
/// associated timings - corresponds to i2c_ll_cal_bus_clk and
|
||||
/// i2c_ll_set_bus_timing in ESP-IDF
|
||||
fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) {
|
||||
fn set_frequency(
|
||||
&self,
|
||||
source_clk: HertzU32,
|
||||
bus_freq: HertzU32,
|
||||
timeout: Option<u32>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let source_clk = source_clk.raw();
|
||||
let bus_freq = bus_freq.raw();
|
||||
|
||||
@ -1278,13 +1298,15 @@ impl Driver<'_> {
|
||||
let raw = if to_peri != 1 << log2 { log2 + 1 } else { log2 };
|
||||
raw.min(0x1F)
|
||||
}),
|
||||
);
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(esp32, esp32s2))]
|
||||
async fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
if buffer.len() > 32 {
|
||||
panic!("On ESP32 and ESP32-S2 the max I2C read is limited to 32 bytes");
|
||||
return Err(Error::FifoExceeded);
|
||||
}
|
||||
|
||||
self.wait_for_completion(false).await?;
|
||||
@ -1322,7 +1344,7 @@ impl Driver<'_> {
|
||||
let max_len = if start { 254usize } else { 255usize };
|
||||
if bytes.len() > max_len {
|
||||
// we could support more by adding multiple write operations
|
||||
return Err(Error::ExceedingFifo);
|
||||
return Err(Error::FifoExceeded);
|
||||
}
|
||||
|
||||
let write_len = if start { bytes.len() + 1 } else { bytes.len() };
|
||||
@ -1371,7 +1393,7 @@ impl Driver<'_> {
|
||||
I: Iterator<Item = &'a COMD>,
|
||||
{
|
||||
if buffer.is_empty() {
|
||||
return Err(Error::InvalidZeroLength);
|
||||
return Err(Error::ZeroLengthInvalid);
|
||||
}
|
||||
let (max_len, initial_len) = if will_continue {
|
||||
(255usize, buffer.len())
|
||||
@ -1380,7 +1402,7 @@ impl Driver<'_> {
|
||||
};
|
||||
if buffer.len() > max_len {
|
||||
// we could support more by adding multiple read operations
|
||||
return Err(Error::ExceedingFifo);
|
||||
return Err(Error::FifoExceeded);
|
||||
}
|
||||
|
||||
if start {
|
||||
@ -1457,7 +1479,7 @@ impl Driver<'_> {
|
||||
// see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615
|
||||
|
||||
if buffer.len() > 32 {
|
||||
panic!("On ESP32 and ESP32-S2 the max I2C read is limited to 32 bytes");
|
||||
return Err(Error::FifoExceeded);
|
||||
}
|
||||
|
||||
// wait for completion - then we can just read the data from FIFO
|
||||
@ -1564,7 +1586,7 @@ impl Driver<'_> {
|
||||
|
||||
tout -= 1;
|
||||
if tout == 0 {
|
||||
return Err(Error::TimeOut);
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
|
||||
embassy_futures::yield_now().await;
|
||||
@ -1592,7 +1614,7 @@ impl Driver<'_> {
|
||||
|
||||
tout -= 1;
|
||||
if tout == 0 {
|
||||
return Err(Error::TimeOut);
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
}
|
||||
self.check_all_commands_done()?;
|
||||
@ -1608,7 +1630,7 @@ impl Driver<'_> {
|
||||
let cmd = cmd_reg.read();
|
||||
|
||||
if cmd.bits() != 0x0 && !cmd.opcode().is_end() && !cmd.command_done().bit_is_set() {
|
||||
return Err(Error::ExecIncomplete);
|
||||
return Err(Error::ExecutionIncomplete);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1631,7 +1653,7 @@ impl Driver<'_> {
|
||||
if #[cfg(esp32)] {
|
||||
// Handle error cases
|
||||
let retval = if interrupts.time_out().bit_is_set() {
|
||||
Err(Error::TimeOut)
|
||||
Err(Error::Timeout)
|
||||
} else if interrupts.nack().bit_is_set() {
|
||||
Err(Error::AckCheckFailed)
|
||||
} else if interrupts.arbitration_lost().bit_is_set() {
|
||||
@ -1642,7 +1664,7 @@ impl Driver<'_> {
|
||||
} else {
|
||||
// Handle error cases
|
||||
let retval = if interrupts.time_out().bit_is_set() {
|
||||
Err(Error::TimeOut)
|
||||
Err(Error::Timeout)
|
||||
} else if interrupts.nack().bit_is_set() {
|
||||
Err(Error::AckCheckFailed)
|
||||
} else if interrupts.arbitration_lost().bit_is_set() {
|
||||
@ -1690,7 +1712,7 @@ impl Driver<'_> {
|
||||
|
||||
#[cfg(not(any(esp32, esp32s2)))]
|
||||
/// Fills the TX FIFO with data from the provided slice.
|
||||
fn fill_tx_fifo(&self, bytes: &[u8]) -> usize {
|
||||
fn fill_tx_fifo(&self, bytes: &[u8]) -> Result<usize, Error> {
|
||||
let mut index = 0;
|
||||
while index < bytes.len()
|
||||
&& !self
|
||||
@ -1715,7 +1737,7 @@ impl Driver<'_> {
|
||||
.int_clr()
|
||||
.write(|w| w.txfifo_ovf().clear_bit_by_one());
|
||||
}
|
||||
index
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
#[cfg(not(any(esp32, esp32s2)))]
|
||||
@ -1765,20 +1787,20 @@ impl Driver<'_> {
|
||||
|
||||
#[cfg(any(esp32, esp32s2))]
|
||||
/// Fills the TX FIFO with data from the provided slice.
|
||||
fn fill_tx_fifo(&self, bytes: &[u8]) -> usize {
|
||||
fn fill_tx_fifo(&self, bytes: &[u8]) -> Result<usize, Error> {
|
||||
// on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the
|
||||
// FIFO apparently it would be possible by using non-fifo mode
|
||||
// see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615
|
||||
|
||||
if bytes.len() > 31 {
|
||||
panic!("On ESP32 and ESP32-S2 the max I2C transfer is limited to 31 bytes");
|
||||
return Err(Error::FifoExceeded);
|
||||
}
|
||||
|
||||
for b in bytes {
|
||||
write_fifo(self.register_block(), *b);
|
||||
}
|
||||
|
||||
bytes.len()
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
#[cfg(any(esp32, esp32s2))]
|
||||
@ -1876,7 +1898,7 @@ impl Driver<'_> {
|
||||
cmd_iterator,
|
||||
if stop { Command::Stop } else { Command::End },
|
||||
)?;
|
||||
let index = self.fill_tx_fifo(bytes);
|
||||
let index = self.fill_tx_fifo(bytes)?;
|
||||
self.start_transmission();
|
||||
|
||||
Ok(index)
|
||||
@ -2136,15 +2158,8 @@ impl Driver<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Info {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.register_block == other.register_block
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for Info {}
|
||||
|
||||
/// Peripheral state for an I2C instance.
|
||||
#[doc(hidden)]
|
||||
#[non_exhaustive]
|
||||
pub struct State {
|
||||
/// Waker for the asynchronous operations.
|
||||
@ -2152,6 +2167,7 @@ pub struct State {
|
||||
}
|
||||
|
||||
/// I2C Peripheral Instance
|
||||
#[doc(hidden)]
|
||||
pub trait Instance: Peripheral<P = Self> + Into<AnyI2c> + 'static {
|
||||
/// Returns the peripheral data and state describing this instance.
|
||||
fn parts(&self) -> (&Info, &State);
|
||||
@ -2174,7 +2190,7 @@ fn add_cmd<'a, I>(cmd_iterator: &mut I, command: Command) -> Result<(), Error>
|
||||
where
|
||||
I: Iterator<Item = &'a COMD>,
|
||||
{
|
||||
let cmd = cmd_iterator.next().ok_or(Error::CommandNrExceeded)?;
|
||||
let cmd = cmd_iterator.next().ok_or(Error::CommandNumberExceeded)?;
|
||||
|
||||
cmd.write(|w| match command {
|
||||
Command::Start => w.opcode().rstart(),
|
||||
|
||||
@ -23,18 +23,19 @@
|
||||
//! - `DMA`
|
||||
//! - `system` (to configure and enable the I2S peripheral)
|
||||
//!
|
||||
//! ## Example
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Initialization
|
||||
//! ### I2S Read
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::i2s::master::{I2s, Standard, DataFormat};
|
||||
//! # use esp_hal::dma_buffers;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority};
|
||||
//! let dma = Dma::new(peripherals.DMA);
|
||||
#![cfg_attr(any(esp32, esp32s2), doc = "let dma_channel = dma.i2s0channel;")]
|
||||
#![cfg_attr(not(any(esp32, esp32s2)), doc = "let dma_channel = dma.channel0;")]
|
||||
#![cfg_attr(any(esp32, esp32s2), doc = "let dma_channel = peripherals.DMA_I2S0;")]
|
||||
#![cfg_attr(
|
||||
not(any(esp32, esp32s2)),
|
||||
doc = "let dma_channel = peripherals.DMA_CH0;"
|
||||
)]
|
||||
//! let (mut rx_buffer, rx_descriptors, _, tx_descriptors) =
|
||||
//! dma_buffers!(0, 4 * 4092);
|
||||
//!
|
||||
@ -43,10 +44,7 @@
|
||||
//! Standard::Philips,
|
||||
//! DataFormat::Data16Channel16,
|
||||
//! 44100.Hz(),
|
||||
//! dma_channel.configure(
|
||||
//! false,
|
||||
//! DmaPriority::Priority0,
|
||||
//! ),
|
||||
//! dma_channel,
|
||||
//! rx_descriptors,
|
||||
//! tx_descriptors,
|
||||
//! );
|
||||
@ -84,7 +82,7 @@ use crate::{
|
||||
ChannelRx,
|
||||
ChannelTx,
|
||||
DescriptorChain,
|
||||
DmaChannelConvert,
|
||||
DmaChannelFor,
|
||||
DmaDescriptor,
|
||||
DmaEligible,
|
||||
DmaError,
|
||||
@ -92,19 +90,20 @@ use crate::{
|
||||
DmaTransferRxCircular,
|
||||
DmaTransferTx,
|
||||
DmaTransferTxCircular,
|
||||
PeripheralRxChannel,
|
||||
PeripheralTxChannel,
|
||||
ReadBuffer,
|
||||
Rx,
|
||||
Tx,
|
||||
WriteBuffer,
|
||||
},
|
||||
gpio::interconnect::PeripheralOutput,
|
||||
interrupt::InterruptHandler,
|
||||
interrupt::{InterruptConfigurable, InterruptHandler},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
system::PeripheralClockControl,
|
||||
system::PeripheralGuard,
|
||||
Async,
|
||||
Blocking,
|
||||
InterruptConfigurable,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
#[derive(Debug, EnumSetType)]
|
||||
@ -143,6 +142,7 @@ impl AcceptedWord for i32 {}
|
||||
/// I2S Error
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum Error {
|
||||
/// An unspecified or unknown error occurred during an I2S operation.
|
||||
Unknown,
|
||||
@ -250,67 +250,19 @@ impl DataFormat {
|
||||
|
||||
/// Instance of the I2S peripheral driver
|
||||
#[non_exhaustive]
|
||||
pub struct I2s<'d, M, T = AnyI2s>
|
||||
pub struct I2s<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Handles the reception (RX) side of the I2S peripheral.
|
||||
pub i2s_rx: RxCreator<'d, M, T>,
|
||||
pub i2s_rx: RxCreator<'d, Dm>,
|
||||
/// Handles the transmission (TX) side of the I2S peripheral.
|
||||
pub i2s_tx: TxCreator<'d, M, T>,
|
||||
pub i2s_tx: TxCreator<'d, Dm>,
|
||||
}
|
||||
|
||||
impl<'d, DmaMode, T> I2s<'d, DmaMode, T>
|
||||
impl<Dm> I2s<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_internal<CH>(
|
||||
i2s: PeripheralRef<'d, T>,
|
||||
standard: Standard,
|
||||
data_format: DataFormat,
|
||||
sample_rate: impl Into<fugit::HertzU32>,
|
||||
channel: Channel<'d, DmaMode, CH>,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<T::Dma>,
|
||||
{
|
||||
channel.runtime_ensure_compatible(&i2s);
|
||||
// on ESP32-C3 / ESP32-S3 and later RX and TX are independent and
|
||||
// could be configured totally independently but for now handle all
|
||||
// the targets the same and force same configuration for both, TX and RX
|
||||
|
||||
PeripheralClockControl::reset(i2s.peripheral());
|
||||
PeripheralClockControl::enable(i2s.peripheral());
|
||||
i2s.set_clock(calculate_clock(sample_rate, 2, data_format.channel_bits()));
|
||||
i2s.configure(&standard, &data_format);
|
||||
i2s.set_master();
|
||||
i2s.update();
|
||||
|
||||
let channel = channel.degrade();
|
||||
Self {
|
||||
i2s_rx: RxCreator {
|
||||
i2s: unsafe { i2s.clone_unchecked() },
|
||||
rx_channel: channel.rx,
|
||||
descriptors: rx_descriptors,
|
||||
},
|
||||
i2s_tx: TxCreator {
|
||||
i2s,
|
||||
tx_channel: channel.tx,
|
||||
descriptors: tx_descriptors,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DmaMode, T> I2s<'_, DmaMode, T>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Sets the interrupt handler
|
||||
///
|
||||
@ -345,17 +297,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<DmaMode, I> crate::private::Sealed for I2s<'_, DmaMode, I>
|
||||
where
|
||||
I: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
{
|
||||
}
|
||||
impl<Dm> crate::private::Sealed for I2s<'_, Dm> where Dm: DriverMode {}
|
||||
|
||||
impl<DmaMode, I> InterruptConfigurable for I2s<'_, DmaMode, I>
|
||||
impl<Dm> InterruptConfigurable for I2s<'_, Dm>
|
||||
where
|
||||
I: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
|
||||
I2s::set_interrupt_handler(self, handler);
|
||||
@ -366,86 +312,77 @@ impl<'d> I2s<'d, Blocking> {
|
||||
/// Construct a new I2S peripheral driver instance for the first I2S
|
||||
/// peripheral
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new<CH, DM>(
|
||||
pub fn new<CH>(
|
||||
i2s: impl Peripheral<P = impl RegisterAccess> + 'd,
|
||||
standard: Standard,
|
||||
data_format: DataFormat,
|
||||
sample_rate: impl Into<fugit::HertzU32>,
|
||||
channel: Channel<'d, DM, CH>,
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<<AnyI2s as DmaEligible>::Dma>,
|
||||
DM: Mode,
|
||||
Channel<'d, Blocking, CH>: From<Channel<'d, DM, CH>>,
|
||||
{
|
||||
Self::new_typed(
|
||||
i2s.map_into(),
|
||||
standard,
|
||||
data_format,
|
||||
sample_rate,
|
||||
channel,
|
||||
rx_descriptors,
|
||||
tx_descriptors,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T> I2s<'d, Blocking, T>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
/// Construct a new I2S peripheral driver instance for the first I2S
|
||||
/// peripheral
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_typed<CH, DM>(
|
||||
i2s: impl Peripheral<P = T> + 'd,
|
||||
standard: Standard,
|
||||
data_format: DataFormat,
|
||||
sample_rate: impl Into<fugit::HertzU32>,
|
||||
channel: Channel<'d, DM, CH>,
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<T::Dma>,
|
||||
DM: Mode,
|
||||
Channel<'d, Blocking, CH>: From<Channel<'d, DM, CH>>,
|
||||
CH: DmaChannelFor<AnyI2s>,
|
||||
{
|
||||
crate::into_ref!(i2s);
|
||||
Self::new_internal(
|
||||
|
||||
let channel = Channel::new(channel.map(|ch| ch.degrade()));
|
||||
channel.runtime_ensure_compatible(&i2s);
|
||||
|
||||
// on ESP32-C3 / ESP32-S3 and later RX and TX are independent and
|
||||
// could be configured totally independently but for now handle all
|
||||
// the targets the same and force same configuration for both, TX and RX
|
||||
|
||||
// make sure the peripheral is enabled before configuring it
|
||||
let peripheral = i2s.peripheral();
|
||||
let rx_guard = PeripheralGuard::new(peripheral);
|
||||
let tx_guard = PeripheralGuard::new(peripheral);
|
||||
|
||||
i2s.set_clock(calculate_clock(sample_rate, 2, data_format.channel_bits()));
|
||||
i2s.configure(&standard, &data_format);
|
||||
i2s.set_master();
|
||||
i2s.update();
|
||||
|
||||
let i2s = i2s.map_into();
|
||||
|
||||
Self {
|
||||
i2s_rx: RxCreator {
|
||||
i2s: unsafe { i2s.clone_unchecked() },
|
||||
rx_channel: channel.rx,
|
||||
descriptors: rx_descriptors,
|
||||
guard: rx_guard,
|
||||
},
|
||||
i2s_tx: TxCreator {
|
||||
i2s,
|
||||
standard,
|
||||
data_format,
|
||||
sample_rate,
|
||||
channel.into(),
|
||||
rx_descriptors,
|
||||
tx_descriptors,
|
||||
)
|
||||
tx_channel: channel.tx,
|
||||
descriptors: tx_descriptors,
|
||||
guard: tx_guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the SPI instance into async mode.
|
||||
pub fn into_async(self) -> I2s<'d, Async, T> {
|
||||
/// Converts the I2S instance into async mode.
|
||||
pub fn into_async(self) -> I2s<'d, Async> {
|
||||
I2s {
|
||||
i2s_rx: RxCreator {
|
||||
i2s: self.i2s_rx.i2s,
|
||||
rx_channel: self.i2s_rx.rx_channel.into_async(),
|
||||
descriptors: self.i2s_rx.descriptors,
|
||||
guard: self.i2s_rx.guard,
|
||||
},
|
||||
i2s_tx: TxCreator {
|
||||
i2s: self.i2s_tx.i2s,
|
||||
tx_channel: self.i2s_tx.tx_channel.into_async(),
|
||||
descriptors: self.i2s_tx.descriptors,
|
||||
guard: self.i2s_tx.guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, M, T> I2s<'d, M, T>
|
||||
impl<'d, Dm> I2s<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Configures the I2S peripheral to use a master clock (MCLK) output pin.
|
||||
pub fn with_mclk<P: PeripheralOutput>(self, pin: impl Peripheral<P = P> + 'd) -> Self {
|
||||
@ -458,29 +395,28 @@ where
|
||||
}
|
||||
|
||||
/// I2S TX channel
|
||||
pub struct I2sTx<'d, DmaMode, T = AnyI2s>
|
||||
pub struct I2sTx<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
i2s: PeripheralRef<'d, T>,
|
||||
tx_channel: ChannelTx<'d, DmaMode, T::Dma>,
|
||||
i2s: PeripheralRef<'d, AnyI2s>,
|
||||
tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<AnyI2s>>,
|
||||
tx_chain: DescriptorChain,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<DmaMode, T> core::fmt::Debug for I2sTx<'_, DmaMode, T>
|
||||
impl<Dm> core::fmt::Debug for I2sTx<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("I2sTx").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<DmaMode, T> DmaSupport for I2sTx<'_, DmaMode, T>
|
||||
impl<Dm> DmaSupport for I2sTx<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
|
||||
self.i2s.wait_for_tx_done();
|
||||
@ -491,12 +427,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DmaMode, T> DmaSupportTx for I2sTx<'d, DmaMode, T>
|
||||
impl<'d, Dm> DmaSupportTx for I2sTx<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
type TX = ChannelTx<'d, DmaMode, T::Dma>;
|
||||
type TX = ChannelTx<'d, Dm, PeripheralTxChannel<AnyI2s>>;
|
||||
|
||||
fn tx(&mut self) -> &mut Self::TX {
|
||||
&mut self.tx_channel
|
||||
@ -507,10 +442,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<DmaMode, T> I2sTx<'_, DmaMode, T>
|
||||
impl<Dm> I2sTx<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> {
|
||||
self.start_tx_transfer(&data, false)?;
|
||||
@ -528,7 +462,7 @@ where
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
TXBUF: ReadBuffer,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
let (ptr, len) = unsafe { words.read_buffer() };
|
||||
|
||||
@ -589,30 +523,28 @@ where
|
||||
}
|
||||
|
||||
/// I2S RX channel
|
||||
pub struct I2sRx<'d, DmaMode, T = AnyI2s>
|
||||
pub struct I2sRx<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
i2s: PeripheralRef<'d, T>,
|
||||
rx_channel: ChannelRx<'d, DmaMode, T::Dma>,
|
||||
i2s: PeripheralRef<'d, AnyI2s>,
|
||||
rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<AnyI2s>>,
|
||||
rx_chain: DescriptorChain,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<DmaMode, T> core::fmt::Debug for I2sRx<'_, DmaMode, T>
|
||||
impl<Dm> core::fmt::Debug for I2sRx<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("I2sRx").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<DmaMode, T> DmaSupport for I2sRx<'_, DmaMode, T>
|
||||
impl<Dm> DmaSupport for I2sRx<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
|
||||
self.i2s.wait_for_rx_done();
|
||||
@ -623,12 +555,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DmaMode, T> DmaSupportRx for I2sRx<'d, DmaMode, T>
|
||||
impl<'d, Dm> DmaSupportRx for I2sRx<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
type RX = ChannelRx<'d, DmaMode, T::Dma>;
|
||||
type RX = ChannelRx<'d, Dm, PeripheralRxChannel<AnyI2s>>;
|
||||
|
||||
fn rx(&mut self) -> &mut Self::RX {
|
||||
&mut self.rx_channel
|
||||
@ -639,10 +570,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<DmaMode, T> I2sRx<'_, DmaMode, T>
|
||||
impl<Dm> I2sRx<'_, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
DmaMode: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn read_bytes(&mut self, mut data: &mut [u8]) -> Result<(), Error> {
|
||||
self.start_rx_transfer(&mut data, false)?;
|
||||
@ -737,15 +667,7 @@ mod private {
|
||||
use enumset::EnumSet;
|
||||
use fugit::HertzU32;
|
||||
|
||||
use super::{
|
||||
DataFormat,
|
||||
I2sInterrupt,
|
||||
I2sRx,
|
||||
I2sTx,
|
||||
RegisterAccess,
|
||||
Standard,
|
||||
I2S_LL_MCLK_DIVIDER_MAX,
|
||||
};
|
||||
use super::*;
|
||||
#[cfg(not(i2s1))]
|
||||
use crate::peripherals::i2s0::RegisterBlock;
|
||||
// on ESP32-S3 I2S1 doesn't support all features - use that to avoid using those features
|
||||
@ -763,29 +685,30 @@ mod private {
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::{Interrupt, I2S0},
|
||||
private,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
pub struct TxCreator<'d, M, T>
|
||||
pub struct TxCreator<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
pub i2s: PeripheralRef<'d, T>,
|
||||
pub tx_channel: ChannelTx<'d, M, T::Dma>,
|
||||
pub i2s: PeripheralRef<'d, AnyI2s>,
|
||||
pub tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<AnyI2s>>,
|
||||
pub descriptors: &'static mut [DmaDescriptor],
|
||||
pub(crate) guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<'d, M, T> TxCreator<'d, M, T>
|
||||
impl<'d, Dm> TxCreator<'d, Dm>
|
||||
where
|
||||
M: Mode,
|
||||
T: RegisterAccess,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
pub fn build(self) -> I2sTx<'d, M, T> {
|
||||
pub fn build(self) -> I2sTx<'d, Dm> {
|
||||
let peripheral = self.i2s.peripheral();
|
||||
I2sTx {
|
||||
i2s: self.i2s,
|
||||
tx_channel: self.tx_channel,
|
||||
tx_chain: DescriptorChain::new(self.descriptors),
|
||||
_guard: PeripheralGuard::new(peripheral),
|
||||
}
|
||||
}
|
||||
|
||||
@ -823,26 +746,27 @@ mod private {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RxCreator<'d, M, T>
|
||||
pub struct RxCreator<'d, Dm>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
M: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
pub i2s: PeripheralRef<'d, T>,
|
||||
pub rx_channel: ChannelRx<'d, M, T::Dma>,
|
||||
pub i2s: PeripheralRef<'d, AnyI2s>,
|
||||
pub rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<AnyI2s>>,
|
||||
pub descriptors: &'static mut [DmaDescriptor],
|
||||
pub(crate) guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<'d, M, T> RxCreator<'d, M, T>
|
||||
impl<'d, Dm> RxCreator<'d, Dm>
|
||||
where
|
||||
M: Mode,
|
||||
T: RegisterAccess,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
pub fn build(self) -> I2sRx<'d, M, T> {
|
||||
pub fn build(self) -> I2sRx<'d, Dm> {
|
||||
let peripheral = self.i2s.peripheral();
|
||||
I2sRx {
|
||||
i2s: self.i2s,
|
||||
rx_channel: self.rx_channel,
|
||||
rx_chain: DescriptorChain::new(self.descriptors),
|
||||
_guard: PeripheralGuard::new(peripheral),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1875,10 +1799,11 @@ mod private {
|
||||
|
||||
/// Async functionality
|
||||
pub mod asynch {
|
||||
use super::{Error, I2sRx, I2sTx, RegisterAccess};
|
||||
use super::{Error, I2sRx, I2sTx, RegisterAccessPrivate};
|
||||
use crate::{
|
||||
dma::{
|
||||
asynch::{DmaRxDoneChFuture, DmaRxFuture, DmaTxDoneChFuture, DmaTxFuture},
|
||||
DmaEligible,
|
||||
ReadBuffer,
|
||||
Rx,
|
||||
RxCircularState,
|
||||
@ -1889,10 +1814,7 @@ pub mod asynch {
|
||||
Async,
|
||||
};
|
||||
|
||||
impl<'d, T> I2sTx<'d, Async, T>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
impl<'d> I2sTx<'d, Async> {
|
||||
/// One-shot write I2S.
|
||||
pub async fn write_dma_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
|
||||
let (ptr, len) = (words.as_ptr(), words.len());
|
||||
@ -1919,7 +1841,7 @@ pub mod asynch {
|
||||
pub fn write_dma_circular_async<TXBUF: ReadBuffer>(
|
||||
mut self,
|
||||
words: TXBUF,
|
||||
) -> Result<I2sWriteDmaTransferAsync<'d, TXBUF, T>, Error> {
|
||||
) -> Result<I2sWriteDmaTransferAsync<'d, TXBUF>, Error> {
|
||||
let (ptr, len) = unsafe { words.read_buffer() };
|
||||
|
||||
// Reset TX unit and TX FIFO
|
||||
@ -1950,19 +1872,13 @@ pub mod asynch {
|
||||
}
|
||||
|
||||
/// An in-progress async circular DMA write transfer.
|
||||
pub struct I2sWriteDmaTransferAsync<'d, BUFFER, T = super::AnyI2s>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
i2s_tx: I2sTx<'d, Async, T>,
|
||||
pub struct I2sWriteDmaTransferAsync<'d, BUFFER> {
|
||||
i2s_tx: I2sTx<'d, Async>,
|
||||
state: TxCircularState,
|
||||
_buffer: BUFFER,
|
||||
}
|
||||
|
||||
impl<T, BUFFER> I2sWriteDmaTransferAsync<'_, BUFFER, T>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
impl<BUFFER> I2sWriteDmaTransferAsync<'_, BUFFER> {
|
||||
/// How many bytes can be pushed into the DMA transaction.
|
||||
/// Will wait for more than 0 bytes available.
|
||||
pub async fn available(&mut self) -> Result<usize, Error> {
|
||||
@ -1999,10 +1915,7 @@ pub mod asynch {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T> I2sRx<'d, Async, T>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
impl<'d> I2sRx<'d, Async> {
|
||||
/// One-shot read I2S.
|
||||
pub async fn read_dma_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
|
||||
let (ptr, len) = (words.as_mut_ptr(), words.len());
|
||||
@ -2037,7 +1950,7 @@ pub mod asynch {
|
||||
pub fn read_dma_circular_async<RXBUF>(
|
||||
mut self,
|
||||
mut words: RXBUF,
|
||||
) -> Result<I2sReadDmaTransferAsync<'d, RXBUF, T>, Error>
|
||||
) -> Result<I2sReadDmaTransferAsync<'d, RXBUF>, Error>
|
||||
where
|
||||
RXBUF: WriteBuffer,
|
||||
{
|
||||
@ -2073,19 +1986,13 @@ pub mod asynch {
|
||||
}
|
||||
|
||||
/// An in-progress async circular DMA read transfer.
|
||||
pub struct I2sReadDmaTransferAsync<'d, BUFFER, T = super::AnyI2s>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
i2s_rx: I2sRx<'d, Async, T>,
|
||||
pub struct I2sReadDmaTransferAsync<'d, BUFFER> {
|
||||
i2s_rx: I2sRx<'d, Async>,
|
||||
state: RxCircularState,
|
||||
_buffer: BUFFER,
|
||||
}
|
||||
|
||||
impl<T, BUFFER> I2sReadDmaTransferAsync<'_, BUFFER, T>
|
||||
where
|
||||
T: RegisterAccess,
|
||||
{
|
||||
impl<BUFFER> I2sReadDmaTransferAsync<'_, BUFFER> {
|
||||
/// How many bytes can be popped from the DMA transaction.
|
||||
/// Will wait for more than 0 bytes available.
|
||||
pub async fn available(&mut self) -> Result<usize, Error> {
|
||||
|
||||
@ -34,6 +34,69 @@
|
||||
//! - `GPIO`
|
||||
//! - `DMA`
|
||||
//! - `system` (to configure and enable the I2S peripheral)
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::dma::DmaTxBuf;
|
||||
//! # use esp_hal::dma_buffers;
|
||||
//! # use esp_hal::delay::Delay;
|
||||
//! # use esp_hal::i2s::parallel::{I2sParallel, TxEightBits};
|
||||
//!
|
||||
//! const BUFFER_SIZE: usize = 256;
|
||||
//!
|
||||
//! let delay = Delay::new();
|
||||
//! let dma_channel = peripherals.DMA_I2S1;
|
||||
//! let i2s = peripherals.I2S1;
|
||||
//! let clock = peripherals.GPIO25;
|
||||
//!
|
||||
//! let pins = TxEightBits::new(
|
||||
//! peripherals.GPIO16,
|
||||
//! peripherals.GPIO4,
|
||||
//! peripherals.GPIO17,
|
||||
//! peripherals.GPIO18,
|
||||
//! peripherals.GPIO5,
|
||||
//! peripherals.GPIO19,
|
||||
//! peripherals.GPIO12,
|
||||
//! peripherals.GPIO14,
|
||||
//! );
|
||||
//!
|
||||
//! let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, BUFFER_SIZE);
|
||||
//! let mut parallel = I2sParallel::new(
|
||||
//! i2s,
|
||||
//! dma_channel,
|
||||
//! 1.MHz(),
|
||||
//! pins,
|
||||
//! clock,
|
||||
//! ).into_async();
|
||||
//!
|
||||
//! for (i, data) in tx_buffer.chunks_mut(4).enumerate() {
|
||||
//! let offset = i * 4;
|
||||
//! // i2s parallel driver expects the buffer to be interleaved
|
||||
//! data[0] = (offset + 2) as u8;
|
||||
//! data[1] = (offset + 3) as u8;
|
||||
//! data[2] = offset as u8;
|
||||
//! data[3] = (offset + 1) as u8;
|
||||
//! }
|
||||
//!
|
||||
//! let mut tx_buf: DmaTxBuf =
|
||||
//! DmaTxBuf::new(tx_descriptors, tx_buffer).expect("DmaTxBuf::new failed");
|
||||
//!
|
||||
//! // Sending 256 bytes.
|
||||
//! loop {
|
||||
//! let xfer = match parallel.send(tx_buf) {
|
||||
//! Ok(xfer) => xfer,
|
||||
//! Err(_) => {
|
||||
//! panic!("Failed to send buffer");
|
||||
//! }
|
||||
//! };
|
||||
//! (parallel, tx_buf) = xfer.wait();
|
||||
//! delay.delay_millis(10);
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
use core::{
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
@ -46,11 +109,12 @@ use crate::{
|
||||
asynch::DmaTxFuture,
|
||||
Channel,
|
||||
ChannelTx,
|
||||
DmaChannelConvert,
|
||||
DmaChannelFor,
|
||||
DmaEligible,
|
||||
DmaError,
|
||||
DmaPeripheral,
|
||||
DmaTxBuffer,
|
||||
PeripheralTxChannel,
|
||||
Tx,
|
||||
},
|
||||
gpio::{
|
||||
@ -60,9 +124,10 @@ use crate::{
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::{i2s0::RegisterBlock, I2S0, I2S1},
|
||||
private::Internal,
|
||||
system::PeripheralClockControl,
|
||||
system::PeripheralGuard,
|
||||
Async,
|
||||
Mode,
|
||||
Blocking,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -170,58 +235,35 @@ impl<'d> TxPins<'d> for TxEightBits<'d> {
|
||||
}
|
||||
|
||||
/// I2S Parallel Interface
|
||||
pub struct I2sParallel<'d, DM, I = AnyI2s>
|
||||
pub struct I2sParallel<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
I: Instance,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
instance: PeripheralRef<'d, I>,
|
||||
tx_channel: ChannelTx<'d, DM, I::Dma>,
|
||||
instance: PeripheralRef<'d, AnyI2s>,
|
||||
tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<AnyI2s>>,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<'d, DM> I2sParallel<'d, DM>
|
||||
where
|
||||
DM: Mode,
|
||||
{
|
||||
impl<'d> I2sParallel<'d, Blocking> {
|
||||
/// Create a new I2S Parallel Interface
|
||||
pub fn new<CH>(
|
||||
i2s: impl Peripheral<P = impl Instance> + 'd,
|
||||
channel: Channel<'d, DM, CH>,
|
||||
frequency: impl Into<fugit::HertzU32>,
|
||||
pins: impl TxPins<'d>,
|
||||
clock_pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<<AnyI2s as DmaEligible>::Dma>,
|
||||
{
|
||||
Self::new_typed(i2s.map_into(), channel, frequency, pins, clock_pin)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, I, DM> I2sParallel<'d, DM, I>
|
||||
where
|
||||
I: Instance,
|
||||
DM: Mode,
|
||||
{
|
||||
/// Create a new I2S Parallel Interface
|
||||
pub fn new_typed<CH>(
|
||||
i2s: impl Peripheral<P = I> + 'd,
|
||||
channel: Channel<'d, DM, CH>,
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
frequency: impl Into<fugit::HertzU32>,
|
||||
mut pins: impl TxPins<'d>,
|
||||
clock_pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<I::Dma>,
|
||||
CH: DmaChannelFor<AnyI2s>,
|
||||
{
|
||||
crate::into_ref!(i2s);
|
||||
crate::into_mapped_ref!(i2s);
|
||||
crate::into_mapped_ref!(clock_pin);
|
||||
|
||||
let channel = Channel::new(channel.map(|ch| ch.degrade()));
|
||||
channel.runtime_ensure_compatible(&i2s);
|
||||
let channel = channel.degrade();
|
||||
|
||||
PeripheralClockControl::reset(i2s.peripheral());
|
||||
PeripheralClockControl::enable(i2s.peripheral());
|
||||
let guard = PeripheralGuard::new(i2s.peripheral());
|
||||
|
||||
// configure the I2S peripheral for parallel mode
|
||||
i2s.setup(frequency, pins.bus_width());
|
||||
// setup the clock pin
|
||||
@ -232,14 +274,40 @@ where
|
||||
Self {
|
||||
instance: i2s,
|
||||
tx_channel: channel.tx,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the I2S instance into async mode.
|
||||
pub fn into_async(self) -> I2sParallel<'d, Async> {
|
||||
I2sParallel {
|
||||
instance: self.instance,
|
||||
tx_channel: self.tx_channel.into_async(),
|
||||
_guard: self._guard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> I2sParallel<'d, Async> {
|
||||
/// Converts the I2S instance into async mode.
|
||||
pub fn into_blocking(self) -> I2sParallel<'d, Blocking> {
|
||||
I2sParallel {
|
||||
instance: self.instance,
|
||||
tx_channel: self.tx_channel.into_blocking(),
|
||||
_guard: self._guard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, Dm> I2sParallel<'d, Dm>
|
||||
where
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Write data to the I2S peripheral
|
||||
pub fn send<BUF: DmaTxBuffer>(
|
||||
mut self,
|
||||
mut data: BUF,
|
||||
) -> Result<I2sParallelTransfer<'d, BUF, DM, I>, (DmaError, Self, BUF)> {
|
||||
) -> Result<I2sParallelTransfer<'d, BUF, Dm>, (DmaError, Self, BUF)> {
|
||||
self.instance.tx_reset();
|
||||
self.instance.tx_fifo_reset();
|
||||
let result = unsafe {
|
||||
@ -261,21 +329,19 @@ where
|
||||
|
||||
/// Represents an ongoing (or potentially finished) transfer using the i2s
|
||||
/// parallel interface
|
||||
pub struct I2sParallelTransfer<'d, BUF, DM, I = AnyI2s>
|
||||
pub struct I2sParallelTransfer<'d, BUF, Dm>
|
||||
where
|
||||
I: Instance,
|
||||
BUF: DmaTxBuffer,
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
i2s: ManuallyDrop<I2sParallel<'d, DM, I>>,
|
||||
i2s: ManuallyDrop<I2sParallel<'d, Dm>>,
|
||||
buf_view: ManuallyDrop<BUF::View>,
|
||||
}
|
||||
|
||||
impl<'d, I, BUF, DM> I2sParallelTransfer<'d, BUF, DM, I>
|
||||
impl<'d, BUF, Dm> I2sParallelTransfer<'d, BUF, Dm>
|
||||
where
|
||||
I: Instance,
|
||||
BUF: DmaTxBuffer,
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Returns true when [Self::wait] will not block.
|
||||
pub fn is_done(&self) -> bool {
|
||||
@ -283,7 +349,7 @@ where
|
||||
}
|
||||
|
||||
/// Wait for the transfer to finish
|
||||
pub fn wait(mut self) -> (I2sParallel<'d, DM, I>, BUF) {
|
||||
pub fn wait(mut self) -> (I2sParallel<'d, Dm>, BUF) {
|
||||
self.i2s.instance.tx_wait_done();
|
||||
let i2s = unsafe { ManuallyDrop::take(&mut self.i2s) };
|
||||
let view = unsafe { ManuallyDrop::take(&mut self.buf_view) };
|
||||
@ -297,9 +363,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, I, BUF> I2sParallelTransfer<'d, BUF, Async, I>
|
||||
impl<'d, BUF> I2sParallelTransfer<'d, BUF, Async>
|
||||
where
|
||||
I: Instance,
|
||||
BUF: DmaTxBuffer,
|
||||
{
|
||||
/// Wait for the transfer to finish
|
||||
@ -308,11 +373,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, I, BUF, DM> Deref for I2sParallelTransfer<'d, BUF, DM, I>
|
||||
impl<BUF, Dm> Deref for I2sParallelTransfer<'_, BUF, Dm>
|
||||
where
|
||||
I: Instance,
|
||||
BUF: DmaTxBuffer,
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
type Target = BUF::View;
|
||||
|
||||
@ -321,22 +385,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, I, BUF, DM> DerefMut for I2sParallelTransfer<'d, BUF, DM, I>
|
||||
impl<BUF, Dm> DerefMut for I2sParallelTransfer<'_, BUF, Dm>
|
||||
where
|
||||
I: Instance,
|
||||
BUF: DmaTxBuffer,
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buf_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, I, BUF, DM> Drop for I2sParallelTransfer<'d, BUF, DM, I>
|
||||
impl<BUF, Dm> Drop for I2sParallelTransfer<'_, BUF, Dm>
|
||||
where
|
||||
I: Instance,
|
||||
BUF: DmaTxBuffer,
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
//! We reserve a number of CPU interrupts, which cannot be used; see
|
||||
//! [`RESERVED_INTERRUPTS`].
|
||||
//!
|
||||
//! ## Example
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Using the peripheral driver to register an interrupt handler
|
||||
//!
|
||||
@ -60,7 +60,7 @@
|
||||
//!
|
||||
//! #[handler(priority = Priority::Priority1)]
|
||||
//! fn swint0_handler() {
|
||||
//! // esp_println::println!("SW interrupt0");
|
||||
//! println!("SW interrupt0");
|
||||
//! critical_section::with(|cs| {
|
||||
//! SWINT0.borrow_ref(cs).as_ref().unwrap().reset();
|
||||
//! });
|
||||
@ -81,12 +81,45 @@ mod xtensa;
|
||||
|
||||
pub mod software;
|
||||
|
||||
#[cfg(xtensa)]
|
||||
#[no_mangle]
|
||||
extern "C" fn EspDefaultHandler(_level: u32, _interrupt: crate::peripherals::Interrupt) {
|
||||
panic!("Unhandled level {} interrupt: {:?}", _level, _interrupt);
|
||||
}
|
||||
|
||||
#[cfg(riscv)]
|
||||
#[no_mangle]
|
||||
extern "C" fn EspDefaultHandler(_interrupt: crate::peripherals::Interrupt) {
|
||||
panic!("Unhandled interrupt: {:?}", _interrupt);
|
||||
}
|
||||
|
||||
/// Default (unhandled) interrupt handler
|
||||
pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new(
|
||||
unsafe { core::mem::transmute::<*const (), extern "C" fn()>(EspDefaultHandler as *const ()) },
|
||||
Priority::min(),
|
||||
);
|
||||
|
||||
/// Trait implemented by drivers which allow the user to set an
|
||||
/// [InterruptHandler]
|
||||
pub trait InterruptConfigurable: crate::private::Sealed {
|
||||
/// Set the interrupt handler
|
||||
///
|
||||
/// Note that this will replace any previously registered interrupt handler.
|
||||
/// Some peripherals offer a shared interrupt handler for multiple purposes.
|
||||
/// It's the users duty to honor this.
|
||||
///
|
||||
/// You can restore the default/unhandled interrupt handler by using
|
||||
/// [DEFAULT_INTERRUPT_HANDLER]
|
||||
fn set_interrupt_handler(&mut self, handler: InterruptHandler);
|
||||
}
|
||||
|
||||
/// An interrupt handler
|
||||
#[cfg_attr(
|
||||
multi_core,
|
||||
doc = "**Note**: Interrupts are handled on the core they were setup on, if a driver is initialized on core 0, and moved to core 1, core 0 will still handle the interrupt."
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InterruptHandler {
|
||||
f: extern "C" fn(),
|
||||
prio: Priority,
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
//! interrupt15() => Priority::Priority15
|
||||
//! ```
|
||||
|
||||
pub(crate) use esp_riscv_rt::riscv::interrupt::free;
|
||||
pub use esp_riscv_rt::TrapFrame;
|
||||
use riscv::register::{mcause, mtvec};
|
||||
|
||||
@ -117,7 +118,7 @@ pub enum CpuInterrupt {
|
||||
}
|
||||
|
||||
/// Interrupt priority levels.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Priority {
|
||||
@ -167,9 +168,35 @@ impl Priority {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Priority {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Priority::None),
|
||||
1 => Ok(Priority::Priority1),
|
||||
2 => Ok(Priority::Priority2),
|
||||
3 => Ok(Priority::Priority3),
|
||||
4 => Ok(Priority::Priority4),
|
||||
5 => Ok(Priority::Priority5),
|
||||
6 => Ok(Priority::Priority6),
|
||||
7 => Ok(Priority::Priority7),
|
||||
8 => Ok(Priority::Priority8),
|
||||
9 => Ok(Priority::Priority9),
|
||||
10 => Ok(Priority::Priority10),
|
||||
11 => Ok(Priority::Priority11),
|
||||
12 => Ok(Priority::Priority12),
|
||||
13 => Ok(Priority::Priority13),
|
||||
14 => Ok(Priority::Priority14),
|
||||
15 => Ok(Priority::Priority15),
|
||||
_ => Err(Error::InvalidInterruptPriority),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The interrupts reserved by the HAL
|
||||
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
|
||||
pub static RESERVED_INTERRUPTS: &[usize] = INTERRUPT_TO_PRIORITY;
|
||||
pub static RESERVED_INTERRUPTS: &[usize] = PRIORITY_TO_INTERRUPT;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
@ -681,6 +708,34 @@ mod classic {
|
||||
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
|
||||
intr.cpu_int_thresh().write(|w| w.bits(stored_prio));
|
||||
}
|
||||
|
||||
/// Get the current run level (the level below which interrupts are masked).
|
||||
pub(crate) fn current_runlevel() -> Priority {
|
||||
let intr = unsafe { crate::peripherals::INTERRUPT_CORE0::steal() };
|
||||
let prev_interrupt_priority = intr.cpu_int_thresh().read().bits().saturating_sub(1) as u8;
|
||||
|
||||
unwrap!(Priority::try_from(prev_interrupt_priority))
|
||||
}
|
||||
|
||||
/// Changes the current run level (the level below which interrupts are
|
||||
/// masked), and returns the previous run level.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be used to raise the runlevel and to restore it
|
||||
/// to a previous value. It must not be used to arbitrarily lower the
|
||||
/// runlevel.
|
||||
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
|
||||
let prev_interrupt_priority = current_runlevel();
|
||||
|
||||
// The CPU responds to interrupts `>= level`, but we want to also disable
|
||||
// interrupts at `level` so we set the threshold to `level + 1`.
|
||||
crate::peripherals::INTERRUPT_CORE0::steal()
|
||||
.cpu_int_thresh()
|
||||
.write(|w| w.bits(level as u32 + 1));
|
||||
|
||||
prev_interrupt_priority
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(plic)]
|
||||
@ -817,4 +872,36 @@ mod plic {
|
||||
plic.mxint_thresh()
|
||||
.write(|w| w.cpu_mxint_thresh().bits(stored_prio as u8));
|
||||
}
|
||||
|
||||
/// Get the current run level (the level below which interrupts are masked).
|
||||
pub(crate) fn current_runlevel() -> Priority {
|
||||
let prev_interrupt_priority = unsafe { crate::peripherals::PLIC_MX::steal() }
|
||||
.mxint_thresh()
|
||||
.read()
|
||||
.cpu_mxint_thresh()
|
||||
.bits()
|
||||
.saturating_sub(1);
|
||||
|
||||
unwrap!(Priority::try_from(prev_interrupt_priority))
|
||||
}
|
||||
|
||||
/// Changes the current run level (the level below which interrupts are
|
||||
/// masked), and returns the previous run level.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be used to raise the runlevel and to restore it
|
||||
/// to a previous value. It must not be used to arbitrarily lower the
|
||||
/// runlevel.
|
||||
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
|
||||
let prev_interrupt_priority = current_runlevel();
|
||||
|
||||
// The CPU responds to interrupts `>= level`, but we want to also disable
|
||||
// interrupts at `level` so we set the threshold to `level + 1`.
|
||||
crate::peripherals::PLIC_MX::steal()
|
||||
.mxint_thresh()
|
||||
.write(|w| w.cpu_mxint_thresh().bits(level as u8 + 1));
|
||||
|
||||
prev_interrupt_priority
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
//! // Set up the interrupt handler. Do this in a critical section so the global
|
||||
//! // contains the interrupt object before the interrupt is triggered.
|
||||
//! critical_section::with(|cs| {
|
||||
//! int0.set_interrupt_handler(interrupt_handler);
|
||||
//! int0.set_interrupt_handler(swint0_handler);
|
||||
//! SWINT0.borrow_ref_mut(cs).replace(int0);
|
||||
//! });
|
||||
//! # }
|
||||
@ -35,8 +35,8 @@
|
||||
//! Mutex::new(RefCell::new(None));
|
||||
//!
|
||||
//! #[handler]
|
||||
//! fn interrupt_handler() {
|
||||
//! // esp_println::println!("SW interrupt0 handled");
|
||||
//! fn swint0_handler() {
|
||||
//! println!("SW interrupt0 handled");
|
||||
//!
|
||||
//! // Clear the interrupt request.
|
||||
//! critical_section::with(|cs| {
|
||||
@ -45,7 +45,7 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::{interrupt::InterruptHandler, InterruptConfigurable};
|
||||
use crate::interrupt::{InterruptConfigurable, InterruptHandler};
|
||||
|
||||
/// A software interrupt can be triggered by software.
|
||||
#[non_exhaustive]
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! Interrupt handling
|
||||
|
||||
use xtensa_lx::interrupt;
|
||||
pub(crate) use xtensa_lx::interrupt::free;
|
||||
use xtensa_lx_rt::exception::Context;
|
||||
|
||||
pub use self::vectored::*;
|
||||
@ -334,13 +335,45 @@ unsafe fn core1_interrupt_peripheral() -> *const crate::peripherals::interrupt_c
|
||||
crate::peripherals::INTERRUPT_CORE1::PTR
|
||||
}
|
||||
|
||||
/// Get the current run level (the level below which interrupts are masked).
|
||||
pub(crate) fn current_runlevel() -> Priority {
|
||||
let ps: u32;
|
||||
unsafe { core::arch::asm!("rsr.ps {0}", out(reg) ps) };
|
||||
|
||||
let prev_interrupt_priority = ps as u8 & 0x0F;
|
||||
|
||||
unwrap!(Priority::try_from(prev_interrupt_priority))
|
||||
}
|
||||
|
||||
/// Changes the current run level (the level below which interrupts are
|
||||
/// masked), and returns the previous run level.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be used to raise the runlevel and to restore it
|
||||
/// to a previous value. It must not be used to arbitrarily lower the
|
||||
/// runlevel.
|
||||
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
|
||||
let token: u32;
|
||||
match level {
|
||||
Priority::None => core::arch::asm!("rsil {0}, 0", out(reg) token),
|
||||
Priority::Priority1 => core::arch::asm!("rsil {0}, 1", out(reg) token),
|
||||
Priority::Priority2 => core::arch::asm!("rsil {0}, 2", out(reg) token),
|
||||
Priority::Priority3 => core::arch::asm!("rsil {0}, 3", out(reg) token),
|
||||
};
|
||||
|
||||
let prev_interrupt_priority = token as u8 & 0x0F;
|
||||
|
||||
unwrap!(Priority::try_from(prev_interrupt_priority))
|
||||
}
|
||||
|
||||
mod vectored {
|
||||
use procmacros::ram;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Interrupt priority levels.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Priority {
|
||||
@ -366,6 +399,20 @@ mod vectored {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Priority {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Priority::None),
|
||||
1 => Ok(Priority::Priority1),
|
||||
2 => Ok(Priority::Priority2),
|
||||
3 => Ok(Priority::Priority3),
|
||||
_ => Err(Error::InvalidInterrupt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CpuInterrupt {
|
||||
#[inline]
|
||||
fn level(&self) -> Priority {
|
||||
|
||||
@ -16,21 +16,12 @@
|
||||
//! master mode.
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::lcd_cam::{cam::{Camera, RxEightBits}, LcdCam};
|
||||
//! # use esp_hal::lcd_cam::{cam::{Camera, Config, RxEightBits}, LcdCam};
|
||||
//! # use fugit::RateExtU32;
|
||||
//! # use esp_hal::dma_rx_stream_buffer;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority};
|
||||
//!
|
||||
//! # let dma = Dma::new(peripherals.DMA);
|
||||
//! # let channel = dma.channel0;
|
||||
//!
|
||||
//! # let dma_buf = dma_rx_stream_buffer!(20 * 1000, 1000);
|
||||
//!
|
||||
//! # let channel = channel.configure(
|
||||
//! # false,
|
||||
//! # DmaPriority::Priority0,
|
||||
//! # );
|
||||
//!
|
||||
//! let mclk_pin = peripherals.GPIO15;
|
||||
//! let vsync_pin = peripherals.GPIO6;
|
||||
//! let href_pin = peripherals.GPIO7;
|
||||
@ -46,15 +37,18 @@
|
||||
//! peripherals.GPIO16,
|
||||
//! );
|
||||
//!
|
||||
//! let mut config = Config::default();
|
||||
//! config.frequency = 20.MHz();
|
||||
//!
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//! let mut camera = Camera::new(
|
||||
//! lcd_cam.cam,
|
||||
//! channel.rx,
|
||||
//! peripherals.DMA_CH0,
|
||||
//! data_pins,
|
||||
//! 20u32.MHz(),
|
||||
//! config,
|
||||
//! )
|
||||
//! // Remove this for slave mode.
|
||||
//! .with_master_clock(mclk_pin)
|
||||
//! .unwrap()
|
||||
//! .with_master_clock(mclk_pin) // Remove this for slave mode
|
||||
//! .with_pixel_clock(pclk_pin)
|
||||
//! .with_ctrl_pins(vsync_pin, href_pin);
|
||||
//!
|
||||
@ -68,20 +62,21 @@ use core::{
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use fugit::HertzU32;
|
||||
use fugit::{HertzU32, RateExtU32};
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{ChannelRx, DmaChannelConvert, DmaEligible, DmaError, DmaPeripheral, DmaRxBuffer, Rx},
|
||||
dma::{ChannelRx, DmaError, DmaPeripheral, DmaRxBuffer, PeripheralRxChannel, Rx, RxChannelFor},
|
||||
gpio::{
|
||||
interconnect::{PeripheralInput, PeripheralOutput},
|
||||
InputSignal,
|
||||
OutputSignal,
|
||||
Pull,
|
||||
},
|
||||
lcd_cam::{calculate_clkm, BitOrder, ByteOrder},
|
||||
lcd_cam::{calculate_clkm, BitOrder, ByteOrder, ClockError},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
system::{self, GenericPeripheralGuard},
|
||||
Blocking,
|
||||
};
|
||||
|
||||
@ -117,125 +112,114 @@ pub enum VsyncFilterThreshold {
|
||||
Eight,
|
||||
}
|
||||
|
||||
/// Vsync Filter Threshold
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ConfigError {
|
||||
/// The frequency is out of range.
|
||||
Clock(ClockError),
|
||||
}
|
||||
|
||||
/// Represents the camera interface.
|
||||
pub struct Cam<'d> {
|
||||
/// The LCD_CAM peripheral reference for managing the camera functionality.
|
||||
pub(crate) lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
pub(super) _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>,
|
||||
}
|
||||
|
||||
/// Represents the camera interface with DMA support.
|
||||
pub struct Camera<'d> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
rx_channel: ChannelRx<'d, Blocking, <LCD_CAM as DmaEligible>::Dma>,
|
||||
rx_channel: ChannelRx<'d, Blocking, PeripheralRxChannel<LCD_CAM>>,
|
||||
_guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>,
|
||||
}
|
||||
|
||||
impl<'d> Camera<'d> {
|
||||
/// Creates a new `Camera` instance with DMA support.
|
||||
pub fn new<P, CH>(
|
||||
cam: Cam<'d>,
|
||||
channel: ChannelRx<'d, Blocking, CH>,
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
_pins: P,
|
||||
frequency: HertzU32,
|
||||
) -> Self
|
||||
config: Config,
|
||||
) -> Result<Self, ConfigError>
|
||||
where
|
||||
CH: DmaChannelConvert<<LCD_CAM as DmaEligible>::Dma>,
|
||||
CH: RxChannelFor<LCD_CAM>,
|
||||
P: RxPins,
|
||||
{
|
||||
let lcd_cam = cam.lcd_cam;
|
||||
let rx_channel = ChannelRx::new(channel.map(|ch| ch.degrade()));
|
||||
|
||||
let mut this = Self {
|
||||
lcd_cam: cam.lcd_cam,
|
||||
rx_channel,
|
||||
_guard: cam._guard,
|
||||
};
|
||||
|
||||
this.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_2byte_en().bit(P::BUS_WIDTH == 2));
|
||||
|
||||
this.apply_config(&config)?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// Applies the configuration to the camera interface.
|
||||
pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
|
||||
let clocks = Clocks::get();
|
||||
let (i, divider) = calculate_clkm(
|
||||
frequency.to_Hz() as _,
|
||||
config.frequency.to_Hz() as _,
|
||||
&[
|
||||
clocks.xtal_clock.to_Hz() as _,
|
||||
clocks.cpu_clock.to_Hz() as _,
|
||||
clocks.crypto_pwm_clock.to_Hz() as _,
|
||||
],
|
||||
);
|
||||
)
|
||||
.map_err(ConfigError::Clock)?;
|
||||
|
||||
lcd_cam.cam_ctrl().write(|w| {
|
||||
self.lcd_cam.cam_ctrl().write(|w| {
|
||||
// Force enable the clock for all configuration registers.
|
||||
unsafe {
|
||||
w.cam_clk_sel().bits((i + 1) as _);
|
||||
w.cam_clkm_div_num().bits(divider.div_num as _);
|
||||
w.cam_clkm_div_b().bits(divider.div_b as _);
|
||||
w.cam_clkm_div_a().bits(divider.div_a as _);
|
||||
w.cam_vsync_filter_thres().bits(0);
|
||||
if let Some(threshold) = config.vsync_filter_threshold {
|
||||
w.cam_vsync_filter_thres().bits(threshold as _);
|
||||
}
|
||||
w.cam_byte_order()
|
||||
.bit(config.byte_order != ByteOrder::default());
|
||||
w.cam_bit_order()
|
||||
.bit(config.bit_order != BitOrder::default());
|
||||
w.cam_vs_eof_en().set_bit();
|
||||
w.cam_line_int_en().clear_bit();
|
||||
w.cam_stop_en().clear_bit()
|
||||
}
|
||||
});
|
||||
lcd_cam.cam_ctrl1().write(|w| unsafe {
|
||||
self.lcd_cam.cam_ctrl1().modify(|_, w| unsafe {
|
||||
w.cam_vh_de_mode_en().set_bit();
|
||||
w.cam_rec_data_bytelen().bits(0);
|
||||
w.cam_line_int_num().bits(0);
|
||||
w.cam_vsync_filter_en().clear_bit();
|
||||
w.cam_2byte_en().bit(P::BUS_WIDTH == 2);
|
||||
w.cam_vsync_filter_en()
|
||||
.bit(config.vsync_filter_threshold.is_some());
|
||||
w.cam_clk_inv().clear_bit();
|
||||
w.cam_de_inv().clear_bit();
|
||||
w.cam_hsync_inv().clear_bit();
|
||||
w.cam_vsync_inv().clear_bit()
|
||||
});
|
||||
|
||||
lcd_cam
|
||||
self.lcd_cam
|
||||
.cam_rgb_yuv()
|
||||
.write(|w| w.cam_conv_bypass().clear_bit());
|
||||
|
||||
lcd_cam.cam_ctrl().modify(|_, w| w.cam_update().set_bit());
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| w.cam_update().set_bit());
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
rx_channel: channel.degrade(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Camera<'d> {
|
||||
/// Configures the byte order for the camera data.
|
||||
pub fn set_byte_order(&mut self, byte_order: ByteOrder) -> &mut Self {
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| w.cam_byte_order().bit(byte_order != ByteOrder::default()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the bit order for the camera data.
|
||||
pub fn set_bit_order(&mut self, bit_order: BitOrder) -> &mut Self {
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| w.cam_bit_order().bit(bit_order != BitOrder::default()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the VSYNC filter threshold.
|
||||
pub fn set_vsync_filter(&mut self, threshold: Option<VsyncFilterThreshold>) -> &mut Self {
|
||||
if let Some(threshold) = threshold {
|
||||
let value = match threshold {
|
||||
VsyncFilterThreshold::One => 0,
|
||||
VsyncFilterThreshold::Two => 1,
|
||||
VsyncFilterThreshold::Three => 2,
|
||||
VsyncFilterThreshold::Four => 3,
|
||||
VsyncFilterThreshold::Five => 4,
|
||||
VsyncFilterThreshold::Six => 5,
|
||||
VsyncFilterThreshold::Seven => 6,
|
||||
VsyncFilterThreshold::Eight => 7,
|
||||
};
|
||||
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| unsafe { w.cam_vsync_filter_thres().bits(value) });
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_vsync_filter_en().set_bit());
|
||||
} else {
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_vsync_filter_en().clear_bit());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the master clock (MCLK) pin for the camera interface.
|
||||
pub fn with_master_clock<MCLK: PeripheralOutput>(
|
||||
self,
|
||||
@ -448,7 +432,7 @@ impl<'d, BUF: DmaRxBuffer> CameraTransfer<'d, BUF> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> Deref for CameraTransfer<'d, BUF> {
|
||||
impl<BUF: DmaRxBuffer> Deref for CameraTransfer<'_, BUF> {
|
||||
type Target = BUF::View;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -456,13 +440,13 @@ impl<'d, BUF: DmaRxBuffer> Deref for CameraTransfer<'d, BUF> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> DerefMut for CameraTransfer<'d, BUF> {
|
||||
impl<BUF: DmaRxBuffer> DerefMut for CameraTransfer<'_, BUF> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buffer_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> Drop for CameraTransfer<'d, BUF> {
|
||||
impl<BUF: DmaRxBuffer> Drop for CameraTransfer<'_, BUF> {
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
@ -609,3 +593,31 @@ impl RxPins for RxSixteenBits {
|
||||
pub trait RxPins {
|
||||
const BUS_WIDTH: usize;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Configuration settings for the Camera interface.
|
||||
pub struct Config {
|
||||
/// The pixel clock frequency for the camera interface.
|
||||
pub frequency: HertzU32,
|
||||
|
||||
/// The byte order for the camera data.
|
||||
pub byte_order: ByteOrder,
|
||||
|
||||
/// The bit order for the camera data.
|
||||
pub bit_order: BitOrder,
|
||||
|
||||
/// The Vsync filter threshold.
|
||||
pub vsync_filter_threshold: Option<VsyncFilterThreshold>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frequency: 20.MHz(),
|
||||
byte_order: Default::default(),
|
||||
bit_order: Default::default(),
|
||||
vsync_filter_threshold: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,21 +24,14 @@
|
||||
//! # }
|
||||
//! # };
|
||||
//! # use esp_hal::dma_loop_buffer;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority};
|
||||
//!
|
||||
//! # let dma = Dma::new(peripherals.DMA);
|
||||
//! # let channel = dma.channel0;
|
||||
//!
|
||||
//! # let channel = peripherals.DMA_CH0;
|
||||
//! # let mut dma_buf = dma_loop_buffer!(32);
|
||||
//!
|
||||
//! # let channel = channel.configure(
|
||||
//! # false,
|
||||
//! # DmaPriority::Priority0,
|
||||
//! # );
|
||||
//!
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//!
|
||||
//! let mut config = dpi::Config::default();
|
||||
//! config.frequency = 1.MHz();
|
||||
//! config.clock_mode = ClockMode {
|
||||
//! polarity: Polarity::IdleLow,
|
||||
//! phase: Phase::ShiftLow,
|
||||
@ -66,7 +59,7 @@
|
||||
//! config.de_idle_level = Level::Low;
|
||||
//! config.disable_black_region = false;
|
||||
//!
|
||||
//! let mut dpi = Dpi::new(lcd_cam.lcd, channel.tx, 1.MHz(), config)
|
||||
//! let mut dpi = Dpi::new(lcd_cam.lcd, channel, config).unwrap()
|
||||
//! .with_vsync(peripherals.GPIO3)
|
||||
//! .with_hsync(peripherals.GPIO46)
|
||||
//! .with_de(peripherals.GPIO17)
|
||||
@ -102,61 +95,88 @@
|
||||
//! ```
|
||||
|
||||
use core::{
|
||||
marker::PhantomData,
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use fugit::HertzU32;
|
||||
use fugit::{HertzU32, RateExtU32};
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{ChannelTx, DmaChannelConvert, DmaEligible, DmaError, DmaPeripheral, DmaTxBuffer, Tx},
|
||||
dma::{ChannelTx, DmaError, DmaPeripheral, DmaTxBuffer, PeripheralTxChannel, Tx, TxChannelFor},
|
||||
gpio::{interconnect::PeripheralOutput, Level, OutputSignal},
|
||||
lcd_cam::{
|
||||
calculate_clkm,
|
||||
lcd::{ClockMode, DelayMode, Lcd, Phase, Polarity},
|
||||
BitOrder,
|
||||
ByteOrder,
|
||||
ClockError,
|
||||
},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
Blocking,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
/// Represents the RGB LCD interface.
|
||||
pub struct Dpi<'d> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
tx_channel: ChannelTx<'d, Blocking, <LCD_CAM as DmaEligible>::Dma>,
|
||||
/// Errors that can occur when configuring the DPI peripheral.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ConfigError {
|
||||
/// Clock configuration error.
|
||||
Clock(ClockError),
|
||||
}
|
||||
|
||||
impl<'d> Dpi<'d> {
|
||||
/// Create a new instance of the RGB/DPI driver.
|
||||
pub fn new<DM: Mode, CH>(
|
||||
lcd: Lcd<'d, DM>,
|
||||
channel: ChannelTx<'d, Blocking, CH>,
|
||||
frequency: HertzU32,
|
||||
config: Config,
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<<LCD_CAM as DmaEligible>::Dma>,
|
||||
{
|
||||
let lcd_cam = lcd.lcd_cam;
|
||||
/// Represents the RGB LCD interface.
|
||||
pub struct Dpi<'d, Dm: DriverMode> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
tx_channel: ChannelTx<'d, Blocking, PeripheralTxChannel<LCD_CAM>>,
|
||||
_mode: PhantomData<Dm>,
|
||||
}
|
||||
|
||||
impl<'d, Dm> Dpi<'d, Dm>
|
||||
where
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Create a new instance of the RGB/DPI driver.
|
||||
pub fn new<CH>(
|
||||
lcd: Lcd<'d, Dm>,
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
config: Config,
|
||||
) -> Result<Self, ConfigError>
|
||||
where
|
||||
CH: TxChannelFor<LCD_CAM>,
|
||||
{
|
||||
let tx_channel = ChannelTx::new(channel.map(|ch| ch.degrade()));
|
||||
|
||||
let mut this = Self {
|
||||
lcd_cam: lcd.lcd_cam,
|
||||
tx_channel,
|
||||
_mode: PhantomData,
|
||||
};
|
||||
|
||||
this.apply_config(&config)?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// Applies the configuration to the peripheral.
|
||||
pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
|
||||
let clocks = Clocks::get();
|
||||
// Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf
|
||||
// the LCD_PCLK divider must be at least 2. To make up for this the user
|
||||
// provided frequency is doubled to match.
|
||||
let (i, divider) = calculate_clkm(
|
||||
(frequency.to_Hz() * 2) as _,
|
||||
(config.frequency.to_Hz() * 2) as _,
|
||||
&[
|
||||
clocks.xtal_clock.to_Hz() as _,
|
||||
clocks.cpu_clock.to_Hz() as _,
|
||||
clocks.crypto_pwm_clock.to_Hz() as _,
|
||||
],
|
||||
);
|
||||
)
|
||||
.map_err(ConfigError::Clock)?;
|
||||
|
||||
lcd_cam.lcd_clock().write(|w| unsafe {
|
||||
self.lcd_cam.lcd_clock().write(|w| unsafe {
|
||||
// Force enable the clock for all configuration registers.
|
||||
w.clk_en().set_bit();
|
||||
w.lcd_clk_sel().bits((i + 1) as _);
|
||||
@ -170,13 +190,15 @@ impl<'d> Dpi<'d> {
|
||||
w.lcd_ck_out_edge()
|
||||
.bit(config.clock_mode.phase == Phase::ShiftHigh)
|
||||
});
|
||||
lcd_cam.lcd_user().modify(|_, w| w.lcd_reset().set_bit());
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_reset().set_bit());
|
||||
|
||||
lcd_cam
|
||||
self.lcd_cam
|
||||
.lcd_rgb_yuv()
|
||||
.write(|w| w.lcd_conv_bypass().clear_bit());
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| {
|
||||
self.lcd_cam.lcd_user().modify(|_, w| {
|
||||
if config.format.enable_2byte_mode {
|
||||
w.lcd_8bits_order().bit(false);
|
||||
w.lcd_byte_order()
|
||||
@ -199,7 +221,7 @@ impl<'d> Dpi<'d> {
|
||||
});
|
||||
|
||||
let timing = &config.timing;
|
||||
lcd_cam.lcd_ctrl().modify(|_, w| unsafe {
|
||||
self.lcd_cam.lcd_ctrl().modify(|_, w| unsafe {
|
||||
// Enable RGB mode, and input VSYNC, HSYNC, and DE signals.
|
||||
w.lcd_rgb_mode_en().set_bit();
|
||||
|
||||
@ -210,7 +232,7 @@ impl<'d> Dpi<'d> {
|
||||
w.lcd_vt_height()
|
||||
.bits((timing.vertical_total_height as u16).saturating_sub(1))
|
||||
});
|
||||
lcd_cam.lcd_ctrl1().modify(|_, w| unsafe {
|
||||
self.lcd_cam.lcd_ctrl1().modify(|_, w| unsafe {
|
||||
w.lcd_vb_front()
|
||||
.bits((timing.vertical_blank_front_porch as u8).saturating_sub(1));
|
||||
w.lcd_ha_width()
|
||||
@ -218,7 +240,7 @@ impl<'d> Dpi<'d> {
|
||||
w.lcd_ht_width()
|
||||
.bits((timing.horizontal_total_width as u16).saturating_sub(1))
|
||||
});
|
||||
lcd_cam.lcd_ctrl2().modify(|_, w| unsafe {
|
||||
self.lcd_cam.lcd_ctrl2().modify(|_, w| unsafe {
|
||||
w.lcd_vsync_width()
|
||||
.bits((timing.vsync_width as u8).saturating_sub(1));
|
||||
w.lcd_vsync_idle_pol().bit(config.vsync_idle_level.into());
|
||||
@ -230,7 +252,7 @@ impl<'d> Dpi<'d> {
|
||||
w.lcd_hsync_position().bits(timing.hsync_position as u8)
|
||||
});
|
||||
|
||||
lcd_cam.lcd_misc().modify(|_, w| unsafe {
|
||||
self.lcd_cam.lcd_misc().modify(|_, w| unsafe {
|
||||
// TODO: Find out what this field actually does.
|
||||
// Set the threshold for Async Tx FIFO full event. (5 bits)
|
||||
w.lcd_afifo_threshold_num().bits((1 << 5) - 1);
|
||||
@ -246,13 +268,13 @@ impl<'d> Dpi<'d> {
|
||||
// Enable blank region when LCD sends data out.
|
||||
w.lcd_bk_en().bit(!config.disable_black_region)
|
||||
});
|
||||
lcd_cam.lcd_dly_mode().modify(|_, w| unsafe {
|
||||
self.lcd_cam.lcd_dly_mode().modify(|_, w| unsafe {
|
||||
w.lcd_de_mode().bits(config.de_mode as u8);
|
||||
w.lcd_hsync_mode().bits(config.hsync_mode as u8);
|
||||
w.lcd_vsync_mode().bits(config.vsync_mode as u8);
|
||||
w
|
||||
});
|
||||
lcd_cam.lcd_data_dout_mode().modify(|_, w| unsafe {
|
||||
self.lcd_cam.lcd_data_dout_mode().modify(|_, w| unsafe {
|
||||
w.dout0_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout1_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout2_mode().bits(config.output_bit_mode as u8);
|
||||
@ -271,12 +293,11 @@ impl<'d> Dpi<'d> {
|
||||
w.dout15_mode().bits(config.output_bit_mode as u8)
|
||||
});
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| w.lcd_update().set_bit());
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_update().set_bit());
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
tx_channel: channel.degrade(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Assign the VSYNC pin for the LCD_CAM.
|
||||
@ -527,7 +548,7 @@ impl<'d> Dpi<'d> {
|
||||
mut self,
|
||||
next_frame_en: bool,
|
||||
mut buf: TX,
|
||||
) -> Result<DpiTransfer<'d, TX>, (DmaError, Self, TX)> {
|
||||
) -> Result<DpiTransfer<'d, TX, Dm>, (DmaError, Self, TX)> {
|
||||
let result = unsafe {
|
||||
self.tx_channel
|
||||
.prepare_transfer(DmaPeripheral::LcdCam, &mut buf)
|
||||
@ -566,12 +587,12 @@ impl<'d> Dpi<'d> {
|
||||
|
||||
/// Represents an ongoing (or potentially finished) transfer using the RGB LCD
|
||||
/// interface
|
||||
pub struct DpiTransfer<'d, BUF: DmaTxBuffer> {
|
||||
dpi: ManuallyDrop<Dpi<'d>>,
|
||||
pub struct DpiTransfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> {
|
||||
dpi: ManuallyDrop<Dpi<'d, Dm>>,
|
||||
buffer_view: ManuallyDrop<BUF::View>,
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> DpiTransfer<'d, BUF> {
|
||||
impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> DpiTransfer<'d, BUF, Dm> {
|
||||
/// Returns true when [Self::wait] will not block.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.dpi
|
||||
@ -583,7 +604,7 @@ impl<'d, BUF: DmaTxBuffer> DpiTransfer<'d, BUF> {
|
||||
}
|
||||
|
||||
/// Stops this transfer on the spot and returns the peripheral and buffer.
|
||||
pub fn stop(mut self) -> (Dpi<'d>, BUF) {
|
||||
pub fn stop(mut self) -> (Dpi<'d, Dm>, BUF) {
|
||||
self.stop_peripherals();
|
||||
let (dpi, view) = self.release();
|
||||
(dpi, BUF::from_view(view))
|
||||
@ -593,7 +614,7 @@ impl<'d, BUF: DmaTxBuffer> DpiTransfer<'d, BUF> {
|
||||
///
|
||||
/// Note: If you specified `next_frame_en` as true in [Dpi::send], you're
|
||||
/// just waiting for a DMA error when you call this.
|
||||
pub fn wait(mut self) -> (Result<(), DmaError>, Dpi<'d>, BUF) {
|
||||
pub fn wait(mut self) -> (Result<(), DmaError>, Dpi<'d, Dm>, BUF) {
|
||||
while !self.is_done() {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
@ -616,7 +637,7 @@ impl<'d, BUF: DmaTxBuffer> DpiTransfer<'d, BUF> {
|
||||
(result, dpi, BUF::from_view(view))
|
||||
}
|
||||
|
||||
fn release(mut self) -> (Dpi<'d>, BUF::View) {
|
||||
fn release(mut self) -> (Dpi<'d, Dm>, BUF::View) {
|
||||
// SAFETY: Since forget is called on self, we know that self.dpi and
|
||||
// self.buffer_view won't be touched again.
|
||||
let result = unsafe {
|
||||
@ -640,7 +661,7 @@ impl<'d, BUF: DmaTxBuffer> DpiTransfer<'d, BUF> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> Deref for DpiTransfer<'d, BUF> {
|
||||
impl<BUF: DmaTxBuffer, Dm: DriverMode> Deref for DpiTransfer<'_, BUF, Dm> {
|
||||
type Target = BUF::View;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -648,13 +669,13 @@ impl<'d, BUF: DmaTxBuffer> Deref for DpiTransfer<'d, BUF> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> DerefMut for DpiTransfer<'d, BUF> {
|
||||
impl<BUF: DmaTxBuffer, Dm: DriverMode> DerefMut for DpiTransfer<'_, BUF, Dm> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buffer_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> Drop for DpiTransfer<'d, BUF> {
|
||||
impl<BUF: DmaTxBuffer, Dm: DriverMode> Drop for DpiTransfer<'_, BUF, Dm> {
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
@ -676,6 +697,9 @@ pub struct Config {
|
||||
/// Specifies the clock mode, including polarity and phase settings.
|
||||
pub clock_mode: ClockMode,
|
||||
|
||||
/// The frequency of the pixel clock.
|
||||
pub frequency: HertzU32,
|
||||
|
||||
/// Format of the byte data sent out.
|
||||
pub format: Format,
|
||||
|
||||
@ -713,6 +737,7 @@ impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
clock_mode: Default::default(),
|
||||
frequency: 1.MHz(),
|
||||
format: Default::default(),
|
||||
timing: Default::default(),
|
||||
vsync_idle_level: Level::Low,
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
//! format/timing. The driver mandates DMA (Direct Memory Access) for
|
||||
//! efficient data transfer.
|
||||
//!
|
||||
//! ## Example
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### MIPI-DSI Display
|
||||
//!
|
||||
@ -17,18 +17,10 @@
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::lcd_cam::{LcdCam, lcd::i8080::{Config, I8080, TxEightBits}};
|
||||
//! # use esp_hal::dma_tx_buffer;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority, DmaTxBuf};
|
||||
//!
|
||||
//! # let dma = Dma::new(peripherals.DMA);
|
||||
//! # let channel = dma.channel0;
|
||||
//! # use esp_hal::dma::DmaTxBuf;
|
||||
//!
|
||||
//! # let mut dma_buf = dma_tx_buffer!(32678).unwrap();
|
||||
//!
|
||||
//! # let channel = channel.configure(
|
||||
//! # false,
|
||||
//! # DmaPriority::Priority0,
|
||||
//! # );
|
||||
//!
|
||||
//! let tx_pins = TxEightBits::new(
|
||||
//! peripherals.GPIO9,
|
||||
//! peripherals.GPIO46,
|
||||
@ -41,13 +33,16 @@
|
||||
//! );
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//!
|
||||
//! let mut config = Config::default();
|
||||
//! config.frequency = 20.MHz();
|
||||
//!
|
||||
//! let mut i8080 = I8080::new(
|
||||
//! lcd_cam.lcd,
|
||||
//! channel.tx,
|
||||
//! peripherals.DMA_CH0,
|
||||
//! tx_pins,
|
||||
//! 20.MHz(),
|
||||
//! Config::default(),
|
||||
//! config,
|
||||
//! )
|
||||
//! .unwrap()
|
||||
//! .with_ctrl_pins(peripherals.GPIO0, peripherals.GPIO47);
|
||||
//!
|
||||
//! dma_buf.fill(&[0x55]);
|
||||
@ -63,11 +58,11 @@ use core::{
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use fugit::HertzU32;
|
||||
use fugit::{HertzU32, RateExtU32};
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{ChannelTx, DmaChannelConvert, DmaEligible, DmaError, DmaPeripheral, DmaTxBuffer, Tx},
|
||||
dma::{ChannelTx, DmaError, DmaPeripheral, DmaTxBuffer, PeripheralTxChannel, Tx, TxChannelFor},
|
||||
gpio::{
|
||||
interconnect::{OutputConnection, PeripheralOutput},
|
||||
OutputSignal,
|
||||
@ -77,6 +72,7 @@ use crate::{
|
||||
lcd::{ClockMode, DelayMode, Phase, Polarity},
|
||||
BitOrder,
|
||||
ByteOrder,
|
||||
ClockError,
|
||||
Instance,
|
||||
Lcd,
|
||||
LCD_DONE_WAKER,
|
||||
@ -84,48 +80,70 @@ use crate::{
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
Blocking,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
/// Represents the I8080 LCD interface.
|
||||
pub struct I8080<'d, DM: Mode> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
tx_channel: ChannelTx<'d, Blocking, <LCD_CAM as DmaEligible>::Dma>,
|
||||
_mode: PhantomData<DM>,
|
||||
/// A configuration error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ConfigError {
|
||||
/// Clock configuration error.
|
||||
Clock(ClockError),
|
||||
}
|
||||
|
||||
impl<'d, DM> I8080<'d, DM>
|
||||
/// Represents the I8080 LCD interface.
|
||||
pub struct I8080<'d, Dm: DriverMode> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
tx_channel: ChannelTx<'d, Blocking, PeripheralTxChannel<LCD_CAM>>,
|
||||
_mode: PhantomData<Dm>,
|
||||
}
|
||||
|
||||
impl<'d, Dm> I8080<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Creates a new instance of the I8080 LCD interface.
|
||||
pub fn new<P, CH>(
|
||||
lcd: Lcd<'d, DM>,
|
||||
channel: ChannelTx<'d, Blocking, CH>,
|
||||
lcd: Lcd<'d, Dm>,
|
||||
channel: impl Peripheral<P = CH> + 'd,
|
||||
mut pins: P,
|
||||
frequency: HertzU32,
|
||||
config: Config,
|
||||
) -> Self
|
||||
) -> Result<Self, ConfigError>
|
||||
where
|
||||
CH: DmaChannelConvert<<LCD_CAM as DmaEligible>::Dma>,
|
||||
CH: TxChannelFor<LCD_CAM>,
|
||||
P: TxPins,
|
||||
{
|
||||
let lcd_cam = lcd.lcd_cam;
|
||||
let tx_channel = ChannelTx::new(channel.map(|ch| ch.degrade()));
|
||||
|
||||
let mut this = Self {
|
||||
lcd_cam: lcd.lcd_cam,
|
||||
tx_channel,
|
||||
_mode: PhantomData,
|
||||
};
|
||||
|
||||
this.apply_config(&config)?;
|
||||
pins.configure();
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// Applies configuration.
|
||||
pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
|
||||
let clocks = Clocks::get();
|
||||
// Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf
|
||||
// the LCD_PCLK divider must be at least 2. To make up for this the user
|
||||
// provided frequency is doubled to match.
|
||||
let (i, divider) = calculate_clkm(
|
||||
(frequency.to_Hz() * 2) as _,
|
||||
(config.frequency.to_Hz() * 2) as _,
|
||||
&[
|
||||
clocks.xtal_clock.to_Hz() as _,
|
||||
clocks.cpu_clock.to_Hz() as _,
|
||||
clocks.crypto_pwm_clock.to_Hz() as _,
|
||||
],
|
||||
);
|
||||
)
|
||||
.map_err(ConfigError::Clock)?;
|
||||
|
||||
lcd_cam.lcd_clock().write(|w| unsafe {
|
||||
self.lcd_cam.lcd_clock().write(|w| unsafe {
|
||||
// Force enable the clock for all configuration registers.
|
||||
w.clk_en().set_bit();
|
||||
w.lcd_clk_sel().bits((i + 1) as _);
|
||||
@ -140,20 +158,20 @@ where
|
||||
.bit(config.clock_mode.phase == Phase::ShiftHigh)
|
||||
});
|
||||
|
||||
lcd_cam
|
||||
self.lcd_cam
|
||||
.lcd_ctrl()
|
||||
.write(|w| w.lcd_rgb_mode_en().clear_bit());
|
||||
lcd_cam
|
||||
self.lcd_cam
|
||||
.lcd_rgb_yuv()
|
||||
.write(|w| w.lcd_conv_bypass().clear_bit());
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| {
|
||||
self.lcd_cam.lcd_user().modify(|_, w| {
|
||||
w.lcd_8bits_order().bit(false);
|
||||
w.lcd_bit_order().bit(false);
|
||||
w.lcd_byte_order().bit(false);
|
||||
w.lcd_2byte_en().bit(false)
|
||||
});
|
||||
lcd_cam.lcd_misc().write(|w| unsafe {
|
||||
self.lcd_cam.lcd_misc().write(|w| unsafe {
|
||||
// Set the threshold for Async Tx FIFO full event. (5 bits)
|
||||
w.lcd_afifo_threshold_num().bits(0);
|
||||
// Configure the setup cycles in LCD non-RGB mode. Setup cycles
|
||||
@ -184,10 +202,10 @@ where
|
||||
// The default value of LCD_CD
|
||||
w.lcd_cd_idle_edge().bit(config.cd_idle_edge)
|
||||
});
|
||||
lcd_cam
|
||||
self.lcd_cam
|
||||
.lcd_dly_mode()
|
||||
.write(|w| unsafe { w.lcd_cd_mode().bits(config.cd_mode as u8) });
|
||||
lcd_cam.lcd_data_dout_mode().write(|w| unsafe {
|
||||
self.lcd_cam.lcd_data_dout_mode().write(|w| unsafe {
|
||||
w.dout0_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout1_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout2_mode().bits(config.output_bit_mode as u8);
|
||||
@ -206,15 +224,11 @@ where
|
||||
w.dout15_mode().bits(config.output_bit_mode as u8)
|
||||
});
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| w.lcd_update().set_bit());
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_update().set_bit());
|
||||
|
||||
pins.configure();
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
tx_channel: channel.degrade(),
|
||||
_mode: PhantomData,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configures the byte order for data transmission in 16-bit mode.
|
||||
@ -286,7 +300,7 @@ where
|
||||
cmd: impl Into<Command<W>>,
|
||||
dummy: u8,
|
||||
mut data: BUF,
|
||||
) -> Result<I8080Transfer<'d, BUF, DM>, (DmaError, Self, BUF)> {
|
||||
) -> Result<I8080Transfer<'d, BUF, Dm>, (DmaError, Self, BUF)> {
|
||||
let cmd = cmd.into();
|
||||
|
||||
// Reset LCD control unit and Async Tx FIFO
|
||||
@ -382,7 +396,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM: Mode> core::fmt::Debug for I8080<'d, DM> {
|
||||
impl<Dm: DriverMode> core::fmt::Debug for I8080<'_, Dm> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("I8080").finish()
|
||||
}
|
||||
@ -390,12 +404,12 @@ impl<'d, DM: Mode> core::fmt::Debug for I8080<'d, DM> {
|
||||
|
||||
/// Represents an ongoing (or potentially finished) transfer using the I8080 LCD
|
||||
/// interface
|
||||
pub struct I8080Transfer<'d, BUF: DmaTxBuffer, DM: Mode> {
|
||||
i8080: ManuallyDrop<I8080<'d, DM>>,
|
||||
pub struct I8080Transfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> {
|
||||
i8080: ManuallyDrop<I8080<'d, Dm>>,
|
||||
buf_view: ManuallyDrop<BUF::View>,
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer, DM: Mode> I8080Transfer<'d, BUF, DM> {
|
||||
impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> I8080Transfer<'d, BUF, Dm> {
|
||||
/// Returns true when [Self::wait] will not block.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.i8080
|
||||
@ -407,7 +421,7 @@ impl<'d, BUF: DmaTxBuffer, DM: Mode> I8080Transfer<'d, BUF, DM> {
|
||||
}
|
||||
|
||||
/// Stops this transfer on the spot and returns the peripheral and buffer.
|
||||
pub fn cancel(mut self) -> (I8080<'d, DM>, BUF) {
|
||||
pub fn cancel(mut self) -> (I8080<'d, Dm>, BUF) {
|
||||
self.stop_peripherals();
|
||||
let (_, i8080, buf) = self.wait();
|
||||
(i8080, buf)
|
||||
@ -417,7 +431,7 @@ impl<'d, BUF: DmaTxBuffer, DM: Mode> I8080Transfer<'d, BUF, DM> {
|
||||
///
|
||||
/// Note: This also clears the transfer interrupt so it can be used in
|
||||
/// interrupt handlers to "handle" the interrupt.
|
||||
pub fn wait(mut self) -> (Result<(), DmaError>, I8080<'d, DM>, BUF) {
|
||||
pub fn wait(mut self) -> (Result<(), DmaError>, I8080<'d, Dm>, BUF) {
|
||||
while !self.is_done() {}
|
||||
|
||||
// Clear "done" interrupt.
|
||||
@ -456,7 +470,7 @@ impl<'d, BUF: DmaTxBuffer, DM: Mode> I8080Transfer<'d, BUF, DM> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer, DM: Mode> Deref for I8080Transfer<'d, BUF, DM> {
|
||||
impl<BUF: DmaTxBuffer, Dm: DriverMode> Deref for I8080Transfer<'_, BUF, Dm> {
|
||||
type Target = BUF::View;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -464,7 +478,7 @@ impl<'d, BUF: DmaTxBuffer, DM: Mode> Deref for I8080Transfer<'d, BUF, DM> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer, DM: Mode> DerefMut for I8080Transfer<'d, BUF, DM> {
|
||||
impl<BUF: DmaTxBuffer, Dm: DriverMode> DerefMut for I8080Transfer<'_, BUF, Dm> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buf_view
|
||||
}
|
||||
@ -509,7 +523,7 @@ impl<'d, BUF: DmaTxBuffer> I8080Transfer<'d, BUF, crate::Async> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer, DM: Mode> Drop for I8080Transfer<'d, BUF, DM> {
|
||||
impl<BUF: DmaTxBuffer, Dm: DriverMode> Drop for I8080Transfer<'_, BUF, Dm> {
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
@ -530,6 +544,9 @@ pub struct Config {
|
||||
/// Specifies the clock mode, including polarity and phase settings.
|
||||
pub clock_mode: ClockMode,
|
||||
|
||||
/// The frequency of the pixel clock.
|
||||
pub frequency: HertzU32,
|
||||
|
||||
/// Setup cycles expected, must be at least 1. (6 bits)
|
||||
pub setup_cycles: usize,
|
||||
|
||||
@ -555,6 +572,7 @@ impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
clock_mode: Default::default(),
|
||||
frequency: 20.MHz(),
|
||||
setup_cycles: 1,
|
||||
hold_cycles: 1,
|
||||
cd_idle_edge: false,
|
||||
@ -627,7 +645,7 @@ impl<'d> TxEightBits<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> TxPins for TxEightBits<'d> {
|
||||
impl TxPins for TxEightBits<'_> {
|
||||
fn configure(&mut self) {
|
||||
const SIGNALS: [OutputSignal; 8] = [
|
||||
OutputSignal::LCD_DATA_0,
|
||||
@ -688,7 +706,7 @@ impl<'d> TxSixteenBits<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> TxPins for TxSixteenBits<'d> {
|
||||
impl TxPins for TxSixteenBits<'_> {
|
||||
fn configure(&mut self) {
|
||||
const SIGNALS: [OutputSignal; 16] = [
|
||||
OutputSignal::LCD_DATA_0,
|
||||
|
||||
@ -10,18 +10,21 @@
|
||||
//! ## Implementation State
|
||||
//! - RGB is not supported yet
|
||||
|
||||
use crate::{peripheral::PeripheralRef, peripherals::LCD_CAM};
|
||||
use super::GenericPeripheralGuard;
|
||||
use crate::{peripheral::PeripheralRef, peripherals::LCD_CAM, system};
|
||||
|
||||
pub mod dpi;
|
||||
pub mod i8080;
|
||||
|
||||
/// Represents an LCD interface.
|
||||
pub struct Lcd<'d, DM: crate::Mode> {
|
||||
pub struct Lcd<'d, Dm: crate::DriverMode> {
|
||||
/// The `LCD_CAM` peripheral reference for managing the LCD functionality.
|
||||
pub(crate) lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
|
||||
/// A marker for the mode of operation (blocking or asynchronous).
|
||||
pub(crate) _mode: core::marker::PhantomData<DM>,
|
||||
pub(crate) _mode: core::marker::PhantomData<Dm>,
|
||||
|
||||
pub(super) _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
|
||||
@ -10,25 +10,23 @@ pub mod lcd;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::{
|
||||
interrupt::InterruptHandler,
|
||||
asynch::AtomicWaker,
|
||||
interrupt::{InterruptConfigurable, InterruptHandler},
|
||||
lcd_cam::{cam::Cam, lcd::Lcd},
|
||||
macros::handler,
|
||||
peripheral::Peripheral,
|
||||
peripherals::{Interrupt, LCD_CAM},
|
||||
system::{self, PeripheralClockControl},
|
||||
system::GenericPeripheralGuard,
|
||||
Async,
|
||||
Blocking,
|
||||
Cpu,
|
||||
InterruptConfigurable,
|
||||
};
|
||||
|
||||
/// Represents a combined LCD and Camera interface.
|
||||
pub struct LcdCam<'d, DM: crate::Mode> {
|
||||
pub struct LcdCam<'d, Dm: crate::DriverMode> {
|
||||
/// The LCD interface.
|
||||
pub lcd: Lcd<'d, DM>,
|
||||
pub lcd: Lcd<'d, Dm>,
|
||||
/// The Camera interface.
|
||||
pub cam: Cam<'d>,
|
||||
}
|
||||
@ -38,15 +36,19 @@ impl<'d> LcdCam<'d, Blocking> {
|
||||
pub fn new(lcd_cam: impl Peripheral<P = LCD_CAM> + 'd) -> Self {
|
||||
crate::into_ref!(lcd_cam);
|
||||
|
||||
PeripheralClockControl::reset(system::Peripheral::LcdCam);
|
||||
PeripheralClockControl::enable(system::Peripheral::LcdCam);
|
||||
let lcd_guard = GenericPeripheralGuard::new();
|
||||
let cam_guard = GenericPeripheralGuard::new();
|
||||
|
||||
Self {
|
||||
lcd: Lcd {
|
||||
lcd_cam: unsafe { lcd_cam.clone_unchecked() },
|
||||
_mode: PhantomData,
|
||||
_guard: lcd_guard,
|
||||
},
|
||||
cam: Cam {
|
||||
lcd_cam,
|
||||
_guard: cam_guard,
|
||||
},
|
||||
cam: Cam { lcd_cam },
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +59,7 @@ impl<'d> LcdCam<'d, Blocking> {
|
||||
lcd: Lcd {
|
||||
lcd_cam: self.lcd.lcd_cam,
|
||||
_mode: PhantomData,
|
||||
_guard: self.lcd._guard,
|
||||
},
|
||||
cam: self.cam,
|
||||
}
|
||||
@ -87,6 +90,7 @@ impl<'d> LcdCam<'d, Async> {
|
||||
lcd: Lcd {
|
||||
lcd_cam: self.lcd.lcd_cam,
|
||||
_mode: PhantomData,
|
||||
_guard: self.lcd._guard,
|
||||
},
|
||||
cam: self.cam,
|
||||
}
|
||||
@ -169,10 +173,18 @@ pub(crate) struct ClockDivider {
|
||||
pub div_a: usize,
|
||||
}
|
||||
|
||||
/// Clock configuration errors.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ClockError {
|
||||
/// Desired frequency was too low for the dividers to divide to
|
||||
FrequencyTooLow,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_clkm(
|
||||
desired_frequency: usize,
|
||||
source_frequencies: &[usize],
|
||||
) -> (usize, ClockDivider) {
|
||||
) -> Result<(usize, ClockDivider), ClockError> {
|
||||
let mut result_freq = 0;
|
||||
let mut result = None;
|
||||
|
||||
@ -187,7 +199,7 @@ pub(crate) fn calculate_clkm(
|
||||
}
|
||||
}
|
||||
|
||||
result.expect("Desired frequency was too low for the dividers to divide to")
|
||||
result.ok_or(ClockError::FrequencyTooLow)
|
||||
}
|
||||
|
||||
fn calculate_output_frequency(source_frequency: usize, divider: &ClockDivider) -> usize {
|
||||
@ -253,15 +265,15 @@ fn calculate_closest_divider(
|
||||
}
|
||||
} else {
|
||||
let target = div_fraction;
|
||||
let closest = farey_sequence(63)
|
||||
.find(|curr| {
|
||||
let closest = farey_sequence(63).find(|curr| {
|
||||
// https://en.wikipedia.org/wiki/Fraction#Adding_unlike_quantities
|
||||
|
||||
let new_curr_num = curr.numerator * target.denominator;
|
||||
let new_target_num = target.numerator * curr.denominator;
|
||||
new_curr_num >= new_target_num
|
||||
})
|
||||
.expect("The fraction must be between 0 and 1");
|
||||
});
|
||||
|
||||
let closest = unwrap!(closest, "The fraction must be between 0 and 1");
|
||||
|
||||
ClockDivider {
|
||||
div_num,
|
||||
|
||||
@ -20,15 +20,17 @@
|
||||
//! ### Low Speed Channel
|
||||
//!
|
||||
//! The following example will configure the Low Speed Channel0 to 24kHz output
|
||||
//! with 10% duty using the ABPClock.
|
||||
//! with 10% duty using the ABPClock and turn on LED with the option to change
|
||||
//! LED intensity depending on `duty` value. Possible values (`u32`) are in
|
||||
//! range 0..100.
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::ledc::Ledc;
|
||||
//! # use esp_hal::ledc::LSGlobalClkSource;
|
||||
//! # use esp_hal::ledc::timer;
|
||||
//! # use esp_hal::ledc::timer::{self, TimerIFace};
|
||||
//! # use esp_hal::ledc::LowSpeed;
|
||||
//! # use esp_hal::ledc::channel;
|
||||
//! # use esp_hal::ledc::channel::{self, ChannelIFace};
|
||||
//! # let led = peripherals.GPIO0;
|
||||
//!
|
||||
//! let mut ledc = Ledc::new(peripherals.LEDC);
|
||||
@ -51,6 +53,15 @@
|
||||
//! pin_config: channel::config::PinConfig::PushPull,
|
||||
//! })
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! loop {
|
||||
//! // Set up a breathing LED: fade from off to on over a second, then
|
||||
//! // from on back off over the next second. Then loop.
|
||||
//! channel0.start_duty_fade(0, 100, 1000).unwrap();
|
||||
//! while channel0.is_duty_fade_running() {}
|
||||
//! channel0.start_duty_fade(100, 0, 1000).unwrap();
|
||||
//! while channel0.is_duty_fade_running() {}
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
@ -111,8 +122,9 @@ impl<'d> Ledc<'d> {
|
||||
pub fn new(_instance: impl Peripheral<P = crate::peripherals::LEDC> + 'd) -> Self {
|
||||
crate::into_ref!(_instance);
|
||||
|
||||
if PeripheralClockControl::enable(PeripheralEnable::Ledc) {
|
||||
PeripheralClockControl::reset(PeripheralEnable::Ledc);
|
||||
PeripheralClockControl::enable(PeripheralEnable::Ledc);
|
||||
}
|
||||
|
||||
let ledc = unsafe { &*crate::peripherals::LEDC::ptr() };
|
||||
Ledc { _instance, ledc }
|
||||
|
||||
@ -68,6 +68,7 @@ pub mod config {
|
||||
/// Number of bits reserved for duty cycle adjustment
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names)] // FIXME: resolve before stabilizing this driver
|
||||
pub enum Duty {
|
||||
/// 1-bit resolution for duty cycle adjustment.
|
||||
Duty1Bit = 1,
|
||||
@ -369,7 +370,7 @@ impl TimerHW<LowSpeed> for Timer<'_, LowSpeed> {
|
||||
|
||||
#[cfg(esp32)]
|
||||
/// Timer HW implementation for HighSpeed timers
|
||||
impl<'a> TimerHW<HighSpeed> for Timer<'a, HighSpeed> {
|
||||
impl TimerHW<HighSpeed> for Timer<'_, HighSpeed> {
|
||||
/// Get the current source timer frequency from the HW
|
||||
fn freq_hw(&self) -> Option<HertzU32> {
|
||||
self.clock_source.map(|source| match source {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#![cfg_attr(
|
||||
docsrs,
|
||||
all(docsrs, not(not_really_docsrs)),
|
||||
doc = "<div style='padding:30px;background:#810;color:#fff;text-align:center;'><p>You might want to <a href='https://docs.esp-rs.org/esp-hal/'>browse the <code>esp-hal</code> documentation on the esp-rs website</a> instead.</p><p>The documentation here on <a href='https://docs.rs'>docs.rs</a> is built for a single chip only (ESP32-C6, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs change depending on the chip.</p></div>\n\n<br/>\n\n"
|
||||
)]
|
||||
//! # Bare-metal (`no_std`) HAL for all Espressif ESP32 devices.
|
||||
@ -68,19 +68,16 @@
|
||||
//! # loop {}
|
||||
//! # }
|
||||
//! use esp_hal::{
|
||||
//! clock::CpuClock,
|
||||
//! delay::Delay,
|
||||
//! entry,
|
||||
//! gpio::{Io, Level, Output},
|
||||
//! prelude::*,
|
||||
//! };
|
||||
//!
|
||||
//! #[entry]
|
||||
//! fn main() -> ! {
|
||||
//! let peripherals = esp_hal::init({
|
||||
//! let mut config = esp_hal::Config::default();
|
||||
//! // Configure the CPU to run at the maximum frequency.
|
||||
//! config.cpu_clock = CpuClock::max();
|
||||
//! config
|
||||
//! });
|
||||
//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
//! let peripherals = esp_hal::init(config);
|
||||
//!
|
||||
//! // Set GPIO0 as an output, and set its state high initially.
|
||||
//! let mut led = Output::new(peripherals.GPIO0, Level::High);
|
||||
@ -127,8 +124,8 @@
|
||||
//!
|
||||
//! [documentation]: https://docs.esp-rs.org/esp-hal
|
||||
//! [examples]: https://github.com/esp-rs/esp-hal/tree/main/examples
|
||||
//! [embedded-hal]: https://github.com/rust-embedded/embedded-hal/tree/master/embedded-hal
|
||||
//! [embedded-hal-async]: https://github.com/rust-embedded/embedded-hal/tree/master/embedded-hal-async
|
||||
//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/
|
||||
//! [embedded-hal-async]: https://docs.rs/embedded-hal-async/latest/embedded_hal_async/
|
||||
//! [xtask]: https://github.com/matklad/cargo-xtask
|
||||
//! [esp-generate]: https://github.com/esp-rs/esp-generate
|
||||
//! [book]: https://docs.esp-rs.org/book/
|
||||
@ -140,11 +137,14 @@
|
||||
#![allow(asm_sub_register, async_fn_in_trait, stable_features)]
|
||||
#![cfg_attr(xtensa, feature(asm_experimental_arch))]
|
||||
#![deny(missing_docs, rust_2018_idioms)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![no_std]
|
||||
|
||||
// MUST be the first module
|
||||
mod fmt;
|
||||
|
||||
pub mod asynch;
|
||||
|
||||
#[cfg(riscv)]
|
||||
pub use esp_riscv_rt::{self, entry, riscv};
|
||||
#[cfg(xtensa)]
|
||||
@ -152,6 +152,7 @@ pub use xtensa_lx;
|
||||
#[cfg(xtensa)]
|
||||
pub use xtensa_lx_rt::{self, entry};
|
||||
|
||||
// TODO what should we reexport stably?
|
||||
#[cfg(any(esp32, esp32s3))]
|
||||
pub use self::soc::cpu_control;
|
||||
#[cfg(efuse)]
|
||||
@ -164,12 +165,6 @@ pub use self::soc::psram;
|
||||
#[cfg(ulp_riscv_core)]
|
||||
pub use self::soc::ulp_core;
|
||||
|
||||
#[cfg(aes)]
|
||||
pub mod aes;
|
||||
#[cfg(any(adc, dac))]
|
||||
pub mod analog;
|
||||
#[cfg(assist_debug)]
|
||||
pub mod assist_debug;
|
||||
#[cfg(any(dport, hp_sys, pcr, system))]
|
||||
pub mod clock;
|
||||
|
||||
@ -177,75 +172,108 @@ pub mod config;
|
||||
|
||||
#[cfg(any(xtensa, all(riscv, systimer)))]
|
||||
pub mod delay;
|
||||
#[cfg(any(gdma, pdma))]
|
||||
pub mod dma;
|
||||
#[cfg(ecc)]
|
||||
pub mod ecc;
|
||||
#[cfg(soc_etm)]
|
||||
pub mod etm;
|
||||
#[cfg(gpio)]
|
||||
pub mod gpio;
|
||||
#[cfg(hmac)]
|
||||
pub mod hmac;
|
||||
#[cfg(any(i2c0, i2c1))]
|
||||
pub mod i2c;
|
||||
#[cfg(any(i2s0, i2s1))]
|
||||
pub mod i2s;
|
||||
#[cfg(any(dport, interrupt_core0, interrupt_core1))]
|
||||
pub mod interrupt;
|
||||
#[cfg(lcd_cam)]
|
||||
pub mod lcd_cam;
|
||||
#[cfg(ledc)]
|
||||
pub mod ledc;
|
||||
#[cfg(any(mcpwm0, mcpwm1))]
|
||||
pub mod mcpwm;
|
||||
#[cfg(usb0)]
|
||||
pub mod otg_fs;
|
||||
#[cfg(parl_io)]
|
||||
pub mod parl_io;
|
||||
#[cfg(pcnt)]
|
||||
pub mod pcnt;
|
||||
pub mod peripheral;
|
||||
pub mod prelude;
|
||||
#[cfg(any(hmac, sha))]
|
||||
mod reg_access;
|
||||
#[cfg(any(lp_clkrst, rtc_cntl))]
|
||||
pub mod reset;
|
||||
#[cfg(rmt)]
|
||||
pub mod rmt;
|
||||
#[cfg(rng)]
|
||||
pub mod rng;
|
||||
pub mod rom;
|
||||
#[cfg(rsa)]
|
||||
pub mod rsa;
|
||||
#[cfg(any(lp_clkrst, rtc_cntl))]
|
||||
pub mod rtc_cntl;
|
||||
#[cfg(sha)]
|
||||
pub mod sha;
|
||||
#[cfg(any(spi0, spi1, spi2, spi3))]
|
||||
pub mod spi;
|
||||
#[cfg(any(dport, hp_sys, pcr, system))]
|
||||
pub mod system;
|
||||
pub mod time;
|
||||
#[cfg(any(systimer, timg0, timg1))]
|
||||
pub mod timer;
|
||||
#[cfg(touch)]
|
||||
pub mod touch;
|
||||
#[cfg(trace0)]
|
||||
pub mod trace;
|
||||
#[cfg(any(twai0, twai1))]
|
||||
pub mod twai;
|
||||
#[cfg(any(uart0, uart1, uart2))]
|
||||
pub mod uart;
|
||||
#[cfg(usb_device)]
|
||||
pub mod usb_serial_jtag;
|
||||
|
||||
pub mod debugger;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod sync;
|
||||
|
||||
pub mod macros;
|
||||
pub mod rom;
|
||||
|
||||
pub mod debugger;
|
||||
#[doc(hidden)]
|
||||
pub mod sync;
|
||||
pub mod time;
|
||||
|
||||
// can't use instability on inline module definitions, see https://github.com/rust-lang/rust/issues/54727
|
||||
#[doc(hidden)]
|
||||
macro_rules! unstable_module {
|
||||
($(
|
||||
$(#[$meta:meta])*
|
||||
pub mod $module:ident;
|
||||
)*) => {
|
||||
$(
|
||||
$(#[$meta])*
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
pub mod $module;
|
||||
|
||||
$(#[$meta])*
|
||||
#[cfg(not(feature = "unstable"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
#[allow(unused)]
|
||||
pub(crate) mod $module;
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use unstable_module;
|
||||
|
||||
unstable_module! {
|
||||
#[cfg(aes)]
|
||||
pub mod aes;
|
||||
#[cfg(any(adc, dac))]
|
||||
pub mod analog;
|
||||
#[cfg(assist_debug)]
|
||||
pub mod assist_debug;
|
||||
#[cfg(any(gdma, pdma))]
|
||||
pub mod dma;
|
||||
#[cfg(ecc)]
|
||||
pub mod ecc;
|
||||
#[cfg(soc_etm)]
|
||||
pub mod etm;
|
||||
#[cfg(hmac)]
|
||||
pub mod hmac;
|
||||
#[cfg(any(i2s0, i2s1))]
|
||||
pub mod i2s;
|
||||
#[cfg(lcd_cam)]
|
||||
pub mod lcd_cam;
|
||||
#[cfg(ledc)]
|
||||
pub mod ledc;
|
||||
#[cfg(any(mcpwm0, mcpwm1))]
|
||||
pub mod mcpwm;
|
||||
#[cfg(usb0)]
|
||||
pub mod otg_fs;
|
||||
#[cfg(parl_io)]
|
||||
pub mod parl_io;
|
||||
#[cfg(pcnt)]
|
||||
pub mod pcnt;
|
||||
#[cfg(any(lp_clkrst, rtc_cntl))]
|
||||
pub mod reset;
|
||||
#[cfg(rmt)]
|
||||
pub mod rmt;
|
||||
#[cfg(rng)]
|
||||
pub mod rng;
|
||||
#[cfg(rsa)]
|
||||
pub mod rsa;
|
||||
#[cfg(any(lp_clkrst, rtc_cntl))]
|
||||
pub mod rtc_cntl;
|
||||
#[cfg(sha)]
|
||||
pub mod sha;
|
||||
#[cfg(any(dport, hp_sys, pcr, system))]
|
||||
pub mod system;
|
||||
#[cfg(any(systimer, timg0, timg1))]
|
||||
pub mod timer;
|
||||
#[cfg(touch)]
|
||||
pub mod touch;
|
||||
#[cfg(trace0)]
|
||||
pub mod trace;
|
||||
#[cfg(tsens)]
|
||||
pub mod tsens;
|
||||
#[cfg(any(twai0, twai1))]
|
||||
pub mod twai;
|
||||
#[cfg(usb_device)]
|
||||
pub mod usb_serial_jtag;
|
||||
}
|
||||
|
||||
/// State of the CPU saved when entering exception or interrupt
|
||||
pub mod trapframe {
|
||||
@ -259,20 +287,17 @@ pub mod trapframe {
|
||||
// be directly exposed.
|
||||
mod soc;
|
||||
|
||||
#[cfg(xtensa)]
|
||||
#[no_mangle]
|
||||
extern "C" fn EspDefaultHandler(_level: u32, _interrupt: peripherals::Interrupt) {
|
||||
panic!("Unhandled level {} interrupt: {:?}", _level, _interrupt);
|
||||
}
|
||||
|
||||
#[cfg(riscv)]
|
||||
#[no_mangle]
|
||||
extern "C" fn EspDefaultHandler(_interrupt: peripherals::Interrupt) {
|
||||
panic!("Unhandled interrupt: {:?}", _interrupt);
|
||||
}
|
||||
#[cfg(is_debug_build)]
|
||||
esp_build::warning! {"
|
||||
WARNING: use --release
|
||||
We *strongly* recommend using release profile when building esp-hal.
|
||||
The dev profile can potentially be one or more orders of magnitude
|
||||
slower than release, and may cause issues with timing-senstive
|
||||
peripherals and/or devices.
|
||||
"}
|
||||
|
||||
/// A marker trait for initializing drivers in a specific mode.
|
||||
pub trait Mode: crate::private::Sealed {}
|
||||
pub trait DriverMode: crate::private::Sealed {}
|
||||
|
||||
/// Driver initialized in blocking mode.
|
||||
#[derive(Debug)]
|
||||
@ -282,8 +307,8 @@ pub struct Blocking;
|
||||
#[derive(Debug)]
|
||||
pub struct Async;
|
||||
|
||||
impl crate::Mode for Blocking {}
|
||||
impl crate::Mode for Async {}
|
||||
impl crate::DriverMode for Blocking {}
|
||||
impl crate::DriverMode for Async {}
|
||||
impl crate::private::Sealed for Blocking {}
|
||||
impl crate::private::Sealed for Async {}
|
||||
|
||||
@ -313,6 +338,7 @@ pub(crate) mod private {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
#[doc(hidden)]
|
||||
pub use private::Internal;
|
||||
|
||||
@ -440,26 +466,6 @@ fn raw_core() -> usize {
|
||||
(xtensa_lx::get_processor_id() & 0x2000) as usize
|
||||
}
|
||||
|
||||
/// Default (unhandled) interrupt handler
|
||||
pub const DEFAULT_INTERRUPT_HANDLER: interrupt::InterruptHandler = interrupt::InterruptHandler::new(
|
||||
unsafe { core::mem::transmute::<*const (), extern "C" fn()>(EspDefaultHandler as *const ()) },
|
||||
crate::interrupt::Priority::min(),
|
||||
);
|
||||
|
||||
/// Trait implemented by drivers which allow the user to set an
|
||||
/// [interrupt::InterruptHandler]
|
||||
pub trait InterruptConfigurable: private::Sealed {
|
||||
/// Set the interrupt handler
|
||||
///
|
||||
/// Note that this will replace any previously registered interrupt handler.
|
||||
/// Some peripherals offer a shared interrupt handler for multiple purposes.
|
||||
/// It's the users duty to honor this.
|
||||
///
|
||||
/// You can restore the default/unhandled interrupt handler by using
|
||||
/// [DEFAULT_INTERRUPT_HANDLER]
|
||||
fn set_interrupt_handler(&mut self, handler: interrupt::InterruptHandler);
|
||||
}
|
||||
|
||||
#[cfg(riscv)]
|
||||
#[export_name = "hal_main"]
|
||||
fn hal_main(a0: usize, a1: usize, a2: usize) -> ! {
|
||||
@ -502,12 +508,17 @@ use crate::{
|
||||
///
|
||||
/// For usage examples, see the [config module documentation](crate::config).
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
#[derive(Default, procmacros::BuilderLite)]
|
||||
pub struct Config {
|
||||
/// The CPU clock configuration.
|
||||
pub cpu_clock: CpuClock,
|
||||
|
||||
/// Enable watchdog timer(s).
|
||||
pub watchdog: WatchdogConfig,
|
||||
|
||||
/// PSRAM configuration.
|
||||
#[cfg(any(feature = "quad-psram", feature = "octal-psram"))]
|
||||
pub psram: psram::PsramConfig,
|
||||
}
|
||||
|
||||
/// Initialize the system.
|
||||
@ -515,6 +526,8 @@ pub struct Config {
|
||||
/// This function sets up the CPU clock and watchdog, then, returns the
|
||||
/// peripherals and clocks.
|
||||
pub fn init(config: Config) -> Peripherals {
|
||||
system::disable_peripherals();
|
||||
|
||||
let mut peripherals = Peripherals::take();
|
||||
|
||||
// RTC domain must be enabled before we try to disable
|
||||
@ -568,5 +581,8 @@ pub fn init(config: Config) -> Peripherals {
|
||||
|
||||
crate::gpio::bind_default_interrupt_handler();
|
||||
|
||||
#[cfg(any(feature = "quad-psram", feature = "octal-psram"))]
|
||||
crate::psram::init_psram(config.psram);
|
||||
|
||||
peripherals
|
||||
}
|
||||
|
||||
@ -13,12 +13,14 @@ macro_rules! before_snippet {
|
||||
() => {
|
||||
r#"
|
||||
# #![no_std]
|
||||
# use esp_hal::prelude::*;
|
||||
# use procmacros::handler;
|
||||
# use esp_hal::interrupt;
|
||||
# use esp_hal::{interrupt::{self, InterruptConfigurable}, time::{RateExtU32 as _, ExtU64 as _}};
|
||||
# macro_rules! println {
|
||||
# ($($tt:tt)*) => { };
|
||||
# }
|
||||
# macro_rules! print {
|
||||
# ($($tt:tt)*) => { };
|
||||
# }
|
||||
# #[panic_handler]
|
||||
# fn panic(_ : &core::panic::PanicInfo) -> ! {
|
||||
# loop {}
|
||||
@ -87,6 +89,8 @@ macro_rules! any_peripheral {
|
||||
}) => {
|
||||
paste::paste! {
|
||||
$(#[$meta])*
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
$vis struct $name([< $name Inner >]);
|
||||
impl $crate::private::Sealed for $name {}
|
||||
|
||||
@ -103,6 +107,8 @@ macro_rules! any_peripheral {
|
||||
}
|
||||
}
|
||||
|
||||
$(#[$meta])*
|
||||
#[derive(Debug)]
|
||||
enum [< $name Inner >] {
|
||||
$(
|
||||
$(#[cfg($variant_meta)])*
|
||||
@ -110,6 +116,18 @@ macro_rules! any_peripheral {
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for [< $name Inner >] {
|
||||
fn format(&self, fmt: defmt::Formatter<'_>) {
|
||||
match self {
|
||||
$(
|
||||
$(#[cfg($variant_meta)])*
|
||||
[< $name Inner >]::$variant(inner) => inner.format(fmt),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
$(#[cfg($variant_meta)])*
|
||||
impl From<$inner> for $name {
|
||||
@ -134,3 +152,13 @@ macro_rules! if_set {
|
||||
$set
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to ignore tokens.
|
||||
///
|
||||
/// This is useful when we need existence of a metavariable (to expand a
|
||||
/// repetition), but we don't need to use it.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! ignore {
|
||||
($($item:tt)*) => {};
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ use crate::{
|
||||
clock::Clocks,
|
||||
gpio::OutputSignal,
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
system::{self, PeripheralGuard},
|
||||
};
|
||||
|
||||
/// MCPWM operators
|
||||
@ -115,11 +115,12 @@ pub struct McPwm<'d, PWM> {
|
||||
/// Timer2
|
||||
pub timer2: Timer<2, PWM>,
|
||||
/// Operator0
|
||||
pub operator0: Operator<0, PWM>,
|
||||
pub operator0: Operator<'d, 0, PWM>,
|
||||
/// Operator1
|
||||
pub operator1: Operator<1, PWM>,
|
||||
pub operator1: Operator<'d, 1, PWM>,
|
||||
/// Operator2
|
||||
pub operator2: Operator<2, PWM>,
|
||||
pub operator2: Operator<'d, 2, PWM>,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<'d, PWM: PwmPeripheral> McPwm<'d, PWM> {
|
||||
@ -131,8 +132,7 @@ impl<'d, PWM: PwmPeripheral> McPwm<'d, PWM> {
|
||||
) -> Self {
|
||||
crate::into_ref!(peripheral);
|
||||
|
||||
PWM::reset();
|
||||
PWM::enable();
|
||||
let guard = PeripheralGuard::new(PWM::peripheral());
|
||||
|
||||
#[cfg(not(esp32c6))]
|
||||
{
|
||||
@ -181,6 +181,7 @@ impl<'d, PWM: PwmPeripheral> McPwm<'d, PWM> {
|
||||
operator0: Operator::new(),
|
||||
operator1: Operator::new(),
|
||||
operator2: Operator::new(),
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -313,26 +314,16 @@ pub struct FrequencyError;
|
||||
|
||||
/// A MCPWM peripheral
|
||||
pub trait PwmPeripheral: Deref<Target = RegisterBlock> + crate::private::Sealed {
|
||||
/// Enable peripheral
|
||||
fn enable();
|
||||
/// Reset peripheral
|
||||
fn reset();
|
||||
/// Get a pointer to the peripheral RegisterBlock
|
||||
fn block() -> *const RegisterBlock;
|
||||
/// Get operator GPIO mux output signal
|
||||
fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal;
|
||||
/// Peripheral
|
||||
fn peripheral() -> system::Peripheral;
|
||||
}
|
||||
|
||||
#[cfg(mcpwm0)]
|
||||
impl PwmPeripheral for crate::peripherals::MCPWM0 {
|
||||
fn enable() {
|
||||
PeripheralClockControl::enable(PeripheralEnable::Mcpwm0)
|
||||
}
|
||||
|
||||
fn reset() {
|
||||
PeripheralClockControl::reset(PeripheralEnable::Mcpwm0)
|
||||
}
|
||||
|
||||
fn block() -> *const RegisterBlock {
|
||||
Self::PTR
|
||||
}
|
||||
@ -348,18 +339,14 @@ impl PwmPeripheral for crate::peripherals::MCPWM0 {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn peripheral() -> system::Peripheral {
|
||||
system::Peripheral::Mcpwm0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(mcpwm1)]
|
||||
impl PwmPeripheral for crate::peripherals::MCPWM1 {
|
||||
fn enable() {
|
||||
PeripheralClockControl::enable(PeripheralEnable::Mcpwm1)
|
||||
}
|
||||
|
||||
fn reset() {
|
||||
PeripheralClockControl::reset(PeripheralEnable::Mcpwm1)
|
||||
}
|
||||
|
||||
fn block() -> *const RegisterBlock {
|
||||
Self::PTR
|
||||
}
|
||||
@ -375,4 +362,8 @@ impl PwmPeripheral for crate::peripherals::MCPWM1 {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn peripheral() -> system::Peripheral {
|
||||
system::Peripheral::Mcpwm1
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::PeripheralGuard;
|
||||
use crate::{
|
||||
gpio::interconnect::{OutputConnection, PeripheralOutput},
|
||||
mcpwm::{timer::Timer, PwmPeripheral},
|
||||
@ -20,6 +21,7 @@ use crate::{
|
||||
|
||||
/// Input/Output Stream descriptor for each channel
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(clippy::upper_case_acronyms, reason = "peripheral is unstable")]
|
||||
pub enum PWMStream {
|
||||
/// PWM Stream A
|
||||
PWMA,
|
||||
@ -167,12 +169,15 @@ impl DeadTimeCfg {
|
||||
/// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet
|
||||
/// implemented)
|
||||
/// * Handles response under fault conditions. (Not yet implemented)
|
||||
pub struct Operator<const OP: u8, PWM> {
|
||||
phantom: PhantomData<PWM>,
|
||||
pub struct Operator<'d, const OP: u8, PWM> {
|
||||
phantom: PhantomData<&'d PWM>,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
|
||||
impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> {
|
||||
pub(super) fn new() -> Self {
|
||||
let guard = PeripheralGuard::new(PWM::peripheral());
|
||||
|
||||
// Side note:
|
||||
// It would have been nice to deselect any timer reference on peripheral
|
||||
// initialization.
|
||||
@ -181,6 +186,7 @@ impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
|
||||
// written.
|
||||
Operator {
|
||||
phantom: PhantomData,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +210,7 @@ impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
|
||||
}
|
||||
|
||||
/// Use the A output with the given pin and configuration
|
||||
pub fn with_pin_a<'d>(
|
||||
pub fn with_pin_a(
|
||||
self,
|
||||
pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
config: PwmPinConfig<true>,
|
||||
@ -213,7 +219,7 @@ impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
|
||||
}
|
||||
|
||||
/// Use the B output with the given pin and configuration
|
||||
pub fn with_pin_b<'d>(
|
||||
pub fn with_pin_b(
|
||||
self,
|
||||
pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
config: PwmPinConfig<false>,
|
||||
@ -222,7 +228,7 @@ impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
|
||||
}
|
||||
|
||||
/// Use both the A and the B output with the given pins and configurations
|
||||
pub fn with_pins<'d>(
|
||||
pub fn with_pins(
|
||||
self,
|
||||
pin_a: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
config_a: PwmPinConfig<true>,
|
||||
@ -236,7 +242,7 @@ impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
|
||||
///
|
||||
/// This is useful for complementary or mirrored signals with or without
|
||||
/// configured deadtime
|
||||
pub fn with_linked_pins<'d>(
|
||||
pub fn with_linked_pins(
|
||||
self,
|
||||
pin_a: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
config_a: PwmPinConfig<true>,
|
||||
@ -283,6 +289,7 @@ impl<const IS_A: bool> PwmPinConfig<IS_A> {
|
||||
pub struct PwmPin<'d, PWM, const OP: u8, const IS_A: bool> {
|
||||
pin: PeripheralRef<'d, OutputConnection>,
|
||||
phantom: PhantomData<PWM>,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, IS_A> {
|
||||
@ -291,9 +298,13 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP,
|
||||
config: PwmPinConfig<IS_A>,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
|
||||
let guard = PeripheralGuard::new(PWM::peripheral());
|
||||
|
||||
let mut pin = PwmPin {
|
||||
pin,
|
||||
phantom: PhantomData,
|
||||
_guard: guard,
|
||||
};
|
||||
pin.set_actions(config.actions);
|
||||
pin.set_update_method(config.update_method);
|
||||
@ -411,39 +422,6 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP,
|
||||
}
|
||||
}
|
||||
|
||||
impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal_02::PwmPin
|
||||
for PwmPin<'_, PWM, OP, IS_A>
|
||||
{
|
||||
type Duty = u16;
|
||||
|
||||
/// This only set the timestamp to 0, if you want to disable the PwmPin,
|
||||
/// it must be done on the timer itself.
|
||||
fn disable(&mut self) {
|
||||
self.set_timestamp(0);
|
||||
}
|
||||
|
||||
/// This only set the timestamp to the maximum, if you want to disable the
|
||||
/// PwmPin, it must be done on the timer itself.
|
||||
fn enable(&mut self) {
|
||||
self.set_timestamp(u16::MAX);
|
||||
}
|
||||
|
||||
/// Get the duty of the pin
|
||||
fn get_duty(&self) -> Self::Duty {
|
||||
self.timestamp()
|
||||
}
|
||||
|
||||
/// Get the max duty of the pin
|
||||
fn get_max_duty(&self) -> Self::Duty {
|
||||
self.period()
|
||||
}
|
||||
|
||||
/// Set the duty of the pin
|
||||
fn set_duty(&mut self, duty: Self::Duty) {
|
||||
self.set_timestamp(duty);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement no error type for the PwmPin because the method are infallible
|
||||
impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::ErrorType
|
||||
for PwmPin<'_, PWM, OP, IS_A>
|
||||
@ -476,7 +454,6 @@ impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::SetD
|
||||
///
|
||||
/// ```rust, no_run
|
||||
#[doc = crate::before_snippet!()]
|
||||
/// # use esp_hal::{mcpwm, prelude::*};
|
||||
/// # use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
|
||||
/// # use esp_hal::mcpwm::operator::{DeadTimeCfg, PwmPinConfig, PWMStream};
|
||||
/// // active high complementary using PWMA input
|
||||
|
||||
@ -8,6 +8,7 @@ use core::marker::PhantomData;
|
||||
|
||||
use fugit::HertzU32;
|
||||
|
||||
use super::PeripheralGuard;
|
||||
use crate::mcpwm::{FrequencyError, PeripheralClockConfig, PwmPeripheral};
|
||||
|
||||
/// A MCPWM timer
|
||||
@ -17,12 +18,15 @@ use crate::mcpwm::{FrequencyError, PeripheralClockConfig, PwmPeripheral};
|
||||
/// [`Operator`](super::operator::Operator) of that peripheral
|
||||
pub struct Timer<const TIM: u8, PWM> {
|
||||
pub(super) phantom: PhantomData<PWM>,
|
||||
_guard: PeripheralGuard,
|
||||
}
|
||||
|
||||
impl<const TIM: u8, PWM: PwmPeripheral> Timer<TIM, PWM> {
|
||||
pub(super) fn new() -> Self {
|
||||
let guard = PeripheralGuard::new(PWM::peripheral());
|
||||
Timer {
|
||||
phantom: PhantomData,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ use crate::{
|
||||
gpio::InputSignal,
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals,
|
||||
system::{Peripheral as PeripheralEnable, PeripheralClockControl},
|
||||
system::{GenericPeripheralGuard, Peripheral as PeripheralEnable},
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -57,30 +57,27 @@ pub trait UsbDm: crate::private::Sealed {}
|
||||
/// USB peripheral.
|
||||
pub struct Usb<'d> {
|
||||
_usb0: PeripheralRef<'d, peripherals::USB0>,
|
||||
_guard: GenericPeripheralGuard<{ PeripheralEnable::Usb as u8 }>,
|
||||
}
|
||||
|
||||
impl<'d> Usb<'d> {
|
||||
/// Creates a new `Usb` instance.
|
||||
pub fn new<P, M>(
|
||||
pub fn new(
|
||||
usb0: impl Peripheral<P = peripherals::USB0> + 'd,
|
||||
_usb_dp: impl Peripheral<P = P> + 'd,
|
||||
_usb_dm: impl Peripheral<P = M> + 'd,
|
||||
) -> Self
|
||||
where
|
||||
P: UsbDp + Send + Sync,
|
||||
M: UsbDm + Send + Sync,
|
||||
{
|
||||
PeripheralClockControl::reset(PeripheralEnable::Usb);
|
||||
PeripheralClockControl::enable(PeripheralEnable::Usb);
|
||||
_usb_dp: impl Peripheral<P = impl UsbDp> + 'd,
|
||||
_usb_dm: impl Peripheral<P = impl UsbDm> + 'd,
|
||||
) -> Self {
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
Self {
|
||||
_usb0: usb0.into_ref(),
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
fn _enable() {
|
||||
unsafe {
|
||||
let usb_wrap = &*peripherals::USB_WRAP::PTR;
|
||||
let usb_wrap = peripherals::USB_WRAP::steal();
|
||||
usb_wrap.otg_conf().modify(|_, w| {
|
||||
w.usb_pad_enable().set_bit();
|
||||
w.phy_sel().clear_bit();
|
||||
@ -88,12 +85,15 @@ impl<'d> Usb<'d> {
|
||||
w.ahb_clk_force_on().set_bit();
|
||||
w.phy_clk_force_on().set_bit()
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(esp32s3)]
|
||||
{
|
||||
let rtc = &*peripherals::LPWR::PTR;
|
||||
rtc.usb_conf()
|
||||
.modify(|_, w| w.sw_hw_usb_phy_sel().set_bit().sw_usb_phy_sel().set_bit());
|
||||
unsafe {
|
||||
let rtc = peripherals::LPWR::steal();
|
||||
rtc.usb_conf().modify(|_, w| {
|
||||
w.sw_hw_usb_phy_sel().set_bit();
|
||||
w.sw_usb_phy_sel().set_bit()
|
||||
});
|
||||
}
|
||||
|
||||
use crate::gpio::Level;
|
||||
@ -103,16 +103,15 @@ impl<'d> Usb<'d> {
|
||||
InputSignal::USB_OTG_VBUSVALID.connect_to(Level::High); // receiving a valid Vbus from device
|
||||
InputSignal::USB_OTG_AVALID.connect_to(Level::Low);
|
||||
}
|
||||
}
|
||||
|
||||
fn _disable() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'d> Sync for Usb<'d> {}
|
||||
unsafe impl Sync for Usb<'_> {}
|
||||
|
||||
unsafe impl<'d> UsbPeripheral for Usb<'d> {
|
||||
unsafe impl UsbPeripheral for Usb<'_> {
|
||||
const REGISTERS: *const () = peripherals::USB0::ptr() as *const ();
|
||||
|
||||
const HIGH_SPEED: bool = false;
|
||||
@ -165,6 +164,7 @@ pub mod asynch {
|
||||
/// Asynchronous USB driver.
|
||||
pub struct Driver<'d> {
|
||||
inner: OtgDriver<'d, MAX_EP_COUNT>,
|
||||
_usb: Usb<'d>,
|
||||
}
|
||||
|
||||
impl<'d> Driver<'d> {
|
||||
@ -180,7 +180,7 @@ pub mod asynch {
|
||||
///
|
||||
/// Must be large enough to fit all OUT endpoint max packet sizes.
|
||||
/// Endpoint allocation will fail if it is too small.
|
||||
pub fn new(_peri: Usb<'d>, ep_out_buffer: &'d mut [u8], config: Config) -> Self {
|
||||
pub fn new(peri: Usb<'d>, ep_out_buffer: &'d mut [u8], config: Config) -> Self {
|
||||
// From `synopsys-usb-otg` crate:
|
||||
// This calculation doesn't correspond to one in a Reference Manual.
|
||||
// In fact, the required number of words is higher than indicated in RM.
|
||||
@ -194,11 +194,11 @@ pub mod asynch {
|
||||
extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS,
|
||||
endpoint_count: Usb::ENDPOINT_COUNT,
|
||||
phy_type: PhyType::InternalFullSpeed,
|
||||
quirk_setup_late_cnak: quirk_setup_late_cnak(),
|
||||
calculate_trdt_fn: |_| 5,
|
||||
};
|
||||
Self {
|
||||
inner: OtgDriver::new(ep_out_buffer, instance, config),
|
||||
_usb: peri,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,13 +232,14 @@ pub mod asynch {
|
||||
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
||||
let (bus, cp) = self.inner.start(control_max_packet_size);
|
||||
|
||||
(
|
||||
Bus {
|
||||
let mut bus = Bus {
|
||||
inner: bus,
|
||||
inited: false,
|
||||
},
|
||||
cp,
|
||||
)
|
||||
_usb: self._usb,
|
||||
};
|
||||
|
||||
bus.init();
|
||||
|
||||
(bus, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,10 +247,10 @@ pub mod asynch {
|
||||
// We need a custom wrapper implementation to handle custom initialization.
|
||||
pub struct Bus<'d> {
|
||||
inner: OtgBus<'d, MAX_EP_COUNT>,
|
||||
inited: bool,
|
||||
_usb: Usb<'d>,
|
||||
}
|
||||
|
||||
impl<'d> Bus<'d> {
|
||||
impl Bus<'_> {
|
||||
fn init(&mut self) {
|
||||
Usb::_enable();
|
||||
|
||||
@ -259,42 +260,39 @@ pub mod asynch {
|
||||
while !r.grstctl().read().ahbidl() {}
|
||||
|
||||
// Configure as device.
|
||||
r.gusbcfg().write(|w| {
|
||||
r.gusbcfg().modify(|w| {
|
||||
// Force device mode
|
||||
w.set_fdmod(true);
|
||||
w.set_srpcap(false);
|
||||
// Enable internal full-speed PHY
|
||||
w.set_physel(true);
|
||||
});
|
||||
self.inner.config_v1();
|
||||
|
||||
// Perform core soft-reset
|
||||
while !r.grstctl().read().ahbidl() {}
|
||||
r.grstctl().modify(|w| w.set_csrst(true));
|
||||
while r.grstctl().read().csrst() {}
|
||||
|
||||
r.pcgcctl().modify(|w| {
|
||||
// Disable power down
|
||||
w.set_stppclk(false);
|
||||
});
|
||||
self.inner.config_v1();
|
||||
|
||||
// Enable PHY clock
|
||||
r.pcgcctl().write(|w| w.0 = 0);
|
||||
|
||||
unsafe {
|
||||
crate::interrupt::bind_interrupt(
|
||||
crate::peripherals::Interrupt::USB,
|
||||
interrupt_handler.handler(),
|
||||
);
|
||||
crate::interrupt::enable(
|
||||
}
|
||||
unwrap!(crate::interrupt::enable(
|
||||
crate::peripherals::Interrupt::USB,
|
||||
interrupt_handler.priority(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
fn disable(&mut self) {
|
||||
crate::interrupt::disable(Cpu::ProCpu, crate::peripherals::Interrupt::USB);
|
||||
crate::interrupt::disable(Cpu::ProCpu, peripherals::Interrupt::USB);
|
||||
|
||||
#[cfg(multi_core)]
|
||||
crate::interrupt::disable(Cpu::AppCpu, crate::peripherals::Interrupt::USB);
|
||||
crate::interrupt::disable(Cpu::AppCpu, peripherals::Interrupt::USB);
|
||||
|
||||
Usb::_disable();
|
||||
}
|
||||
@ -302,11 +300,6 @@ pub mod asynch {
|
||||
|
||||
impl<'d> embassy_usb_driver::Bus for Bus<'d> {
|
||||
async fn poll(&mut self) -> Event {
|
||||
if !self.inited {
|
||||
self.init();
|
||||
self.inited = true;
|
||||
}
|
||||
|
||||
self.inner.poll().await
|
||||
}
|
||||
|
||||
@ -335,35 +328,19 @@ pub mod asynch {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Drop for Bus<'d> {
|
||||
impl Drop for Bus<'_> {
|
||||
fn drop(&mut self) {
|
||||
Bus::disable(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn quirk_setup_late_cnak() -> bool {
|
||||
// Our CID register is 4 bytes offset from what's in embassy-usb-synopsys-otg
|
||||
let cid = unsafe {
|
||||
Driver::REGISTERS
|
||||
.as_ptr()
|
||||
.cast::<u32>()
|
||||
.add(0x40)
|
||||
.read_volatile()
|
||||
};
|
||||
// ESP32-Sx has a different CID register value, too
|
||||
cid == 0x4f54_400a || cid & 0xf000 == 0x1000
|
||||
}
|
||||
|
||||
#[handler(priority = crate::interrupt::Priority::max())]
|
||||
fn interrupt_handler() {
|
||||
let setup_late_cnak = quirk_setup_late_cnak();
|
||||
|
||||
unsafe {
|
||||
on_interrupt(
|
||||
Driver::REGISTERS,
|
||||
&STATE,
|
||||
Usb::ENDPOINT_COUNT,
|
||||
setup_late_cnak,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,16 +12,118 @@
|
||||
//!
|
||||
//! ## Examples
|
||||
//! ### Initialization for RX
|
||||
//! See the [Parallel IO RX] example to learn how to initialize the RX and
|
||||
//! reading data.
|
||||
//!
|
||||
//! [Parallel IO RX]: https://github.com/esp-rs/esp-hal/blob/main/examples/src/bin/parl_io_rx.rs
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::delay::Delay;
|
||||
//! # use esp_hal::dma_buffers;
|
||||
//! # use esp_hal::gpio::NoPin;
|
||||
//! # use esp_hal::parl_io::{BitPackOrder, ParlIoRxOnly, RxFourBits};
|
||||
//!
|
||||
//! // Initialize DMA buffer and descriptors for data reception
|
||||
//! let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0);
|
||||
//! let dma_channel = peripherals.DMA_CH0;
|
||||
//!
|
||||
//! // Configure the 4-bit input pins and clock pin
|
||||
//! let mut rx_pins = RxFourBits::new(
|
||||
//! peripherals.GPIO1,
|
||||
//! peripherals.GPIO2,
|
||||
//! peripherals.GPIO3,
|
||||
//! peripherals.GPIO4,
|
||||
//! );
|
||||
//! let mut rx_clk_pin = NoPin;
|
||||
//!
|
||||
//! // Set up Parallel IO for 1MHz data input, with DMA and bit packing
|
||||
//! // configuration
|
||||
//! let parl_io = ParlIoRxOnly::new(
|
||||
//! peripherals.PARL_IO,
|
||||
//! dma_channel,
|
||||
//! rx_descriptors,
|
||||
//! 1.MHz(),
|
||||
//! )
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! let mut parl_io_rx = parl_io
|
||||
//! .rx
|
||||
//! .with_config(
|
||||
//! &mut rx_pins,
|
||||
//! &mut rx_clk_pin,
|
||||
//! BitPackOrder::Msb,
|
||||
//! Some(0xfff),
|
||||
//! )
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! // Initialize the buffer and delay
|
||||
//! let mut buffer = rx_buffer;
|
||||
//! buffer.fill(0u8);
|
||||
//! let delay = Delay::new();
|
||||
//!
|
||||
//! loop {
|
||||
//! // Read data via DMA and print received values
|
||||
//! let transfer = parl_io_rx.read_dma(&mut buffer).unwrap();
|
||||
//! transfer.wait().unwrap();
|
||||
//!
|
||||
//! delay.delay_millis(500);
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Initialization for TX
|
||||
//! See the [Parallel IO TX] example to learn how to initialize the TX and
|
||||
//! transferring data.
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::delay::Delay;
|
||||
//! # use esp_hal::dma_buffers;
|
||||
//! # use esp_hal::parl_io::{BitPackOrder, ParlIoTxOnly, TxFourBits, SampleEdge, ClkOutPin, TxPinConfigWithValidPin};
|
||||
//!
|
||||
//! [Parallel IO TX]: https://github.com/esp-rs/esp-hal/blob/main/examples/src/bin/parl_io_tx.rs
|
||||
//! // Initialize DMA buffer and descriptors for data reception
|
||||
//! let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000);
|
||||
//! let dma_channel = peripherals.DMA_CH0;
|
||||
//!
|
||||
//! // Configure the 4-bit input pins and clock pin
|
||||
//! let tx_pins = TxFourBits::new(
|
||||
//! peripherals.GPIO1,
|
||||
//! peripherals.GPIO2,
|
||||
//! peripherals.GPIO3,
|
||||
//! peripherals.GPIO4,
|
||||
//! );
|
||||
//!
|
||||
//! let mut pin_conf = TxPinConfigWithValidPin::new(tx_pins, peripherals.GPIO5);
|
||||
//!
|
||||
//! // Set up Parallel IO for 1MHz data input, with DMA and bit packing
|
||||
//! // configuration
|
||||
//! let parl_io = ParlIoTxOnly::new(
|
||||
//! peripherals.PARL_IO,
|
||||
//! dma_channel,
|
||||
//! tx_descriptors,
|
||||
//! 1.MHz(),
|
||||
//! )
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! let mut clock_pin = ClkOutPin::new(peripherals.GPIO6);
|
||||
//! let mut parl_io_tx = parl_io
|
||||
//! .tx
|
||||
//! .with_config(
|
||||
//! &mut pin_conf,
|
||||
//! &mut clock_pin,
|
||||
//! 0,
|
||||
//! SampleEdge::Normal,
|
||||
//! BitPackOrder::Msb,
|
||||
//! )
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! let buffer = tx_buffer;
|
||||
//! for i in 0..buffer.len() {
|
||||
//! buffer[i] = (i % 255) as u8;
|
||||
//! }
|
||||
//!
|
||||
//! let delay = Delay::new();
|
||||
//! loop {
|
||||
//! let transfer = parl_io_tx.write_dma(&buffer).unwrap();
|
||||
//! transfer.wait().unwrap();
|
||||
//! delay.delay_millis(500);
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use enumset::{EnumSet, EnumSetType};
|
||||
use fugit::HertzU32;
|
||||
@ -35,30 +137,32 @@ use crate::{
|
||||
ChannelRx,
|
||||
ChannelTx,
|
||||
DescriptorChain,
|
||||
DmaChannelConvert,
|
||||
DmaChannelFor,
|
||||
DmaDescriptor,
|
||||
DmaEligible,
|
||||
DmaError,
|
||||
DmaPeripheral,
|
||||
DmaTransferRx,
|
||||
DmaTransferTx,
|
||||
PeripheralRxChannel,
|
||||
PeripheralTxChannel,
|
||||
ReadBuffer,
|
||||
Rx,
|
||||
RxChannelFor,
|
||||
Tx,
|
||||
TxChannelFor,
|
||||
WriteBuffer,
|
||||
},
|
||||
gpio::{
|
||||
interconnect::{InputConnection, OutputConnection, PeripheralInput, PeripheralOutput},
|
||||
NoPin,
|
||||
},
|
||||
interrupt::InterruptHandler,
|
||||
interrupt::{InterruptConfigurable, InterruptHandler},
|
||||
peripheral::{self, Peripheral},
|
||||
peripherals::{self, Interrupt, PARL_IO},
|
||||
system::PeripheralClockControl,
|
||||
peripherals::{Interrupt, PARL_IO},
|
||||
system::{self, GenericPeripheralGuard},
|
||||
Async,
|
||||
Blocking,
|
||||
InterruptConfigurable,
|
||||
Mode,
|
||||
DriverMode,
|
||||
};
|
||||
|
||||
#[allow(unused)]
|
||||
@ -81,6 +185,7 @@ pub enum ParlIoInterrupt {
|
||||
/// Parallel IO errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
|
||||
pub enum Error {
|
||||
/// General DMA error
|
||||
DmaError(DmaError),
|
||||
@ -740,9 +845,9 @@ impl ContainsValidSignalPin for RxEightBits<'_> {}
|
||||
#[cfg(esp32c6)]
|
||||
impl ContainsValidSignalPin for RxSixteenBits<'_> {}
|
||||
|
||||
impl<'d, DM> TxCreatorFullDuplex<'d, DM>
|
||||
impl<'d, Dm> TxCreatorFullDuplex<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Configure TX to use the given pins and settings
|
||||
pub fn with_config<P, CP>(
|
||||
@ -752,7 +857,7 @@ where
|
||||
idle_value: u16,
|
||||
sample_edge: SampleEdge,
|
||||
bit_order: BitPackOrder,
|
||||
) -> Result<ParlIoTx<'d, DM>, Error>
|
||||
) -> Result<ParlIoTx<'d, Dm>, Error>
|
||||
where
|
||||
P: FullDuplex + TxPins + ConfigurePins,
|
||||
CP: TxClkPin,
|
||||
@ -767,13 +872,14 @@ where
|
||||
Ok(ParlIoTx {
|
||||
tx_channel: self.tx_channel,
|
||||
tx_chain: DescriptorChain::new(self.descriptors),
|
||||
_guard: self._guard,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM> TxCreator<'d, DM>
|
||||
impl<'d, Dm> TxCreator<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Configure TX to use the given pins and settings
|
||||
pub fn with_config<P, CP>(
|
||||
@ -783,7 +889,7 @@ where
|
||||
idle_value: u16,
|
||||
sample_edge: SampleEdge,
|
||||
bit_order: BitPackOrder,
|
||||
) -> Result<ParlIoTx<'d, DM>, Error>
|
||||
) -> Result<ParlIoTx<'d, Dm>, Error>
|
||||
where
|
||||
P: TxPins + ConfigurePins,
|
||||
CP: TxClkPin,
|
||||
@ -798,31 +904,33 @@ where
|
||||
Ok(ParlIoTx {
|
||||
tx_channel: self.tx_channel,
|
||||
tx_chain: DescriptorChain::new(self.descriptors),
|
||||
_guard: self._guard,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel IO TX channel
|
||||
pub struct ParlIoTx<'d, DM>
|
||||
pub struct ParlIoTx<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
tx_channel: ChannelTx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>,
|
||||
tx_chain: DescriptorChain,
|
||||
_guard: GenericPeripheralGuard<{ crate::system::Peripheral::ParlIo as u8 }>,
|
||||
}
|
||||
|
||||
impl<DM> core::fmt::Debug for ParlIoTx<'_, DM>
|
||||
impl<Dm> core::fmt::Debug for ParlIoTx<'_, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("ParlIoTx").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM> RxCreatorFullDuplex<'d, DM>
|
||||
impl<'d, Dm> RxCreatorFullDuplex<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Configure RX to use the given pins and settings
|
||||
pub fn with_config<P, CP>(
|
||||
@ -831,11 +939,13 @@ where
|
||||
clk_pin: &'d mut CP,
|
||||
bit_order: BitPackOrder,
|
||||
timeout_ticks: Option<u16>,
|
||||
) -> Result<ParlIoRx<'d, DM>, Error>
|
||||
) -> Result<ParlIoRx<'d, Dm>, Error>
|
||||
where
|
||||
P: FullDuplex + RxPins + ConfigurePins,
|
||||
CP: RxClkPin,
|
||||
{
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
rx_pins.configure()?;
|
||||
clk_pin.configure();
|
||||
|
||||
@ -845,13 +955,14 @@ where
|
||||
Ok(ParlIoRx {
|
||||
rx_channel: self.rx_channel,
|
||||
rx_chain: DescriptorChain::new(self.descriptors),
|
||||
_guard: guard,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM> RxCreator<'d, DM>
|
||||
impl<'d, Dm> RxCreator<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Configure RX to use the given pins and settings
|
||||
pub fn with_config<P, CP>(
|
||||
@ -860,7 +971,7 @@ where
|
||||
clk_pin: &'d mut CP,
|
||||
bit_order: BitPackOrder,
|
||||
timeout_ticks: Option<u16>,
|
||||
) -> Result<ParlIoRx<'d, DM>, Error>
|
||||
) -> Result<ParlIoRx<'d, Dm>, Error>
|
||||
where
|
||||
P: RxPins + ConfigurePins,
|
||||
CP: RxClkPin,
|
||||
@ -874,22 +985,24 @@ where
|
||||
Ok(ParlIoRx {
|
||||
rx_channel: self.rx_channel,
|
||||
rx_chain: DescriptorChain::new(self.descriptors),
|
||||
_guard: self._guard,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel IO RX channel
|
||||
pub struct ParlIoRx<'d, DM>
|
||||
pub struct ParlIoRx<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
rx_channel: ChannelRx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
|
||||
rx_chain: DescriptorChain,
|
||||
_guard: GenericPeripheralGuard<{ crate::system::Peripheral::ParlIo as u8 }>,
|
||||
}
|
||||
|
||||
impl<DM> core::fmt::Debug for ParlIoRx<'_, DM>
|
||||
impl<Dm> core::fmt::Debug for ParlIoRx<'_, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("ParlIoTx").finish()
|
||||
@ -981,57 +1094,72 @@ fn internal_clear_interrupts(interrupts: EnumSet<ParlIoInterrupt>) {
|
||||
/// Parallel IO in full duplex mode
|
||||
///
|
||||
/// Full duplex mode might limit the maximum possible bit width.
|
||||
pub struct ParlIoFullDuplex<'d, DM>
|
||||
pub struct ParlIoFullDuplex<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// The transmitter (TX) channel responsible for handling DMA transfers in
|
||||
/// the parallel I/O full-duplex operation.
|
||||
pub tx: TxCreatorFullDuplex<'d, DM>,
|
||||
pub tx: TxCreatorFullDuplex<'d, Dm>,
|
||||
/// The receiver (RX) channel responsible for handling DMA transfers in the
|
||||
/// parallel I/O full-duplex operation.
|
||||
pub rx: RxCreatorFullDuplex<'d, DM>,
|
||||
pub rx: RxCreatorFullDuplex<'d, Dm>,
|
||||
}
|
||||
|
||||
impl<'d> ParlIoFullDuplex<'d, Blocking> {
|
||||
/// Create a new instance of [ParlIoFullDuplex]
|
||||
pub fn new<CH, DM>(
|
||||
_parl_io: impl Peripheral<P = peripherals::PARL_IO> + 'd,
|
||||
dma_channel: Channel<'d, DM, CH>,
|
||||
pub fn new<CH>(
|
||||
_parl_io: impl Peripheral<P = PARL_IO> + 'd,
|
||||
dma_channel: impl Peripheral<P = CH> + 'd,
|
||||
tx_descriptors: &'static mut [DmaDescriptor],
|
||||
rx_descriptors: &'static mut [DmaDescriptor],
|
||||
frequency: HertzU32,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
DM: Mode,
|
||||
CH: DmaChannelConvert<<PARL_IO as DmaEligible>::Dma>,
|
||||
Channel<'d, Blocking, CH>: From<Channel<'d, DM, CH>>,
|
||||
CH: DmaChannelFor<PARL_IO>,
|
||||
{
|
||||
let dma_channel = Channel::<Blocking, CH>::from(dma_channel);
|
||||
let tx_guard = GenericPeripheralGuard::new();
|
||||
let rx_guard = GenericPeripheralGuard::new();
|
||||
let dma_channel = Channel::new(dma_channel.map(|ch| ch.degrade()));
|
||||
internal_init(frequency)?;
|
||||
|
||||
Ok(Self {
|
||||
tx: TxCreatorFullDuplex {
|
||||
tx_channel: dma_channel.tx.degrade(),
|
||||
tx_channel: dma_channel.tx,
|
||||
descriptors: tx_descriptors,
|
||||
_guard: tx_guard,
|
||||
},
|
||||
rx: RxCreatorFullDuplex {
|
||||
rx_channel: dma_channel.rx.degrade(),
|
||||
rx_channel: dma_channel.rx,
|
||||
descriptors: rx_descriptors,
|
||||
_guard: rx_guard,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert to an async version.
|
||||
pub fn into_async(self) -> ParlIoFullDuplex<'d, Async> {
|
||||
for core in crate::Cpu::other() {
|
||||
#[cfg(esp32c6)]
|
||||
{
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO);
|
||||
}
|
||||
#[cfg(esp32h2)]
|
||||
{
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO_RX);
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO_TX);
|
||||
}
|
||||
}
|
||||
ParlIoFullDuplex {
|
||||
tx: TxCreatorFullDuplex {
|
||||
tx_channel: self.tx.tx_channel.into_async(),
|
||||
descriptors: self.tx.descriptors,
|
||||
_guard: self.tx._guard,
|
||||
},
|
||||
rx: RxCreatorFullDuplex {
|
||||
rx_channel: self.rx.rx_channel.into_async(),
|
||||
descriptors: self.rx.descriptors,
|
||||
_guard: self.rx._guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1080,52 +1208,73 @@ impl<'d> ParlIoFullDuplex<'d, Async> {
|
||||
tx: TxCreatorFullDuplex {
|
||||
tx_channel: self.tx.tx_channel.into_blocking(),
|
||||
descriptors: self.tx.descriptors,
|
||||
_guard: self.tx._guard,
|
||||
},
|
||||
rx: RxCreatorFullDuplex {
|
||||
rx_channel: self.rx.rx_channel.into_blocking(),
|
||||
descriptors: self.rx.descriptors,
|
||||
_guard: self.rx._guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel IO in half duplex / TX only mode
|
||||
pub struct ParlIoTxOnly<'d, DM>
|
||||
pub struct ParlIoTxOnly<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// The transmitter (TX) channel responsible for handling DMA transfers in
|
||||
/// the parallel I/O operation.
|
||||
pub tx: TxCreator<'d, DM>,
|
||||
pub tx: TxCreator<'d, Dm>,
|
||||
}
|
||||
|
||||
impl<'d, DM> ParlIoTxOnly<'d, DM>
|
||||
where
|
||||
DM: Mode,
|
||||
{
|
||||
/// Create a new [ParlIoTxOnly]
|
||||
// TODO: only take a TX DMA channel?
|
||||
impl<'d> ParlIoTxOnly<'d, Blocking> {
|
||||
/// Creates a new [ParlIoTxOnly]
|
||||
pub fn new<CH>(
|
||||
_parl_io: impl Peripheral<P = peripherals::PARL_IO> + 'd,
|
||||
dma_channel: Channel<'d, DM, CH>,
|
||||
_parl_io: impl Peripheral<P = PARL_IO> + 'd,
|
||||
dma_channel: impl Peripheral<P = CH> + 'd,
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
frequency: HertzU32,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
CH: DmaChannelConvert<<PARL_IO as DmaEligible>::Dma>,
|
||||
CH: TxChannelFor<PARL_IO>,
|
||||
{
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
let tx_channel = ChannelTx::new(dma_channel.map(|ch| ch.degrade()));
|
||||
internal_init(frequency)?;
|
||||
|
||||
Ok(Self {
|
||||
tx: TxCreator {
|
||||
tx_channel: dma_channel.tx.degrade(),
|
||||
tx_channel,
|
||||
descriptors,
|
||||
_guard: guard,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParlIoTxOnly<'_, Blocking> {
|
||||
/// Converts to Async mode.
|
||||
pub fn into_async(self) -> ParlIoTxOnly<'d, Async> {
|
||||
for core in crate::Cpu::other() {
|
||||
#[cfg(esp32c6)]
|
||||
{
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO);
|
||||
}
|
||||
#[cfg(esp32h2)]
|
||||
{
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO_RX);
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO_TX);
|
||||
}
|
||||
}
|
||||
ParlIoTxOnly {
|
||||
tx: TxCreator {
|
||||
tx_channel: self.tx.tx_channel.into_async(),
|
||||
descriptors: self.tx.descriptors,
|
||||
_guard: self.tx._guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the interrupt handler, enables it with
|
||||
/// [crate::interrupt::Priority::min()]
|
||||
///
|
||||
@ -1155,6 +1304,19 @@ impl ParlIoTxOnly<'_, Blocking> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ParlIoTxOnly<'d, Async> {
|
||||
/// Convert to a blocking version.
|
||||
pub fn into_blocking(self) -> ParlIoTxOnly<'d, Blocking> {
|
||||
ParlIoTxOnly {
|
||||
tx: TxCreator {
|
||||
tx_channel: self.tx.tx_channel.into_blocking(),
|
||||
descriptors: self.tx.descriptors,
|
||||
_guard: self.tx._guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::private::Sealed for ParlIoTxOnly<'_, Blocking> {}
|
||||
|
||||
impl InterruptConfigurable for ParlIoTxOnly<'_, Blocking> {
|
||||
@ -1164,42 +1326,62 @@ impl InterruptConfigurable for ParlIoTxOnly<'_, Blocking> {
|
||||
}
|
||||
|
||||
/// Parallel IO in half duplex / RX only mode
|
||||
pub struct ParlIoRxOnly<'d, DM>
|
||||
pub struct ParlIoRxOnly<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// The receiver (RX) channel responsible for handling DMA transfers in the
|
||||
/// parallel I/O operation.
|
||||
pub rx: RxCreator<'d, DM>,
|
||||
pub rx: RxCreator<'d, Dm>,
|
||||
}
|
||||
|
||||
impl<'d, DM> ParlIoRxOnly<'d, DM>
|
||||
where
|
||||
DM: Mode,
|
||||
{
|
||||
impl<'d> ParlIoRxOnly<'d, Blocking> {
|
||||
/// Create a new [ParlIoRxOnly] instance
|
||||
// TODO: only take a RX DMA channel?
|
||||
pub fn new<CH>(
|
||||
_parl_io: impl Peripheral<P = peripherals::PARL_IO> + 'd,
|
||||
dma_channel: Channel<'d, DM, CH>,
|
||||
_parl_io: impl Peripheral<P = PARL_IO> + 'd,
|
||||
dma_channel: impl Peripheral<P = CH> + 'd,
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
frequency: HertzU32,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
CH: DmaChannelConvert<<PARL_IO as DmaEligible>::Dma>,
|
||||
CH: RxChannelFor<PARL_IO>,
|
||||
{
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
let rx_channel = ChannelRx::new(dma_channel.map(|ch| ch.degrade()));
|
||||
internal_init(frequency)?;
|
||||
|
||||
Ok(Self {
|
||||
rx: RxCreator {
|
||||
rx_channel: dma_channel.rx.degrade(),
|
||||
rx_channel,
|
||||
descriptors,
|
||||
_guard: guard,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParlIoRxOnly<'_, Blocking> {
|
||||
/// Converts to Async mode.
|
||||
pub fn into_async(self) -> ParlIoRxOnly<'d, Async> {
|
||||
for core in crate::Cpu::other() {
|
||||
#[cfg(esp32c6)]
|
||||
{
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO);
|
||||
}
|
||||
#[cfg(esp32h2)]
|
||||
{
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO_RX);
|
||||
crate::interrupt::disable(core, Interrupt::PARL_IO_TX);
|
||||
}
|
||||
}
|
||||
|
||||
ParlIoRxOnly {
|
||||
rx: RxCreator {
|
||||
rx_channel: self.rx.rx_channel.into_async(),
|
||||
descriptors: self.rx.descriptors,
|
||||
_guard: self.rx._guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the interrupt handler, enables it with
|
||||
/// [crate::interrupt::Priority::min()]
|
||||
///
|
||||
@ -1229,6 +1411,19 @@ impl ParlIoRxOnly<'_, Blocking> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ParlIoRxOnly<'d, Async> {
|
||||
/// Convert to a blocking version.
|
||||
pub fn into_blocking(self) -> ParlIoRxOnly<'d, Blocking> {
|
||||
ParlIoRxOnly {
|
||||
rx: RxCreator {
|
||||
rx_channel: self.rx.rx_channel.into_blocking(),
|
||||
descriptors: self.rx.descriptors,
|
||||
_guard: self.rx._guard,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::private::Sealed for ParlIoRxOnly<'_, Blocking> {}
|
||||
|
||||
impl InterruptConfigurable for ParlIoRxOnly<'_, Blocking> {
|
||||
@ -1242,9 +1437,6 @@ fn internal_init(frequency: HertzU32) -> Result<(), Error> {
|
||||
return Err(Error::UnreachableClockRate);
|
||||
}
|
||||
|
||||
PeripheralClockControl::reset(crate::system::Peripheral::ParlIo);
|
||||
PeripheralClockControl::enable(crate::system::Peripheral::ParlIo);
|
||||
|
||||
let pcr = unsafe { &*crate::peripherals::PCR::PTR };
|
||||
|
||||
let divider = crate::soc::constants::PARL_IO_SCLK / frequency.raw();
|
||||
@ -1270,9 +1462,9 @@ fn internal_init(frequency: HertzU32) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<DM> ParlIoTx<'_, DM>
|
||||
impl<Dm> ParlIoTx<'_, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Perform a DMA write.
|
||||
///
|
||||
@ -1325,9 +1517,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<DM> DmaSupport for ParlIoTx<'_, DM>
|
||||
impl<Dm> DmaSupport for ParlIoTx<'_, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
|
||||
while !Instance::is_tx_eof() {}
|
||||
@ -1340,11 +1532,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM> DmaSupportTx for ParlIoTx<'d, DM>
|
||||
impl<'d, Dm> DmaSupportTx for ParlIoTx<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
type TX = ChannelTx<'d, DM, <PARL_IO as DmaEligible>::Dma>;
|
||||
type TX = ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>;
|
||||
|
||||
fn tx(&mut self) -> &mut Self::TX {
|
||||
&mut self.tx_channel
|
||||
@ -1355,9 +1547,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM> ParlIoRx<'d, DM>
|
||||
impl<'d, Dm> ParlIoRx<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
/// Perform a DMA read.
|
||||
///
|
||||
@ -1386,7 +1578,7 @@ where
|
||||
}
|
||||
|
||||
fn start_receive_bytes_dma(
|
||||
rx_channel: &mut ChannelRx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
rx_channel: &mut ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
|
||||
rx_chain: &mut DescriptorChain,
|
||||
ptr: *mut u8,
|
||||
len: usize,
|
||||
@ -1414,9 +1606,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<DM> DmaSupport for ParlIoRx<'_, DM>
|
||||
impl<Dm> DmaSupport for ParlIoRx<'_, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
|
||||
loop {
|
||||
@ -1436,11 +1628,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM> DmaSupportRx for ParlIoRx<'d, DM>
|
||||
impl<'d, Dm> DmaSupportRx for ParlIoRx<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
type RX = ChannelRx<'d, DM, <PARL_IO as DmaEligible>::Dma>;
|
||||
type RX = ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>;
|
||||
|
||||
fn rx(&mut self) -> &mut Self::RX {
|
||||
&mut self.rx_channel
|
||||
@ -1452,50 +1644,54 @@ where
|
||||
}
|
||||
|
||||
/// Creates a TX channel
|
||||
pub struct TxCreator<'d, DM>
|
||||
pub struct TxCreator<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
tx_channel: ChannelTx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>,
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
_guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
|
||||
}
|
||||
|
||||
/// Creates a RX channel
|
||||
pub struct RxCreator<'d, DM>
|
||||
pub struct RxCreator<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
rx_channel: ChannelRx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
_guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
|
||||
}
|
||||
|
||||
/// Creates a TX channel
|
||||
pub struct TxCreatorFullDuplex<'d, DM>
|
||||
pub struct TxCreatorFullDuplex<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
tx_channel: ChannelTx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>,
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
_guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
|
||||
}
|
||||
|
||||
/// Creates a RX channel
|
||||
pub struct RxCreatorFullDuplex<'d, DM>
|
||||
pub struct RxCreatorFullDuplex<'d, Dm>
|
||||
where
|
||||
DM: Mode,
|
||||
Dm: DriverMode,
|
||||
{
|
||||
rx_channel: ChannelRx<'d, DM, <PARL_IO as DmaEligible>::Dma>,
|
||||
rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
_guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod asynch {
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use procmacros::handler;
|
||||
|
||||
use super::{private::Instance, Error, ParlIoRx, ParlIoTx, MAX_DMA_SIZE};
|
||||
use crate::{
|
||||
asynch::AtomicWaker,
|
||||
dma::{asynch::DmaRxFuture, ReadBuffer, WriteBuffer},
|
||||
peripherals::Interrupt,
|
||||
};
|
||||
@ -1508,6 +1704,25 @@ pub mod asynch {
|
||||
impl TxDoneFuture {
|
||||
pub fn new() -> Self {
|
||||
Instance::listen_tx_done();
|
||||
let mut parl_io = unsafe { crate::peripherals::PARL_IO::steal() };
|
||||
|
||||
#[cfg(esp32c6)]
|
||||
{
|
||||
parl_io.bind_parl_io_interrupt(interrupt_handler.handler());
|
||||
unwrap!(crate::interrupt::enable(
|
||||
Interrupt::PARL_IO,
|
||||
interrupt_handler.priority()
|
||||
));
|
||||
}
|
||||
#[cfg(esp32h2)]
|
||||
{
|
||||
parl_io.bind_parl_io_tx_interrupt(interrupt_handler.handler());
|
||||
unwrap!(crate::interrupt::enable(
|
||||
Interrupt::PARL_IO_TX,
|
||||
interrupt_handler.priority()
|
||||
));
|
||||
}
|
||||
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
@ -1519,20 +1734,6 @@ pub mod asynch {
|
||||
self: core::pin::Pin<&mut Self>,
|
||||
cx: &mut core::task::Context<'_>,
|
||||
) -> Poll<Self::Output> {
|
||||
let mut parl_io = unsafe { crate::peripherals::PARL_IO::steal() };
|
||||
|
||||
#[cfg(esp32c6)]
|
||||
{
|
||||
parl_io.bind_parl_io_interrupt(interrupt_handler.handler());
|
||||
crate::interrupt::enable(Interrupt::PARL_IO, interrupt_handler.priority()).unwrap();
|
||||
}
|
||||
#[cfg(esp32h2)]
|
||||
{
|
||||
parl_io.bind_parl_io_tx_interrupt(interrupt_handler.handler());
|
||||
crate::interrupt::enable(Interrupt::PARL_IO_TX, interrupt_handler.priority())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
TX_WAKER.register(cx.waker());
|
||||
if Instance::is_listening_tx_done() {
|
||||
Poll::Pending
|
||||
|
||||
@ -13,6 +13,7 @@ pub use crate::peripherals::pcnt::unit::conf0::{CTRL_MODE as CtrlMode, EDGE_MODE
|
||||
use crate::{
|
||||
gpio::{interconnect::PeripheralInput, InputSignal},
|
||||
peripheral::Peripheral,
|
||||
system::GenericPeripheralGuard,
|
||||
};
|
||||
|
||||
/// Represents a channel within a pulse counter unit.
|
||||
@ -20,14 +21,18 @@ pub struct Channel<'d, const UNIT: usize, const NUM: usize> {
|
||||
_phantom: PhantomData<&'d ()>,
|
||||
// Individual channels are not Send, since they share registers.
|
||||
_not_send: PhantomData<*const ()>,
|
||||
_guard: GenericPeripheralGuard<{ crate::system::Peripheral::Pcnt as u8 }>,
|
||||
}
|
||||
|
||||
impl<const UNIT: usize, const NUM: usize> Channel<'_, UNIT, NUM> {
|
||||
/// return a new Channel
|
||||
pub(super) fn new() -> Self {
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
|
||||
Self {
|
||||
_phantom: PhantomData,
|
||||
_not_send: PhantomData,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,19 +14,91 @@
|
||||
//!
|
||||
//! ## Examples
|
||||
//! ### Decoding a quadrature encoder
|
||||
//! Visit the [PCNT Encoder] example for an example of using the peripheral.
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::gpio::{Input, Pull};
|
||||
//! # use esp_hal::interrupt::Priority;
|
||||
//! # use esp_hal::pcnt::{channel, unit, Pcnt};
|
||||
//! # use core::{sync::atomic::Ordering, cell::RefCell, cmp::min};
|
||||
//! # use critical_section::Mutex;
|
||||
//! # use portable_atomic::AtomicI32;
|
||||
//!
|
||||
//! static UNIT0: Mutex<RefCell<Option<unit::Unit<'static, 1>>>> =
|
||||
//! Mutex::new(RefCell::new(None)); static VALUE: AtomicI32 = AtomicI32::new(0);
|
||||
//!
|
||||
//! // Initialize Pulse Counter (PCNT) unit with limits and filter settings
|
||||
//! let mut pcnt = Pcnt::new(peripherals.PCNT);
|
||||
//! pcnt.set_interrupt_handler(interrupt_handler);
|
||||
//! let u0 = pcnt.unit1;
|
||||
//! u0.set_low_limit(Some(-100)).unwrap();
|
||||
//! u0.set_high_limit(Some(100)).unwrap();
|
||||
//! u0.set_filter(Some(min(10u16 * 80, 1023u16))).unwrap();
|
||||
//! u0.clear();
|
||||
//!
|
||||
//! // Set up channels with control and edge signals
|
||||
//! let ch0 = &u0.channel0;
|
||||
//! let pin_a = Input::new(peripherals.GPIO4, Pull::Up);
|
||||
//! let pin_b = Input::new(peripherals.GPIO5, Pull::Up);
|
||||
//! let (input_a, _) = pin_a.split();
|
||||
//! let (input_b, _) = pin_b.split();
|
||||
//! ch0.set_ctrl_signal(input_a.clone());
|
||||
//! ch0.set_edge_signal(input_b.clone());
|
||||
//! ch0.set_ctrl_mode(channel::CtrlMode::Reverse, channel::CtrlMode::Keep);
|
||||
//! ch0.set_input_mode(channel::EdgeMode::Increment,
|
||||
//! channel::EdgeMode::Decrement);
|
||||
//!
|
||||
//! let ch1 = &u0.channel1;
|
||||
//! ch1.set_ctrl_signal(input_b);
|
||||
//! ch1.set_edge_signal(input_a);
|
||||
//! ch1.set_ctrl_mode(channel::CtrlMode::Reverse, channel::CtrlMode::Keep);
|
||||
//! ch1.set_input_mode(channel::EdgeMode::Decrement,
|
||||
//! channel::EdgeMode::Increment);
|
||||
//!
|
||||
//! // Enable interrupts and resume pulse counter unit
|
||||
//! u0.listen();
|
||||
//! u0.resume();
|
||||
//! let counter = u0.counter.clone();
|
||||
//!
|
||||
//! critical_section::with(|cs| UNIT0.borrow_ref_mut(cs).replace(u0));
|
||||
//!
|
||||
//! // Monitor counter value and print updates
|
||||
//! let mut last_value: i32 = 0;
|
||||
//! loop {
|
||||
//! let value: i32 = counter.get() as i32 + VALUE.load(Ordering::SeqCst);
|
||||
//! if value != last_value {
|
||||
//! last_value = value;
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[handler(priority = Priority::Priority2)]
|
||||
//! fn interrupt_handler() {
|
||||
//! critical_section::with(|cs| {
|
||||
//! let mut u0 = UNIT0.borrow_ref_mut(cs);
|
||||
//! let u0 = u0.as_mut().unwrap();
|
||||
//! if u0.interrupt_is_set() {
|
||||
//! let events = u0.events();
|
||||
//! if events.high_limit {
|
||||
//! VALUE.fetch_add(100, Ordering::SeqCst);
|
||||
//! } else if events.low_limit {
|
||||
//! VALUE.fetch_add(-100, Ordering::SeqCst);
|
||||
//! }
|
||||
//! u0.reset_interrupt();
|
||||
//! }
|
||||
//! });
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [channel]: channel/index.html
|
||||
//! [unit]: unit/index.html
|
||||
//! [PCNT Encoder]: https://github.com/esp-rs/esp-hal/blob/main/examples/src/bin/pcnt_encoder.rs
|
||||
|
||||
use self::unit::Unit;
|
||||
use crate::{
|
||||
interrupt::{self, InterruptHandler},
|
||||
interrupt::{self, InterruptConfigurable, InterruptHandler},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::{self, Interrupt},
|
||||
system::PeripheralClockControl,
|
||||
InterruptConfigurable,
|
||||
system::GenericPeripheralGuard,
|
||||
};
|
||||
|
||||
pub mod channel;
|
||||
@ -56,6 +128,8 @@ pub struct Pcnt<'d> {
|
||||
#[cfg(esp32)]
|
||||
/// Unit 7
|
||||
pub unit7: Unit<'d, 7>,
|
||||
|
||||
_guard: GenericPeripheralGuard<{ crate::system::Peripheral::Pcnt as u8 }>,
|
||||
}
|
||||
|
||||
impl<'d> Pcnt<'d> {
|
||||
@ -63,10 +137,7 @@ impl<'d> Pcnt<'d> {
|
||||
pub fn new(_instance: impl Peripheral<P = peripherals::PCNT> + 'd) -> Self {
|
||||
crate::into_ref!(_instance);
|
||||
|
||||
// Enable the PCNT peripherals clock in the system peripheral
|
||||
PeripheralClockControl::reset(crate::system::Peripheral::Pcnt);
|
||||
PeripheralClockControl::enable(crate::system::Peripheral::Pcnt);
|
||||
|
||||
let guard = GenericPeripheralGuard::new();
|
||||
let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
|
||||
|
||||
// disable filter, all events, and channel settings
|
||||
@ -105,6 +176,7 @@ impl<'d> Pcnt<'d> {
|
||||
unit6: Unit::new(),
|
||||
#[cfg(esp32)]
|
||||
unit7: Unit::new(),
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user