Compare commits
6 Commits
993a0bcd3e
...
dma-experi
Author | SHA1 | Date | |
---|---|---|---|
6cd2e809d7
|
|||
16d2856fb2
|
|||
01341edc91
|
|||
d3deb8a467
|
|||
988d6adcdc
|
|||
c78c90b60d
|
@ -1,16 +1,21 @@
|
|||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
# runner = "gdb-multiarch -q -x jlink/jlink.gdb"
|
runner = "gdb-multiarch -q -x jlink/jlink.gdb"
|
||||||
# runner = "arm-none-eabi-gdb -q -x jlink/jlink-reva.gdb"
|
# runner = "arm-none-eabi-gdb -q -x jlink/jlink-reva.gdb"
|
||||||
# runner = "gdb-multiarch -q -x jlink/jlink-reva.gdb"
|
# runner = "gdb-multiarch -q -x jlink/jlink-reva.gdb"
|
||||||
runner = "probe-rs run --chip VA416xx_RAM --protocol swd"
|
|
||||||
|
# Probe-rs is currently problematic, possibly because of the
|
||||||
|
# ROM protection?
|
||||||
|
# runner = "probe-rs run --chip-description-path ./scripts/VA416xx_Series.yaml"
|
||||||
|
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
|
||||||
|
|
||||||
|
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C",
|
"-C",
|
||||||
"link-arg=-Tlink.x",
|
"link-arg=-Tlink.x",
|
||||||
"-C",
|
# "-C",
|
||||||
"linker=flip-link",
|
# "linker=flip-link",
|
||||||
"-C",
|
# "-C",
|
||||||
"link-arg=-Tdefmt.x",
|
# "link-arg=-Tdefmt.x",
|
||||||
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
||||||
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||||
"-C",
|
"-C",
|
||||||
@ -30,8 +35,6 @@ target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
|||||||
[alias]
|
[alias]
|
||||||
rb = "run --bin"
|
rb = "run --bin"
|
||||||
rrb = "run --release --bin"
|
rrb = "run --release --bin"
|
||||||
ut = "test --target=x86_64-unknown-linux-gnu"
|
|
||||||
genbin = "objcopy --release -- -O binary app.bin"
|
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
DEFMT_LOG = "debug"
|
DEFMT_LOG = "info"
|
||||||
|
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -10,10 +10,8 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: "thumbv7em-none-eabihf"
|
targets: "thumbv7em-none-eabihf"
|
||||||
- run: cargo check --target thumbv7em-none-eabihf
|
- run: cargo check --target thumbv7em-none-eabihf --release
|
||||||
- run: cargo check --target thumbv7em-none-eabihf --examples
|
- run: cargo check --target thumbv7em-none-eabihf --examples --release
|
||||||
- run: cargo check -p va416xx --target thumbv7em-none-eabihf --all-features
|
|
||||||
- run: cargo check -p va416xx-hal --target thumbv7em-none-eabihf --examples --features "defmt va41630"
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Run Tests
|
name: Run Tests
|
||||||
@ -23,7 +21,7 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@nextest
|
uses: taiki-e/install-action@nextest
|
||||||
- run: cargo nextest run --features va41630 -p va416xx-hal
|
- run: cargo nextest run --all-features -p va416xx-hal
|
||||||
# I think we can skip those on an embedded crate..
|
# I think we can skip those on an embedded crate..
|
||||||
# - run: cargo test --doc -p va108xx-hal
|
# - run: cargo test --doc -p va108xx-hal
|
||||||
|
|
||||||
@ -41,9 +39,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
|
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630
|
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
|
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,6 +14,3 @@ Cargo.lock
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
/app.map
|
/app.map
|
||||||
/app.bin
|
|
||||||
|
|
||||||
/Embed.toml
|
|
||||||
|
22
Cargo.toml
22
Cargo.toml
@ -1,19 +1,10 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
|
"examples/simple",
|
||||||
"va416xx",
|
"va416xx",
|
||||||
"va416xx-hal",
|
"va416xx-hal",
|
||||||
"va416xx-embassy",
|
"vorago-peb1"
|
||||||
"vorago-peb1",
|
|
||||||
"bootloader",
|
|
||||||
"flashloader",
|
|
||||||
"examples/simple",
|
|
||||||
"examples/embassy",
|
|
||||||
"examples/rtic",
|
|
||||||
]
|
|
||||||
exclude = [
|
|
||||||
"flashloader/slot-a-blinky",
|
|
||||||
"flashloader/slot-b-blinky",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
@ -34,12 +25,3 @@ incremental = false
|
|||||||
lto = 'fat'
|
lto = 'fat'
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = false # <-
|
overflow-checks = false # <-
|
||||||
|
|
||||||
[profile.small]
|
|
||||||
inherits = "release"
|
|
||||||
codegen-units = 1
|
|
||||||
debug-assertions = false # <-
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z' # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
strip = true # Automatically strip symbols from the binary.
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[default.general]
|
[default.general]
|
||||||
chip = "VA416xx_RAM"
|
chip = "VA416xx"
|
||||||
|
|
||||||
[default.rtt]
|
[default.rtt]
|
||||||
enabled = true
|
enabled = true
|
151
README.md
151
README.md
@ -3,7 +3,7 @@
|
|||||||
Vorago VA416xx Rust Support
|
Vorago VA416xx Rust Support
|
||||||
=========
|
=========
|
||||||
|
|
||||||
This crate collection provides support to write Rust applications for the VA416XX family
|
This crate collection provided support to write Rust applications for the VA416XX family
|
||||||
of devices.
|
of devices.
|
||||||
|
|
||||||
## List of crates
|
## List of crates
|
||||||
@ -14,26 +14,12 @@ This workspace contains the following crates:
|
|||||||
PAC crate containing basic low-level register definition
|
PAC crate containing basic low-level register definition
|
||||||
- The [`va416xx-hal`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-hal)
|
- The [`va416xx-hal`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-hal)
|
||||||
HAL crate containing higher-level abstractions on top of the PAC register crate.
|
HAL crate containing higher-level abstractions on top of the PAC register crate.
|
||||||
- The [`va416xx-embassy`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-embassy)
|
|
||||||
crate containing support for running the embassy-rs RTOS.
|
|
||||||
- The [`vorago-peb1`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/vorago-peb1)
|
- The [`vorago-peb1`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/vorago-peb1)
|
||||||
BSP crate containing support for the PEB1 development board.
|
BSP crate containing support for the PEB1 development board.
|
||||||
|
|
||||||
It also contains the following helper crates:
|
It also contains the following helper crates:
|
||||||
|
|
||||||
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader)
|
- The `examples` crates contains various example applications for the HAL and the PAC.
|
||||||
crate contains a sample bootloader strongly based on the one provided by Vorago.
|
|
||||||
- The [`flashloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
crate contains a sample flashloader which is able to update the redundant images in the NVM which
|
|
||||||
is compatible to the provided bootloader as well.
|
|
||||||
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples)
|
|
||||||
folder contains various example applications crates using the HAL and the PAC.
|
|
||||||
This folder also contains dedicated example applications using the
|
|
||||||
[`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy)
|
|
||||||
native Rust RTOSes.
|
|
||||||
|
|
||||||
Some parts of the HAL implementation and the Embassy-rs support are contained in the external
|
|
||||||
[`vorago-shared-periphs`](https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs) crate.
|
|
||||||
|
|
||||||
## Using the `.cargo/config.toml` file
|
## Using the `.cargo/config.toml` file
|
||||||
|
|
||||||
@ -61,25 +47,6 @@ You can then adapt the files in `.vscode` to your needs.
|
|||||||
You can use CLI or VS Code for flashing, running and debugging. In any case, take
|
You can use CLI or VS Code for flashing, running and debugging. In any case, take
|
||||||
care of installing the pre-requisites first.
|
care of installing the pre-requisites first.
|
||||||
|
|
||||||
### Using CLI with probe-rs
|
|
||||||
|
|
||||||
Install [probe-rs](https://probe.rs/docs/getting-started/installation/) first.
|
|
||||||
|
|
||||||
You can use `probe-rs` to run the software and display RTT log output. However, debugging does not
|
|
||||||
work yet.
|
|
||||||
|
|
||||||
After installation, you can run the following command
|
|
||||||
|
|
||||||
```sh
|
|
||||||
probe-rs run --chip VA416xx_RAM --protocol jtag target/thumbv7em-none-eabihf/debug/examples/blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
to flash and run the blinky program on the RAM. There is also a `VA416xx` chip target
|
|
||||||
available for persistent flashing.
|
|
||||||
|
|
||||||
Runner configuration is available in the `.cargo/def-config.toml` file to use `probe-rs` for
|
|
||||||
convenience. `probe-rs` is also able to process and display `defmt` strings directly.
|
|
||||||
|
|
||||||
### Pre-Requisites
|
### Pre-Requisites
|
||||||
|
|
||||||
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
|
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
|
||||||
@ -88,13 +55,43 @@ convenience. `probe-rs` is also able to process and display `defmt` strings dire
|
|||||||
|
|
||||||
### Using CLI
|
### Using CLI
|
||||||
|
|
||||||
|
You can build the blinky example application with the following command
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --example blinky
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the GDB server first. The server needs to be started with a certain configuration and with
|
||||||
|
a JLink script to disable ROM protection.
|
||||||
|
For example, on Debian based system the following command can be used to do this (this command
|
||||||
|
is also run when running the `jlink-gdb.sh` script)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
|
||||||
|
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, you can flash and debug the application with the following command
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gdb-mutliarch -q -x jlink/jlink.gdb target/thumbv7em-none-eabihf/debug/examples/blinky
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that you can automate all steps except starting the GDB server by using a cargo
|
||||||
|
runner configuration, for example with the following lines in your `.cargo/config.toml` file:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
|
runner = "gdb-multiarch -q -x jlink/jlink.gdb"
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, you can simply use `cargo run --example blinky` to flash the blinky
|
||||||
|
example.
|
||||||
|
|
||||||
### Using VS Code
|
### Using VS Code
|
||||||
|
|
||||||
Assuming a working debug connection to your VA416xx board, you can debug using VS Code with
|
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
|
||||||
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
|
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
|
||||||
Please make sure that [`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146)
|
|
||||||
are installed as well.
|
|
||||||
|
|
||||||
Some sample configuration files for VS code were provided and can be used by running
|
Some sample configuration files for VS code were provided and can be used by running
|
||||||
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
|
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
|
||||||
@ -109,80 +106,4 @@ configuration variables in your `settings.json`:
|
|||||||
- `"cortex-debug.gdbPath.osx"`
|
- `"cortex-debug.gdbPath.osx"`
|
||||||
|
|
||||||
The provided VS Code configurations also provide an integrated RTT logger, which you can access
|
The provided VS Code configurations also provide an integrated RTT logger, which you can access
|
||||||
via the terminal at `RTT Ch:0 console`. In order for the RTT block address detection to
|
via the terminal at `RTT Ch:0 console`.
|
||||||
work properly, `objdump-multiarch` and `nm-multiarch` need to be installed.
|
|
||||||
|
|
||||||
### Using CLI with GDB and Segger J-Link Tools
|
|
||||||
|
|
||||||
Install the following two tools first:
|
|
||||||
|
|
||||||
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
|
|
||||||
2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar
|
|
||||||
cross-architecture debugger installed. All commands here assume `gdb-multiarch`.
|
|
||||||
|
|
||||||
You can build the blinky example application with the following command
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo build --example blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the GDB server first. Depending on whether the application is flashed to RAM or the NVM
|
|
||||||
flash memory, the server needs to be started with a different configuration
|
|
||||||
For example, on Debian based system the following command can be used to do this (this command
|
|
||||||
is also run when running the `jlink-gdb.sh` script)
|
|
||||||
|
|
||||||
**RAM Flash**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
|
|
||||||
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
|
|
||||||
```
|
|
||||||
|
|
||||||
**NVM Flash**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
JLinkGDBServer -select USB -device VA416xx -endian little -if SWD -speed 2000 \
|
|
||||||
-LocalhostOnly -vd
|
|
||||||
```
|
|
||||||
|
|
||||||
After this, you can flash and debug the application with the following command
|
|
||||||
|
|
||||||
```sh
|
|
||||||
gdb-mutliarch -q -x jlink/jlink.gdb target/thumbv7em-none-eabihf/debug/examples/blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that you can automate all steps except starting the GDB server by using a cargo
|
|
||||||
runner configuration, for example with the following lines in your `.cargo/config.toml` file:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
|
||||||
runner = "gdb-multiarch -q -x jlink/jlink.gdb -tui"
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, you can simply use `cargo run --example blinky` to flash the blinky
|
|
||||||
example.
|
|
||||||
|
|
||||||
### Using the RTT Viewer
|
|
||||||
|
|
||||||
The Segger RTT viewer can be used to display log messages received from the target. The base
|
|
||||||
address for the RTT block placement is 0x1fff8000. It is recommended to use a search range of
|
|
||||||
0x1000 around that base address when using the RTT viewer.
|
|
||||||
|
|
||||||
The RTT viewer will not be able to process `defmt` printouts. However, you can view the defmt
|
|
||||||
logs by [installing defmt-print](https://crates.io/crates/defmt-print) first and then running
|
|
||||||
|
|
||||||
```sh
|
|
||||||
defmt-print -e <pathToElfFile> tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
The path of the ELF file which is being debugged needs to be specified for this to work.
|
|
||||||
|
|
||||||
## Learning (Embedded) Rust
|
|
||||||
|
|
||||||
If you are unfamiliar with Rust on Embedded Systems or Rust in general, the following resources
|
|
||||||
are recommended:
|
|
||||||
|
|
||||||
- [Rust Book](https://doc.rust-lang.org/book/)
|
|
||||||
- [Embedded Rust Book](https://docs.rust-embedded.org/book/)
|
|
||||||
- [Embedded Rust Discovery](https://docs.rust-embedded.org/discovery/microbit/)
|
|
||||||
- [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust)
|
|
||||||
|
8
automation/Jenkinsfile
vendored
8
automation/Jenkinsfile
vendored
@ -25,9 +25,7 @@ pipeline {
|
|||||||
stage('Docs') {
|
stage('Docs') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
sh """
|
||||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
|
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
|
||||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features va41630
|
|
||||||
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,9 +36,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Check Examples') {
|
stage('Check Examples') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
sh 'cargo check --target thumbv7em-none-eabihf --examples'
|
||||||
cargo check --target thumbv7em-none-eabihf --examples
|
|
||||||
"""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "bootloader"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = "0.7"
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
defmt-rtt = "0.4"
|
|
||||||
defmt = "1"
|
|
||||||
panic-probe = { version = "1", features = ["defmt"] }
|
|
||||||
crc = "3"
|
|
||||||
static_assertions = "1"
|
|
||||||
|
|
||||||
[dependencies.va416xx-hal]
|
|
||||||
version = "0.5"
|
|
||||||
features = ["va41630", "defmt"]
|
|
||||||
path = "../va416xx-hal"
|
|
@ -1,47 +0,0 @@
|
|||||||
VA416xx Bootloader Application
|
|
||||||
=======
|
|
||||||
|
|
||||||
This is the Rust version of the bootloader supplied by Vorago.
|
|
||||||
|
|
||||||
## Memory Map
|
|
||||||
|
|
||||||
The bootloader uses the following memory map:
|
|
||||||
|
|
||||||
| Address | Notes | Size |
|
|
||||||
| ------ | ---- | ---- |
|
|
||||||
| 0x0 | Bootloader start | code up to 0x3FFC bytes |
|
|
||||||
| 0x3FFC | Bootloader CRC | word |
|
|
||||||
| 0x4000 | App image A start | code up to 0x1DFF8 (~120K) bytes |
|
|
||||||
| 0x21FF8 | App image A CRC check length | word |
|
|
||||||
| 0x21FFC | App image A CRC check value | word |
|
|
||||||
| 0x22000 | App image B start | code up to 0x1DFF8 (~120K) bytes |
|
|
||||||
| 0x3FFF8 | App image B CRC check length | word |
|
|
||||||
| 0x3FFFC | App image B CRC check value | word |
|
|
||||||
| 0x40000 | End of NVM | end |
|
|
||||||
|
|
||||||
## Additional Information
|
|
||||||
|
|
||||||
As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
|
|
||||||
but does not scale that clock up. It also uses a word (4 bytes) instead of a half-word for the CRC
|
|
||||||
and uses the ISO 3309 CRC32 standard checksum.
|
|
||||||
|
|
||||||
This bootloader does not provide tools to flash the NVM memories by itself. Instead, you can use
|
|
||||||
the [flashloader](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
application to perform this task using a CCSDS interface via a UART.
|
|
||||||
|
|
||||||
The bootloader performs the following steps:
|
|
||||||
|
|
||||||
1. The application will calculate the checksum of itself if the bootloader CRC is blank (all zeroes
|
|
||||||
or all ones). If the CRC is not blank and the checksum check fails, it will immediately boot
|
|
||||||
application image A. Otherwise, it proceeds to the next step.
|
|
||||||
2. Check the checksum of App A. If that checksum is valid, it will boot App A. If not, it will
|
|
||||||
proceed to the next step.
|
|
||||||
3. Check the checksum of App B. If that checksum is valid, it will boot App B. If not, it will
|
|
||||||
boot App A as the fallback image.
|
|
||||||
|
|
||||||
You could adapt and combine this bootloader with a non-volatile memory to select a prefered app
|
|
||||||
image, which would be a first step towards an updatable flight software.
|
|
||||||
|
|
||||||
Please note that you *MUST* compile the application at slot A and slot B with an appropriate
|
|
||||||
`memory.x` file where the base address of the `FLASH` was adapted according to the base address
|
|
||||||
shown in the memory map above. The memory files to do this were provided in the `scripts` folder.
|
|
@ -1,347 +0,0 @@
|
|||||||
//! Vorago bootloader which can boot from two images.
|
|
||||||
//!
|
|
||||||
//! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
|
|
||||||
//! but does not scale that clock up.
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
use crc::{Crc, CRC_32_ISO_HDLC};
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use panic_probe as _;
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::{pll_setup_delay, ClkDivSel, ClkselSys, ClockConfigurator},
|
|
||||||
edac,
|
|
||||||
nvm::Nvm,
|
|
||||||
pac::{self, interrupt},
|
|
||||||
time::Hertz,
|
|
||||||
wdt::Wdt,
|
|
||||||
};
|
|
||||||
|
|
||||||
const EXTCLK_FREQ: u32 = 40_000_000;
|
|
||||||
const WITH_WDT: bool = false;
|
|
||||||
const WDT_FREQ_MS: u32 = 50;
|
|
||||||
const DEBUG_PRINTOUTS: bool = true;
|
|
||||||
|
|
||||||
// Dangerous option! An image with this option set to true will flash itself from RAM directly
|
|
||||||
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
|
|
||||||
// boot process. Please note that this will flash an image which will also always perform the
|
|
||||||
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash
|
|
||||||
// loader to boot a bootloader without this feature.
|
|
||||||
const FLASH_SELF: bool = false;
|
|
||||||
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
|
|
||||||
// the binary stays small enough.
|
|
||||||
const DEFMT_PRINTOUTS: bool = true;
|
|
||||||
|
|
||||||
// Important bootloader addresses and offsets, vector table information.
|
|
||||||
|
|
||||||
const NVM_SIZE: u32 = 0x40000;
|
|
||||||
|
|
||||||
const BOOTLOADER_START_ADDR: u32 = 0x0;
|
|
||||||
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 4;
|
|
||||||
const BOOTLOADER_END_ADDR: u32 = 0x4000;
|
|
||||||
|
|
||||||
// 0x4000
|
|
||||||
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
|
|
||||||
// The actual size of the image which is relevant for CRC calculation will be store at this
|
|
||||||
// address.
|
|
||||||
// 0x21FF8
|
|
||||||
const APP_A_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
|
|
||||||
// 0x21FFC
|
|
||||||
const APP_A_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
|
||||||
pub const APP_A_END_ADDR: u32 = BOOTLOADER_END_ADDR + APP_IMG_SZ;
|
|
||||||
|
|
||||||
// 0x22000
|
|
||||||
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
|
|
||||||
// The actual size of the image which is relevant for CRC calculation will be stored at this
|
|
||||||
// address.
|
|
||||||
// 0x3FFF8
|
|
||||||
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
|
|
||||||
// 0x3FFFC
|
|
||||||
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
|
|
||||||
// 0x40000
|
|
||||||
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
|
|
||||||
|
|
||||||
pub const APP_IMG_SZ: u32 = APP_B_END_ADDR - APP_A_START_ADDR / 2;
|
|
||||||
|
|
||||||
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
|
|
||||||
|
|
||||||
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
|
|
||||||
pub const VECTOR_TABLE_LEN: u32 = 0x350;
|
|
||||||
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
|
|
||||||
|
|
||||||
const CRC_ALGO: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, defmt::Format)]
|
|
||||||
enum AppSel {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait WdtInterface {
|
|
||||||
fn feed(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OptWdt(Option<Wdt>);
|
|
||||||
|
|
||||||
impl WdtInterface for OptWdt {
|
|
||||||
fn feed(&self) {
|
|
||||||
if self.0.is_some() {
|
|
||||||
self.0.as_ref().unwrap().feed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
if DEFMT_PRINTOUTS {
|
|
||||||
defmt::println!("-- VA416xx bootloader --");
|
|
||||||
}
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let cp = cortex_m::Peripherals::take().unwrap();
|
|
||||||
// Disable ROM protection.
|
|
||||||
dp.sysconfig.rom_prot().write(|w| unsafe { w.bits(1) });
|
|
||||||
setup_edac(&mut dp.sysconfig);
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
let mut opt_wdt = OptWdt(None);
|
|
||||||
if WITH_WDT {
|
|
||||||
opt_wdt.0 = Some(Wdt::start(dp.watch_dog, &clocks, WDT_FREQ_MS));
|
|
||||||
}
|
|
||||||
|
|
||||||
let nvm = Nvm::new(dp.spi3, &clocks);
|
|
||||||
|
|
||||||
if FLASH_SELF {
|
|
||||||
let mut first_four_bytes: [u8; 4] = [0; 4];
|
|
||||||
read_four_bytes_at_addr_zero(&mut first_four_bytes);
|
|
||||||
let bootloader_data = {
|
|
||||||
unsafe {
|
|
||||||
&*core::ptr::slice_from_raw_parts(
|
|
||||||
(BOOTLOADER_START_ADDR + 4) as *const u8,
|
|
||||||
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut digest = CRC_ALGO.digest();
|
|
||||||
digest.update(&first_four_bytes);
|
|
||||||
digest.update(bootloader_data);
|
|
||||||
let bootloader_crc = digest.finalize();
|
|
||||||
|
|
||||||
nvm.write_data(0x0, &first_four_bytes);
|
|
||||||
nvm.write_data(0x4, bootloader_data);
|
|
||||||
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
|
|
||||||
if DEFMT_PRINTOUTS {
|
|
||||||
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
|
|
||||||
if DEFMT_PRINTOUTS {
|
|
||||||
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes());
|
|
||||||
if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
|
|
||||||
if DEFMT_PRINTOUTS {
|
|
||||||
defmt::error!(
|
|
||||||
"error: CRC verification for bootloader self-flash failed: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check bootloader's CRC (and write it if blank)
|
|
||||||
check_own_crc(&opt_wdt, &nvm, &cp);
|
|
||||||
|
|
||||||
if check_app_crc(AppSel::A, &opt_wdt) {
|
|
||||||
boot_app(AppSel::A, &cp)
|
|
||||||
} else if check_app_crc(AppSel::B, &opt_wdt) {
|
|
||||||
boot_app(AppSel::B, &cp)
|
|
||||||
} else {
|
|
||||||
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
|
|
||||||
defmt::println!("both images corrupt! booting image A");
|
|
||||||
}
|
|
||||||
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
|
|
||||||
// Both images seem to be corrupt. Boot default image A.
|
|
||||||
boot_app(AppSel::A, &cp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
|
|
||||||
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u32).read_unaligned().to_be() };
|
|
||||||
wdt.feed();
|
|
||||||
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
|
|
||||||
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
|
|
||||||
// panics.
|
|
||||||
let mut first_four_bytes: [u8; 4] = [0; 4];
|
|
||||||
read_four_bytes_at_addr_zero(&mut first_four_bytes);
|
|
||||||
let mut digest = CRC_ALGO.digest();
|
|
||||||
digest.update(&first_four_bytes);
|
|
||||||
digest.update(unsafe {
|
|
||||||
&*core::ptr::slice_from_raw_parts(
|
|
||||||
(BOOTLOADER_START_ADDR + 4) as *const u8,
|
|
||||||
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let crc_calc = digest.finalize();
|
|
||||||
wdt.feed();
|
|
||||||
if crc_exp == 0x0000 || crc_exp == 0xffff {
|
|
||||||
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
|
|
||||||
defmt::info!("BL CRC blank - prog new CRC");
|
|
||||||
}
|
|
||||||
// Blank CRC, write it to NVM.
|
|
||||||
nvm.write_data(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes());
|
|
||||||
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
|
|
||||||
// necessary because somehow the boot will not work if we just continue as usual.
|
|
||||||
// cortex_m::peripheral::SCB::sys_reset();
|
|
||||||
} else if crc_exp != crc_calc {
|
|
||||||
// Bootloader is corrupted. Try to run App A.
|
|
||||||
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
|
|
||||||
defmt::info!(
|
|
||||||
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
|
|
||||||
crc_calc,
|
|
||||||
crc_exp
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
|
|
||||||
boot_app(AppSel::A, cp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
|
|
||||||
unsafe {
|
|
||||||
core::arch::asm!(
|
|
||||||
"ldr r0, [{0}]", // Load 4 bytes from src into r0 register
|
|
||||||
"str r0, [{1}]", // Store r0 register into first_four_bytes
|
|
||||||
in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0)
|
|
||||||
in(reg) buf as *mut [u8; 4], // Input: destination pointer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
|
|
||||||
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
|
|
||||||
defmt::info!("Checking image {:?}", app_sel);
|
|
||||||
}
|
|
||||||
if app_sel == AppSel::A {
|
|
||||||
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR, wdt)
|
|
||||||
} else {
|
|
||||||
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR, wdt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_app_given_addr(
|
|
||||||
crc_addr: u32,
|
|
||||||
start_addr: u32,
|
|
||||||
image_size_addr: u32,
|
|
||||||
wdt: &OptWdt,
|
|
||||||
) -> bool {
|
|
||||||
let crc_exp = unsafe { (crc_addr as *const u32).read_unaligned().to_be() };
|
|
||||||
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
|
|
||||||
// Sanity check.
|
|
||||||
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
|
|
||||||
if DEFMT_PRINTOUTS {
|
|
||||||
defmt::info!("detected invalid app size {}", image_size);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
wdt.feed();
|
|
||||||
let crc_calc = CRC_ALGO.checksum(unsafe {
|
|
||||||
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
|
|
||||||
});
|
|
||||||
wdt.feed();
|
|
||||||
if crc_calc == crc_exp {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
|
|
||||||
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
|
|
||||||
defmt::info!("booting app {:?}", app_sel);
|
|
||||||
}
|
|
||||||
let clkgen = unsafe { pac::Clkgen::steal() };
|
|
||||||
clkgen
|
|
||||||
.ctrl0()
|
|
||||||
.modify(|_, w| unsafe { w.clksel_sys().bits(ClkselSys::Hbo as u8) });
|
|
||||||
pll_setup_delay();
|
|
||||||
clkgen
|
|
||||||
.ctrl0()
|
|
||||||
.modify(|_, w| unsafe { w.clk_div_sel().bits(ClkDivSel::Div1 as u8) });
|
|
||||||
// Clear all interrupts set.
|
|
||||||
unsafe {
|
|
||||||
cp.NVIC.icer[0].write(0xFFFFFFFF);
|
|
||||||
cp.NVIC.icpr[0].write(0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
cortex_m::asm::dsb();
|
|
||||||
cortex_m::asm::isb();
|
|
||||||
unsafe {
|
|
||||||
if app_sel == AppSel::A {
|
|
||||||
cp.SCB.vtor.write(APP_A_START_ADDR);
|
|
||||||
} else {
|
|
||||||
cp.SCB.vtor.write(APP_B_START_ADDR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cortex_m::asm::dsb();
|
|
||||||
cortex_m::asm::isb();
|
|
||||||
vector_reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vector_reset() -> ! {
|
|
||||||
unsafe {
|
|
||||||
// Set R0 to VTOR address (0xE000ED08)
|
|
||||||
let vtor_address: u32 = 0xE000ED08;
|
|
||||||
|
|
||||||
// Load VTOR
|
|
||||||
let vtor: u32 = *(vtor_address as *const u32);
|
|
||||||
|
|
||||||
// Load initial MSP value
|
|
||||||
let initial_msp: u32 = *(vtor as *const u32);
|
|
||||||
|
|
||||||
// Set SP value (assume MSP is selected)
|
|
||||||
core::arch::asm!("mov sp, {0}", in(reg) initial_msp);
|
|
||||||
|
|
||||||
// Load reset vector
|
|
||||||
let reset_vector: u32 = *((vtor + 4) as *const u32);
|
|
||||||
|
|
||||||
// Branch to reset handler
|
|
||||||
core::arch::asm!("bx {0}", in(reg) reset_vector);
|
|
||||||
}
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_edac(syscfg: &mut pac::Sysconfig) {
|
|
||||||
// The scrub values are based on the Vorago provided bootloader.
|
|
||||||
edac::enable_rom_scrub(syscfg, 125);
|
|
||||||
edac::enable_ram0_scrub(syscfg, 1000);
|
|
||||||
edac::enable_ram1_scrub(syscfg, 1000);
|
|
||||||
edac::enable_sbe_irq();
|
|
||||||
edac::enable_mbe_irq();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn WATCHDOG() {
|
|
||||||
let wdt = unsafe { pac::WatchDog::steal() };
|
|
||||||
// Clear interrupt.
|
|
||||||
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn EDAC_SBE() {
|
|
||||||
// TODO: Send some command via UART for notification purposes. Also identify the problematic
|
|
||||||
// memory.
|
|
||||||
edac::clear_sbe_irq();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn EDAC_MBE() {
|
|
||||||
// TODO: Send some command via UART for notification purposes.
|
|
||||||
edac::clear_mbe_irq();
|
|
||||||
// TODO: Reset like the vorago example?
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
VA416xx Example Applications
|
|
||||||
========
|
|
||||||
|
|
||||||
This folder contains various examples
|
|
||||||
Consult the main README first for setup of the repository.
|
|
||||||
|
|
||||||
## Simple examples
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --example blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
You can have a look at the `simple/examples` folder to see all available simple examples
|
|
||||||
|
|
||||||
## RTIC example
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --bin rtic-example
|
|
||||||
```
|
|
||||||
|
|
||||||
## Embassy example
|
|
||||||
|
|
||||||
Blinky with time driver IRQs in library
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --bin embassy-example
|
|
||||||
```
|
|
||||||
|
|
||||||
Blinky with custom time driver IRQs
|
|
||||||
|
|
||||||
```rs
|
|
||||||
cargo run --bin embassy-example --no-default-features --features custom-irqs
|
|
||||||
```
|
|
@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "embassy-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = "0.7"
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
cfg-if = "1"
|
|
||||||
embedded-io = "0.6"
|
|
||||||
embedded-can = "0.4"
|
|
||||||
embedded-hal-async = "1"
|
|
||||||
embedded-io-async = "0.6"
|
|
||||||
|
|
||||||
heapless = "0.8"
|
|
||||||
defmt-rtt = "0.4"
|
|
||||||
defmt = "1"
|
|
||||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
|
||||||
static_cell = "2"
|
|
||||||
critical-section = "1"
|
|
||||||
ringbuf = { version = "0.4", default-features = false }
|
|
||||||
|
|
||||||
nb = "1"
|
|
||||||
embassy-sync = "0.6"
|
|
||||||
embassy-time = "0.4"
|
|
||||||
embassy-executor = { version = "0.7", features = [
|
|
||||||
"arch-cortex-m",
|
|
||||||
"executor-thread",
|
|
||||||
"executor-interrupt"
|
|
||||||
]}
|
|
||||||
|
|
||||||
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["defmt"] }
|
|
||||||
va416xx-embassy = { version = "0.1", path = "../../va416xx-embassy", default-features = false }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"]
|
|
||||||
custom-irqs = []
|
|
||||||
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
|
|
||||||
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]
|
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
|
||||||
ignored = ["cortex-m-rt"]
|
|
@ -1,353 +0,0 @@
|
|||||||
//! This example demonstrates the usage of async GPIO operations on VA416xx.
|
|
||||||
//!
|
|
||||||
//! You need to tie the PA0 to the PA1 pin for this example to work. You can optionally also tie
|
|
||||||
//! more pin combinations together and test other ports by setting the appropriate
|
|
||||||
//! [CHECK_XXX_TO_XXX] constants to true.
|
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use embassy_example::EXTCLK_FREQ;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_sync::channel::{Receiver, Sender};
|
|
||||||
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
|
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
|
||||||
use embedded_hal_async::digital::Wait;
|
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
|
||||||
use va416xx_hal::gpio::asynch::{on_interrupt_for_async_gpio_for_port, InputPinAsync};
|
|
||||||
use va416xx_hal::gpio::{Input, Output, PinState, Port};
|
|
||||||
use va416xx_hal::pac::{self, interrupt};
|
|
||||||
use va416xx_hal::pins::{PinsA, PinsB, PinsC, PinsD, PinsE, PinsF, PinsG};
|
|
||||||
use va416xx_hal::time::Hertz;
|
|
||||||
|
|
||||||
const CHECK_PA0_TO_PA1: bool = true;
|
|
||||||
const CHECK_PB0_TO_PB1: bool = false;
|
|
||||||
const CHECK_PC14_TO_PC15: bool = false;
|
|
||||||
const CHECK_PD2_TO_PD3: bool = false;
|
|
||||||
const CHECK_PE0_TO_PE1: bool = false;
|
|
||||||
const CHECK_PF0_TO_PF1: bool = false;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct GpioCmd {
|
|
||||||
cmd_type: GpioCmdType,
|
|
||||||
after_delay: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GpioCmd {
|
|
||||||
pub fn new(cmd_type: GpioCmdType, after_delay: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
cmd_type,
|
|
||||||
after_delay,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum GpioCmdType {
|
|
||||||
SetHigh,
|
|
||||||
SetLow,
|
|
||||||
RisingEdge,
|
|
||||||
FallingEdge,
|
|
||||||
CloseTask,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare a bounded channel of 3 u32s.
|
|
||||||
static CHANNEL_PA0_TO_PA1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
|
|
||||||
static CHANNEL_PB0_TO_PB1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
|
|
||||||
static CHANNEL_PC14_TO_PC15: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
|
|
||||||
static CHANNEL_PD2_TO_PD3: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
|
|
||||||
static CHANNEL_PE0_TO_PE1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
|
|
||||||
static CHANNEL_PF0_TO_PF1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
|
|
||||||
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(spawner: Spawner) {
|
|
||||||
defmt::println!("-- VA416xx Async GPIO Demo --");
|
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
|
|
||||||
|
|
||||||
let porta = PinsA::new(dp.porta);
|
|
||||||
let portb = PinsB::new(dp.portb);
|
|
||||||
let portc = PinsC::new(dp.portc);
|
|
||||||
let portd = PinsD::new(dp.portd);
|
|
||||||
let porte = PinsE::new(dp.porte);
|
|
||||||
let portf = PinsF::new(dp.portf);
|
|
||||||
|
|
||||||
let portg = PinsG::new(dp.portg);
|
|
||||||
let mut led = Output::new(portg.pg5, PinState::Low);
|
|
||||||
|
|
||||||
if CHECK_PA0_TO_PA1 {
|
|
||||||
let out_pin = Output::new(porta.pa0, PinState::Low);
|
|
||||||
let in_pin = Input::new_floating(porta.pa1);
|
|
||||||
let in_pin = InputPinAsync::new(in_pin).unwrap();
|
|
||||||
|
|
||||||
spawner
|
|
||||||
.spawn(output_task(
|
|
||||||
"PA0 to PA1",
|
|
||||||
out_pin,
|
|
||||||
CHANNEL_PA0_TO_PA1.receiver(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
check_pin_to_pin_async_ops("PA0 to PA1", CHANNEL_PA0_TO_PA1.sender(), in_pin).await;
|
|
||||||
defmt::info!("Example PA0 to PA1 done");
|
|
||||||
}
|
|
||||||
|
|
||||||
if CHECK_PB0_TO_PB1 {
|
|
||||||
let out_pin = Output::new(portb.pb0, PinState::Low);
|
|
||||||
let in_pin = Input::new_floating(portb.pb1);
|
|
||||||
let in_pin = InputPinAsync::new(in_pin).unwrap();
|
|
||||||
|
|
||||||
spawner
|
|
||||||
.spawn(output_task(
|
|
||||||
"PB0 to PB1",
|
|
||||||
out_pin,
|
|
||||||
CHANNEL_PB0_TO_PB1.receiver(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
check_pin_to_pin_async_ops("PB0 to PB1", CHANNEL_PB0_TO_PB1.sender(), in_pin).await;
|
|
||||||
defmt::info!("Example PB0 to PB1 done");
|
|
||||||
}
|
|
||||||
|
|
||||||
if CHECK_PC14_TO_PC15 {
|
|
||||||
let out_pin = Output::new(portc.pc14, PinState::Low);
|
|
||||||
let in_pin = Input::new_floating(portc.pc15);
|
|
||||||
let in_pin = InputPinAsync::new(in_pin).unwrap();
|
|
||||||
spawner
|
|
||||||
.spawn(output_task(
|
|
||||||
"PC14 to PC15",
|
|
||||||
out_pin,
|
|
||||||
CHANNEL_PC14_TO_PC15.receiver(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
check_pin_to_pin_async_ops("PC14 to PC15", CHANNEL_PC14_TO_PC15.sender(), in_pin).await;
|
|
||||||
defmt::info!("Example PC14 to PC15 done");
|
|
||||||
}
|
|
||||||
|
|
||||||
if CHECK_PD2_TO_PD3 {
|
|
||||||
let out_pin = Output::new(portd.pd2, PinState::Low);
|
|
||||||
let in_pin = Input::new_floating(portd.pd3);
|
|
||||||
let in_pin = InputPinAsync::new(in_pin).unwrap();
|
|
||||||
spawner
|
|
||||||
.spawn(output_task(
|
|
||||||
"PD2 to PD3",
|
|
||||||
out_pin,
|
|
||||||
CHANNEL_PD2_TO_PD3.receiver(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
check_pin_to_pin_async_ops("PD2 to PD3", CHANNEL_PD2_TO_PD3.sender(), in_pin).await;
|
|
||||||
defmt::info!("Example PD2 to PD3 done");
|
|
||||||
}
|
|
||||||
|
|
||||||
if CHECK_PE0_TO_PE1 {
|
|
||||||
let out_pin = Output::new(porte.pe0, PinState::Low);
|
|
||||||
let in_pin = Input::new_floating(porte.pe1);
|
|
||||||
let in_pin = InputPinAsync::new(in_pin).unwrap();
|
|
||||||
spawner
|
|
||||||
.spawn(output_task(
|
|
||||||
"PE0 to PE1",
|
|
||||||
out_pin,
|
|
||||||
CHANNEL_PE0_TO_PE1.receiver(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
check_pin_to_pin_async_ops("PE0 to PE1", CHANNEL_PE0_TO_PE1.sender(), in_pin).await;
|
|
||||||
defmt::info!("Example PE0 to PE1 done");
|
|
||||||
}
|
|
||||||
|
|
||||||
if CHECK_PF0_TO_PF1 {
|
|
||||||
let out_pin = Output::new(portf.pf0, PinState::Low);
|
|
||||||
let in_pin = Input::new_floating(portf.pf1);
|
|
||||||
let in_pin = InputPinAsync::new(in_pin).unwrap();
|
|
||||||
spawner
|
|
||||||
.spawn(output_task(
|
|
||||||
"PF0 to PF1",
|
|
||||||
out_pin,
|
|
||||||
CHANNEL_PF0_TO_PF1.receiver(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
check_pin_to_pin_async_ops("PF0 to PF1", CHANNEL_PF0_TO_PF1.sender(), in_pin).await;
|
|
||||||
defmt::info!("Example PF0 to PF1 done");
|
|
||||||
}
|
|
||||||
|
|
||||||
defmt::info!("Example done, toggling LED0");
|
|
||||||
loop {
|
|
||||||
led.toggle();
|
|
||||||
Timer::after(Duration::from_millis(500)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_pin_to_pin_async_ops(
|
|
||||||
ctx: &'static str,
|
|
||||||
sender: Sender<'static, ThreadModeRawMutex, GpioCmd, 3>,
|
|
||||||
mut async_input: impl Wait,
|
|
||||||
) {
|
|
||||||
defmt::info!(
|
|
||||||
"{}: sending SetHigh command ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender.send(GpioCmd::new(GpioCmdType::SetHigh, 20)).await;
|
|
||||||
async_input.wait_for_high().await.unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"{}: Input pin is high now ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
defmt::info!(
|
|
||||||
"{}: sending SetLow command ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender.send(GpioCmd::new(GpioCmdType::SetLow, 20)).await;
|
|
||||||
async_input.wait_for_low().await.unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"{}: Input pin is low now ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
defmt::info!(
|
|
||||||
"{}: sending RisingEdge command ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
|
|
||||||
async_input.wait_for_rising_edge().await.unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"{}: input pin had rising edge ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
defmt::info!(
|
|
||||||
"{}: sending Falling command ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender
|
|
||||||
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
|
|
||||||
.await;
|
|
||||||
async_input.wait_for_falling_edge().await.unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"{}: input pin had a falling edge ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
defmt::info!(
|
|
||||||
"{}: sending Falling command ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender
|
|
||||||
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
|
|
||||||
.await;
|
|
||||||
async_input.wait_for_any_edge().await.unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"{}: input pin had a falling (any) edge ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
defmt::info!(
|
|
||||||
"{}: sending Falling command ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
|
|
||||||
async_input.wait_for_any_edge().await.unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"{}: input pin had a rising (any) edge ({} ms)",
|
|
||||||
ctx,
|
|
||||||
Instant::now().as_millis()
|
|
||||||
);
|
|
||||||
sender.send(GpioCmd::new(GpioCmdType::CloseTask, 0)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task(pool_size = 8)]
|
|
||||||
async fn output_task(
|
|
||||||
ctx: &'static str,
|
|
||||||
mut out: Output,
|
|
||||||
receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
|
|
||||||
) {
|
|
||||||
loop {
|
|
||||||
let next_cmd = receiver.receive().await;
|
|
||||||
Timer::after(Duration::from_millis(next_cmd.after_delay.into())).await;
|
|
||||||
match next_cmd.cmd_type {
|
|
||||||
GpioCmdType::SetHigh => {
|
|
||||||
defmt::info!("{}: Set output high", ctx);
|
|
||||||
out.set_high();
|
|
||||||
}
|
|
||||||
GpioCmdType::SetLow => {
|
|
||||||
defmt::info!("{}: Set output low", ctx);
|
|
||||||
out.set_low();
|
|
||||||
}
|
|
||||||
GpioCmdType::RisingEdge => {
|
|
||||||
defmt::info!("{}: Rising edge", ctx);
|
|
||||||
if !out.is_set_low() {
|
|
||||||
out.set_low();
|
|
||||||
}
|
|
||||||
out.set_high();
|
|
||||||
}
|
|
||||||
GpioCmdType::FallingEdge => {
|
|
||||||
defmt::info!("{}: Falling edge", ctx);
|
|
||||||
if !out.is_set_high() {
|
|
||||||
out.set_high();
|
|
||||||
}
|
|
||||||
out.set_low();
|
|
||||||
}
|
|
||||||
GpioCmdType::CloseTask => {
|
|
||||||
defmt::info!("{}: Closing task", ctx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn PORTA1() {
|
|
||||||
on_interrupt_for_async_gpio_for_port(Port::A).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn PORTB1() {
|
|
||||||
on_interrupt_for_async_gpio_for_port(Port::B).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn PORTC15() {
|
|
||||||
on_interrupt_for_async_gpio_for_port(Port::C).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn PORTD3() {
|
|
||||||
on_interrupt_for_async_gpio_for_port(Port::D).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn PORTE1() {
|
|
||||||
on_interrupt_for_async_gpio_for_port(Port::E).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn PORTF1() {
|
|
||||||
on_interrupt_for_async_gpio_for_port(Port::F).unwrap();
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
//! Asynchronous UART reception example application.
|
|
||||||
//!
|
|
||||||
//! This application receives data on two UARTs permanently using a ring buffer.
|
|
||||||
//! The ring buffer are read them asynchronously.
|
|
||||||
//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board.
|
|
||||||
//!
|
|
||||||
//! Instructions:
|
|
||||||
//!
|
|
||||||
//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1.
|
|
||||||
//! 2. Connect to the serial interface by using an application like Putty or picocom. You can
|
|
||||||
//! type something in the terminal and check if the data is echoed back. You can also check the
|
|
||||||
//! RTT logs to see received data.
|
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use core::cell::RefCell;
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use critical_section::Mutex;
|
|
||||||
use embassy_example::EXTCLK_FREQ;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_time::Instant;
|
|
||||||
use embedded_io::Write;
|
|
||||||
use embedded_io_async::Read;
|
|
||||||
use heapless::spsc::{Producer, Queue};
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::ClockConfigurator,
|
|
||||||
gpio::{Output, PinState},
|
|
||||||
pac::{self, interrupt},
|
|
||||||
pins::PinsG,
|
|
||||||
prelude::*,
|
|
||||||
time::Hertz,
|
|
||||||
uart::{
|
|
||||||
self,
|
|
||||||
rx_asynch::{on_interrupt_rx, RxAsync},
|
|
||||||
Bank,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static QUEUE_UART_A: static_cell::ConstStaticCell<Queue<u8, 256>> =
|
|
||||||
static_cell::ConstStaticCell::new(Queue::new());
|
|
||||||
static PRODUCER_UART_A: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(RefCell::new(None));
|
|
||||||
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(_spawner: Spawner) {
|
|
||||||
defmt::println!("-- VA108xx Async UART RX Demo --");
|
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
|
|
||||||
|
|
||||||
let portg = PinsG::new(dp.portg);
|
|
||||||
let mut led = Output::new(portg.pg5, PinState::Low);
|
|
||||||
|
|
||||||
let uarta =
|
|
||||||
uart::Uart::new(dp.uart0, portg.pg0, portg.pg1, &clocks, 115200.Hz().into()).unwrap();
|
|
||||||
|
|
||||||
let (mut tx_uart_a, rx_uart_a) = uarta.split();
|
|
||||||
let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();
|
|
||||||
// Pass the producer to the interrupt handler.
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
*PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod_uart_a);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Add example for RxAsyncOverwriting using another UART.
|
|
||||||
let mut async_uart_rx = RxAsync::new(rx_uart_a, cons_uart_a);
|
|
||||||
let mut buf = [0u8; 256];
|
|
||||||
loop {
|
|
||||||
defmt::info!("Current time UART A: {}", Instant::now().as_secs());
|
|
||||||
led.toggle();
|
|
||||||
let read_bytes = async_uart_rx.read(&mut buf).await.unwrap();
|
|
||||||
let read_str = core::str::from_utf8(&buf[..read_bytes]).unwrap();
|
|
||||||
defmt::info!(
|
|
||||||
"Read {} bytes asynchronously on UART A: {:?}",
|
|
||||||
read_bytes,
|
|
||||||
read_str
|
|
||||||
);
|
|
||||||
tx_uart_a.write_all(read_str.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn UART0_RX() {
|
|
||||||
let mut prod =
|
|
||||||
critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap());
|
|
||||||
let errors = on_interrupt_rx(Bank::Uart0, &mut prod);
|
|
||||||
critical_section::with(|cs| *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod));
|
|
||||||
// In a production app, we could use a channel to send the errors to the main task.
|
|
||||||
if let Err(errors) = errors {
|
|
||||||
defmt::info!("UART A errors: {:?}", errors);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
//! Asynchronous UART transmission example application.
|
|
||||||
//!
|
|
||||||
//! This application receives sends 4 strings with different sizes permanently.
|
|
||||||
//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board.
|
|
||||||
//!
|
|
||||||
//! Instructions:
|
|
||||||
//!
|
|
||||||
//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1.
|
|
||||||
//! 2. Connect to the serial interface by using an application like Putty or picocom. You can
|
|
||||||
//! type something in the terminal and check if the data is echoed back. You can also check the
|
|
||||||
//! RTT logs to see received data.
|
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use embassy_example::EXTCLK_FREQ;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_time::{Duration, Instant, Ticker};
|
|
||||||
use embedded_io_async::Write;
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::ClockConfigurator,
|
|
||||||
gpio::{Output, PinState},
|
|
||||||
pac::{self, interrupt},
|
|
||||||
pins::PinsG,
|
|
||||||
prelude::*,
|
|
||||||
time::Hertz,
|
|
||||||
uart::{
|
|
||||||
self,
|
|
||||||
tx_asynch::{on_interrupt_tx, TxAsync},
|
|
||||||
Bank,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const STR_LIST: &[&str] = &[
|
|
||||||
"Hello World\r\n",
|
|
||||||
"Smoll\r\n",
|
|
||||||
"A string which is larger than the FIFO size\r\n",
|
|
||||||
"A really large string which is significantly larger than the FIFO size\r\n",
|
|
||||||
];
|
|
||||||
|
|
||||||
// main is itself an async function.
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(_spawner: Spawner) {
|
|
||||||
defmt::println!("-- VA108xx Async UART TX Demo --");
|
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
|
|
||||||
|
|
||||||
let pinsg = PinsG::new(dp.portg);
|
|
||||||
let mut led = Output::new(pinsg.pg5, PinState::Low);
|
|
||||||
|
|
||||||
let uarta =
|
|
||||||
uart::Uart::new(dp.uart0, pinsg.pg0, pinsg.pg1, &clocks, 115200.Hz().into()).unwrap();
|
|
||||||
let (tx, _rx) = uarta.split();
|
|
||||||
let mut async_tx = TxAsync::new(tx);
|
|
||||||
let mut ticker = Ticker::every(Duration::from_secs(1));
|
|
||||||
let mut idx = 0;
|
|
||||||
loop {
|
|
||||||
defmt::println!("Current time: {}", Instant::now().as_secs());
|
|
||||||
led.toggle();
|
|
||||||
let _written = async_tx
|
|
||||||
.write(STR_LIST[idx].as_bytes())
|
|
||||||
.await
|
|
||||||
.expect("writing failed");
|
|
||||||
idx += 1;
|
|
||||||
if idx == STR_LIST.len() {
|
|
||||||
idx = 0;
|
|
||||||
}
|
|
||||||
ticker.next().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn UART0_TX() {
|
|
||||||
on_interrupt_tx(Bank::Uart0);
|
|
||||||
}
|
|
@ -1,239 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use embassy_example::EXTCLK_FREQ;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use va416xx_hal::can::asynch::{on_interrupt_can, CanTxAsync};
|
|
||||||
use va416xx_hal::can::{
|
|
||||||
Can, CanFrame, CanFrameNormal, CanFrameRtr, CanId, CanRx, CanTx, ClockConfig,
|
|
||||||
};
|
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
|
||||||
use va416xx_hal::pac::{self, interrupt};
|
|
||||||
use va416xx_hal::time::Hertz;
|
|
||||||
use va416xx_hal::{can, prelude::*};
|
|
||||||
|
|
||||||
const STANDARD_ID_0: can::StandardId = can::StandardId::new(0x42).unwrap();
|
|
||||||
const STANDARD_ID_1: can::StandardId = can::StandardId::new(0x5).unwrap();
|
|
||||||
const EXTENDED_ID_0: can::ExtendedId = can::ExtendedId::new(0x10).unwrap();
|
|
||||||
|
|
||||||
// Declare a bounded channel of 3 u32s.
|
|
||||||
static CAN_RX_CHANNEL: embassy_sync::channel::Channel<
|
|
||||||
CriticalSectionRawMutex,
|
|
||||||
(usize, CanFrame),
|
|
||||||
3,
|
|
||||||
> = embassy_sync::channel::Channel::<CriticalSectionRawMutex, (usize, CanFrame), 3>::new();
|
|
||||||
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(_spawner: Spawner) {
|
|
||||||
defmt::println!("-- VA416xx CAN Demo --");
|
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
|
|
||||||
defmt::info!("creating CAN peripheral driver");
|
|
||||||
defmt::info!("clocks: {}", clocks);
|
|
||||||
let clk_config = ClockConfig::from_bitrate_and_segments(&clocks, 250.kHz(), 14, 5, 4)
|
|
||||||
.expect("CAN clock config error");
|
|
||||||
let mut can = Can::new(dp.can0, clk_config);
|
|
||||||
can.modify_control(|mut val| {
|
|
||||||
val.set_loopback(true);
|
|
||||||
val.set_ignore_ack(true);
|
|
||||||
val.set_internal(true);
|
|
||||||
val.set_bufflock(true);
|
|
||||||
val.set_diag_enable(true);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
can.set_global_mask_for_exact_id_match_with_rtr_masked();
|
|
||||||
can.set_base_mask_for_all_match();
|
|
||||||
can.enable();
|
|
||||||
let mut channels = can.take_channels().unwrap();
|
|
||||||
// Transmit channel.
|
|
||||||
let mut tx = CanTx::new(channels.take(0).unwrap(), None);
|
|
||||||
// Base channel which has dedicated mask.
|
|
||||||
let mut rx_dedicated = CanRx::new(channels.take(1).unwrap());
|
|
||||||
// Base channel which has dedicated mask.
|
|
||||||
let mut rx_base = CanRx::new(channels.take(14).unwrap());
|
|
||||||
rx_base.configure_for_reception();
|
|
||||||
|
|
||||||
defmt::info!("Running blocking examples");
|
|
||||||
|
|
||||||
send_and_receive_on_dedicated_channel(&mut can, &mut tx, &mut rx_dedicated);
|
|
||||||
send_and_receive_rtr_on_dedicated_channel(&mut can, &mut tx, &mut rx_dedicated);
|
|
||||||
send_extended_on_base_channel(&mut can, &mut tx, &mut rx_base);
|
|
||||||
|
|
||||||
defmt::info!("Running non-blocking (asycnhronous) examples");
|
|
||||||
|
|
||||||
non_blocking_example(&mut can, &mut rx_dedicated, &mut rx_base).await;
|
|
||||||
|
|
||||||
defmt::info!("Non-blocking (asycnhronous) examples done");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_and_receive_on_dedicated_channel(can: &mut Can, tx: &mut CanTx, rx_dedicated: &mut CanRx) {
|
|
||||||
let send_data = &[1, 2, 3, 4];
|
|
||||||
let sent_frame =
|
|
||||||
CanFrame::Normal(CanFrameNormal::new(can::Id::Standard(STANDARD_ID_0), send_data).unwrap());
|
|
||||||
defmt::info!(
|
|
||||||
"sending CAN frame with ID {:#X} and data {}",
|
|
||||||
STANDARD_ID_0.as_raw(),
|
|
||||||
send_data
|
|
||||||
);
|
|
||||||
rx_dedicated.configure_for_reception_with_standard_id(STANDARD_ID_0, false);
|
|
||||||
tx.transmit_frame(sent_frame).unwrap();
|
|
||||||
// Await frame transmission completion.
|
|
||||||
nb::block!(tx.transfer_done()).unwrap();
|
|
||||||
check_and_handle_errors(can);
|
|
||||||
let received_frame = nb::block!(rx_dedicated.receive(true)).expect("invalid CAN rx state");
|
|
||||||
check_and_handle_errors(can);
|
|
||||||
assert_eq!(received_frame, sent_frame);
|
|
||||||
if let CanFrame::Normal(can_frame_normal) = received_frame {
|
|
||||||
if let can::Id::Standard(standard_id) = can_frame_normal.id() {
|
|
||||||
defmt::info!(
|
|
||||||
"received CAN frame with ID {:#X} and data {}",
|
|
||||||
standard_id.as_raw(),
|
|
||||||
can_frame_normal.data()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected CAN extended frame ID");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defmt::error!("received unexpected CAN remote frame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_and_receive_rtr_on_dedicated_channel(
|
|
||||||
can: &mut Can,
|
|
||||||
tx: &mut CanTx,
|
|
||||||
rx_dedicated: &mut CanRx,
|
|
||||||
) {
|
|
||||||
let rtr_frame = CanFrame::Rtr(CanFrameRtr::new(can::Id::Standard(STANDARD_ID_1), 0));
|
|
||||||
// RTR bit is masked, so the setting should not matter.
|
|
||||||
rx_dedicated.configure_for_reception_with_standard_id(STANDARD_ID_1, false);
|
|
||||||
tx.transmit_frame(rtr_frame).unwrap();
|
|
||||||
// Await frame transmission completion.
|
|
||||||
nb::block!(tx.remote_transfer_done_with_tx_reconfig()).unwrap();
|
|
||||||
check_and_handle_errors(can);
|
|
||||||
let received_frame = nb::block!(rx_dedicated.receive(true)).expect("invalid CAN rx state");
|
|
||||||
check_and_handle_errors(can);
|
|
||||||
assert_eq!(received_frame, rtr_frame);
|
|
||||||
if let CanFrame::Rtr(can_frame_rtr) = received_frame {
|
|
||||||
if let can::Id::Standard(standard_id) = can_frame_rtr.id() {
|
|
||||||
defmt::info!("received CAN RTR frame with ID {:#X}", standard_id.as_raw(),);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected CAN extended frame ID");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defmt::error!("received unexpected CAN data frame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_and_handle_errors(can: &mut Can) {
|
|
||||||
let err_counter = can.read_error_counters();
|
|
||||||
if err_counter.transmit() > 0 || err_counter.receive() > 0 {
|
|
||||||
defmt::warn!(
|
|
||||||
"error count tx {}, error count rx {}",
|
|
||||||
err_counter.transmit(),
|
|
||||||
err_counter.receive()
|
|
||||||
);
|
|
||||||
let diag = can.read_error_diagnostics();
|
|
||||||
defmt::warn!("EFID: {}, EBID: {}", diag.efid(), diag.ebid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_extended_on_base_channel(can: &mut Can, tx: &mut CanTx, rx: &mut CanRx) {
|
|
||||||
let send_data = &[4, 3, 2, 1];
|
|
||||||
let sent_frame =
|
|
||||||
CanFrame::Normal(CanFrameNormal::new(can::Id::Extended(EXTENDED_ID_0), send_data).unwrap());
|
|
||||||
tx.transmit_frame(sent_frame).unwrap();
|
|
||||||
// Await frame transmission completion.
|
|
||||||
nb::block!(tx.transfer_done()).unwrap();
|
|
||||||
check_and_handle_errors(can);
|
|
||||||
let received_frame = nb::block!(rx.receive(true)).expect("invalid CAN rx state");
|
|
||||||
check_and_handle_errors(can);
|
|
||||||
assert_eq!(sent_frame, received_frame);
|
|
||||||
if let CanFrame::Normal(can_frame_normal) = received_frame {
|
|
||||||
if let can::Id::Extended(extended_id) = can_frame_normal.id() {
|
|
||||||
defmt::info!(
|
|
||||||
"received CAN frame with ID {:#X} and data {}",
|
|
||||||
extended_id.as_raw(),
|
|
||||||
can_frame_normal.data()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected CAN extended frame ID");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defmt::error!("received unexpected CAN data frame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn non_blocking_example(can: &mut Can, rx_dedicated: &mut CanRx, rx_base: &mut CanRx) {
|
|
||||||
let mut tx_async = CanTxAsync::new(can);
|
|
||||||
// Enable interrupts for RX channels.
|
|
||||||
rx_dedicated.enable_interrupt(true);
|
|
||||||
rx_base.enable_interrupt(true);
|
|
||||||
|
|
||||||
// For asynchronous mode, all TX channels needs to be configured explicitely. Configuring more
|
|
||||||
// channels allows multiple active transfers when using the async API.
|
|
||||||
tx_async.configure_channel(0).unwrap();
|
|
||||||
let send_data = &[1, 2, 3, 4];
|
|
||||||
let send_frame =
|
|
||||||
CanFrame::Normal(CanFrameNormal::new(can::Id::Standard(STANDARD_ID_0), send_data).unwrap());
|
|
||||||
let fut = tx_async.start_transmit(send_frame).unwrap();
|
|
||||||
fut.await;
|
|
||||||
let (ch_idx, frame) = CAN_RX_CHANNEL.receive().await;
|
|
||||||
assert_eq!(send_frame, frame);
|
|
||||||
// Received on base channel.
|
|
||||||
assert_eq!(ch_idx, 14);
|
|
||||||
if let CanFrame::Normal(can_frame_normal) = frame {
|
|
||||||
if let can::Id::Standard(standard_id) = can_frame_normal.id() {
|
|
||||||
defmt::info!(
|
|
||||||
"received CAN frame with ID {:#X} and data {}",
|
|
||||||
standard_id.as_raw(),
|
|
||||||
can_frame_normal.data()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
panic!("unexpected CAN extended frame ID");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defmt::error!("received unexpected CAN remote frame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn CAN0() {
|
|
||||||
match on_interrupt_can(CanId::Can0, false).unwrap() {
|
|
||||||
can::asynch::InterruptResult::NoInterrupt => {
|
|
||||||
defmt::warn!("unexpected interrupt on CAN0");
|
|
||||||
}
|
|
||||||
can::asynch::InterruptResult::ReceivedFrame {
|
|
||||||
channel_index,
|
|
||||||
frame,
|
|
||||||
} => {
|
|
||||||
CAN_RX_CHANNEL.try_send((channel_index, frame)).unwrap();
|
|
||||||
}
|
|
||||||
can::asynch::InterruptResult::TransmissionEvent { channel_index, id } => {
|
|
||||||
defmt::info!(
|
|
||||||
"transmission event on channel {} with event ID {}",
|
|
||||||
channel_index,
|
|
||||||
id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
//! This is an example of using the UART HAL abstraction with the IRQ support and embassy.
|
|
||||||
//!
|
|
||||||
//! It uses the UART0 for communication with another MCU or a host computer (recommended).
|
|
||||||
//! You can connect a USB-to-Serial converter to the UART0 pins and then use a serial terminal
|
|
||||||
//! application like picocom to send data to the microcontroller, which should be echoed
|
|
||||||
//! back to the sender.
|
|
||||||
//!
|
|
||||||
//! This application uses the interrupt support of the VA416xx to read the data arriving
|
|
||||||
//! on the UART without requiring polling.
|
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use core::cell::RefCell;
|
|
||||||
|
|
||||||
use embassy_example::EXTCLK_FREQ;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
||||||
use embassy_sync::blocking_mutex::Mutex;
|
|
||||||
use embassy_time::{Duration, Ticker};
|
|
||||||
use embedded_io::Write;
|
|
||||||
use ringbuf::{
|
|
||||||
traits::{Consumer, Observer, Producer},
|
|
||||||
StaticRb,
|
|
||||||
};
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::ClockConfigurator,
|
|
||||||
gpio::{Output, PinState},
|
|
||||||
pac::{self, interrupt},
|
|
||||||
pins::PinsG,
|
|
||||||
time::Hertz,
|
|
||||||
uart,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type SharedUart = Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt>>>;
|
|
||||||
static RX: SharedUart = Mutex::new(RefCell::new(None));
|
|
||||||
|
|
||||||
const BAUDRATE: u32 = 115200;
|
|
||||||
|
|
||||||
// Ring buffer size.
|
|
||||||
const RING_BUF_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
pub type SharedRingBuf =
|
|
||||||
Mutex<CriticalSectionRawMutex, RefCell<Option<StaticRb<u8, RING_BUF_SIZE>>>>;
|
|
||||||
// Ring buffers to handling variable sized telemetry
|
|
||||||
static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None));
|
|
||||||
|
|
||||||
// See https://embassy.dev/book/#_sharing_using_a_mutex for background information about sharing
|
|
||||||
// a peripheral with embassy.
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(spawner: Spawner) {
|
|
||||||
defmt::println!("VA416xx UART-Embassy Example");
|
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
|
|
||||||
|
|
||||||
let portg = PinsG::new(dp.portg);
|
|
||||||
|
|
||||||
let uart0 = uart::Uart::new(
|
|
||||||
dp.uart0,
|
|
||||||
portg.pg0,
|
|
||||||
portg.pg1,
|
|
||||||
&clocks,
|
|
||||||
Hertz::from_raw(BAUDRATE).into(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let (mut tx, rx) = uart0.split();
|
|
||||||
let mut rx = rx.into_rx_with_irq();
|
|
||||||
rx.start();
|
|
||||||
RX.lock(|static_rx| {
|
|
||||||
static_rx.borrow_mut().replace(rx);
|
|
||||||
});
|
|
||||||
RINGBUF.lock(|static_rb| {
|
|
||||||
static_rb.borrow_mut().replace(StaticRb::default());
|
|
||||||
});
|
|
||||||
|
|
||||||
let led = Output::new(portg.pg5, PinState::Low);
|
|
||||||
let mut ticker = Ticker::every(Duration::from_millis(50));
|
|
||||||
let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE];
|
|
||||||
let mut read_bytes = 0;
|
|
||||||
spawner.spawn(blinky(led)).expect("failed to spawn blinky");
|
|
||||||
loop {
|
|
||||||
RINGBUF.lock(|static_rb| {
|
|
||||||
let mut rb_borrow = static_rb.borrow_mut();
|
|
||||||
let rb_mut = rb_borrow.as_mut().unwrap();
|
|
||||||
read_bytes = rb_mut.occupied_len();
|
|
||||||
rb_mut.pop_slice(&mut processing_buf[0..read_bytes]);
|
|
||||||
});
|
|
||||||
// Simply send back all received data.
|
|
||||||
tx.write_all(&processing_buf[0..read_bytes])
|
|
||||||
.expect("sending back read data failed");
|
|
||||||
ticker.next().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn blinky(mut led: Output) {
|
|
||||||
let mut ticker = Ticker::every(Duration::from_millis(500));
|
|
||||||
loop {
|
|
||||||
led.toggle();
|
|
||||||
ticker.next().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn UART0_RX() {
|
|
||||||
let mut buf: [u8; 16] = [0; 16];
|
|
||||||
let mut read_len: usize = 0;
|
|
||||||
let mut errors = None;
|
|
||||||
RX.lock(|static_rx| {
|
|
||||||
let mut rx_borrow = static_rx.borrow_mut();
|
|
||||||
let rx_mut_ref = rx_borrow.as_mut().unwrap();
|
|
||||||
let result = rx_mut_ref.on_interrupt(&mut buf);
|
|
||||||
read_len = result.bytes_read;
|
|
||||||
if result.errors.is_some() {
|
|
||||||
errors = result.errors;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let mut ringbuf_full = false;
|
|
||||||
if read_len > 0 {
|
|
||||||
// Send the received buffer to the main thread for processing via a ring buffer.
|
|
||||||
RINGBUF.lock(|static_rb| {
|
|
||||||
let mut rb_borrow = static_rb.borrow_mut();
|
|
||||||
let rb_mut_ref = rb_borrow.as_mut().unwrap();
|
|
||||||
if rb_mut_ref.vacant_len() < read_len {
|
|
||||||
ringbuf_full = true;
|
|
||||||
for _ in rb_mut_ref.pop_iter() {}
|
|
||||||
}
|
|
||||||
rb_mut_ref.push_slice(&buf[0..read_len]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.is_some() {
|
|
||||||
defmt::info!("UART error: {:?}", errors);
|
|
||||||
}
|
|
||||||
if ringbuf_full {
|
|
||||||
defmt::info!("ringbuffer is full, deleted oldest data");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
pub const EXTCLK_FREQ: u32 = 40_000_000;
|
|
@ -1,63 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use embassy_example::EXTCLK_FREQ;
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_time::{Duration, Instant, Ticker};
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::ClockConfigurator,
|
|
||||||
gpio::{Output, PinState},
|
|
||||||
pac,
|
|
||||||
pins::PinsG,
|
|
||||||
time::Hertz,
|
|
||||||
};
|
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(feature = "custom-irqs")] {
|
|
||||||
use va416xx_hal::pac::interrupt;
|
|
||||||
va416xx_embassy::embassy_time_driver_irqs!(timekeeper_irq = TIM12, alarm_irq = TIM11);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// main is itself an async function.
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(_spawner: Spawner) {
|
|
||||||
defmt::println!("VA416xx Embassy Demo");
|
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
// Safety: Only called once here.
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(not(feature = "custom-irqs"))] {
|
|
||||||
va416xx_embassy::init(
|
|
||||||
dp.tim15,
|
|
||||||
dp.tim14,
|
|
||||||
&clocks
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
va416xx_embassy::init(
|
|
||||||
dp.tim12,
|
|
||||||
dp.tim11,
|
|
||||||
&clocks
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let pinsg = PinsG::new(dp.portg);
|
|
||||||
let mut led = Output::new(pinsg.pg5, PinState::Low);
|
|
||||||
let mut ticker = Ticker::every(Duration::from_secs(1));
|
|
||||||
loop {
|
|
||||||
ticker.next().await;
|
|
||||||
defmt::info!("Current time: {}", Instant::now().as_secs());
|
|
||||||
led.toggle();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "rtic-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
defmt-rtt = "0.4"
|
|
||||||
defmt = "1"
|
|
||||||
panic-probe = { version = "1", features = ["defmt"] }
|
|
||||||
|
|
||||||
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630"] }
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
|
||||||
version = "2"
|
|
||||||
features = ["thumbv7-backend"]
|
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "2"
|
|
||||||
features = ["cortex-m-systick"]
|
|
@ -1,31 +0,0 @@
|
|||||||
//! Empty RTIC project template
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[rtic::app(device = pac)]
|
|
||||||
mod app {
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use va416xx_hal::pac;
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {}
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(_ctx: init::Context) -> (Shared, Local) {
|
|
||||||
defmt::println!("-- Vorago RTIC template --");
|
|
||||||
(Shared {}, Local {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
#[allow(clippy::empty_loop)]
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
//! RTIC minimal blinky
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use va416xx_hal::time::Hertz;
|
|
||||||
|
|
||||||
const EXTCLK_FREQ: Hertz = Hertz::from_raw(40_000_000);
|
|
||||||
|
|
||||||
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
|
|
||||||
mod app {
|
|
||||||
use super::*;
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use cortex_m::asm;
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use rtic_monotonics::systick::prelude::*;
|
|
||||||
use rtic_monotonics::Monotonic;
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::ClockConfigurator,
|
|
||||||
gpio::{Output, PinState},
|
|
||||||
pac,
|
|
||||||
pins::PinsG,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {
|
|
||||||
led: Output,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {}
|
|
||||||
|
|
||||||
rtic_monotonics::systick_monotonic!(Mono, 1_000);
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(cx: init::Context) -> (Shared, Local) {
|
|
||||||
defmt::println!("-- Vorago RTIC example application --");
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(cx.device.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(EXTCLK_FREQ)
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
Mono::start(cx.core.SYST, clocks.sysclk().raw());
|
|
||||||
let pinsg = PinsG::new(cx.device.portg);
|
|
||||||
let led = Output::new(pinsg.pg5, PinState::Low);
|
|
||||||
blinky::spawn().ok();
|
|
||||||
(Shared {}, Local { led })
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
loop {
|
|
||||||
asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
priority = 3,
|
|
||||||
local=[led],
|
|
||||||
)]
|
|
||||||
async fn blinky(cx: blinky::Context) {
|
|
||||||
loop {
|
|
||||||
cx.local.led.toggle();
|
|
||||||
Mono::delay(200.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,40 +4,15 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
critical-section = "1"
|
va416xx-hal = { path = "../../va416xx-hal" }
|
||||||
defmt-rtt = "0.4"
|
panic-rtt-target = { version = "0.1.3" }
|
||||||
defmt = "1"
|
rtt-target = { version = "0.5" }
|
||||||
panic-probe = { version = "1", features = ["defmt"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-hal-nb = "1"
|
embedded-hal-nb = "1"
|
||||||
nb = "1"
|
nb = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
panic-halt = "1"
|
panic-halt = "0.2"
|
||||||
|
vorago-peb1 = { path = "../../vorago-peb1" }
|
||||||
accelerometer = "0.12"
|
accelerometer = "0.12"
|
||||||
|
|
||||||
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630", "defmt"] }
|
|
||||||
|
|
||||||
[dependencies.vorago-peb1]
|
|
||||||
path = "../../vorago-peb1"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["va41630"]
|
|
||||||
va41630 = ["va416xx-hal/va41630", "has-adc-dac"]
|
|
||||||
va41629 = ["va416xx-hal/va41629", "has-adc-dac"]
|
|
||||||
va41628 = ["va416xx-hal/va41628"]
|
|
||||||
has-adc-dac = []
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "peb1-accelerometer"
|
|
||||||
required-features = ["vorago-peb1"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "dac-adc"
|
|
||||||
required-features = ["has-adc-dac"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "adc"
|
|
||||||
required-features = ["has-adc-dac"]
|
|
||||||
|
@ -2,18 +2,15 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect},
|
adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect},
|
||||||
clock::ClockConfigurator,
|
|
||||||
pac,
|
pac,
|
||||||
|
prelude::*,
|
||||||
timer::CountdownTimer,
|
timer::CountdownTimer,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,32 +19,32 @@ const ENABLE_BUF_PRINTOUT: bool = false;
|
|||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("VA416xx ADC example");
|
rtt_init_print!();
|
||||||
|
rprintln!("VA416xx ADC example");
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let adc = Adc::new_with_channel_tag(dp.adc, &clocks);
|
let adc = Adc::new_with_channel_tag(&mut dp.sysconfig, dp.adc, &clocks);
|
||||||
let mut delay = CountdownTimer::new(dp.tim0, &clocks);
|
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
|
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
|
||||||
loop {
|
loop {
|
||||||
let single_value = adc
|
let single_value = adc
|
||||||
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::TempSensor)
|
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
|
||||||
.expect("reading single channel value failed");
|
.expect("reading single channel value failed");
|
||||||
defmt::info!(
|
rprintln!("Read single ADC value on channel 0: {:?}", single_value);
|
||||||
"Read single ADC value on temperature sensor channel: {:?}",
|
|
||||||
single_value
|
|
||||||
);
|
|
||||||
let read_num = adc
|
let read_num = adc
|
||||||
.sweep_and_read_range(0, 7, &mut read_buf)
|
.sweep_and_read_range(0, 7, &mut read_buf)
|
||||||
.expect("ADC range read failed");
|
.expect("ADC range read failed");
|
||||||
if ENABLE_BUF_PRINTOUT {
|
if ENABLE_BUF_PRINTOUT {
|
||||||
defmt::info!("ADC Range Read (0-8) read {} values", read_num);
|
rprintln!("ADC Range Read (0-8) read {} values", read_num);
|
||||||
defmt::info!("ADC Range Read (0-8): {:?}", read_buf);
|
rprintln!("ADC Range Read (0-8): {:?}", read_buf);
|
||||||
}
|
}
|
||||||
assert_eq!(read_num, 8);
|
assert_eq!(read_num, 8);
|
||||||
for (idx, ch_val) in read_buf.iter().enumerate() {
|
for (idx, ch_val) in read_buf.iter().enumerate() {
|
||||||
@ -63,11 +60,11 @@ fn main() -> ! {
|
|||||||
)
|
)
|
||||||
.expect("ADC multiselect read failed");
|
.expect("ADC multiselect read failed");
|
||||||
if ENABLE_BUF_PRINTOUT {
|
if ENABLE_BUF_PRINTOUT {
|
||||||
defmt::info!("ADC Multiselect Read(0, 2 and 10): {:?}", &read_buf[0..3]);
|
rprintln!("ADC Multiselect Read(0, 2 and 10): {:?}", &read_buf[0..3]);
|
||||||
}
|
}
|
||||||
assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0);
|
assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0);
|
||||||
assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2);
|
assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2);
|
||||||
assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor);
|
assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor);
|
||||||
delay.delay_ms(500);
|
delay_provider.delay_ms(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,27 +2,22 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use va416xx_hal::{
|
use embedded_hal::digital::StatefulOutputPin;
|
||||||
gpio::{Output, PinState},
|
use panic_rtt_target as _;
|
||||||
pac,
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
pins::PinsG,
|
use va416xx_hal::{gpio::PinsG, pac};
|
||||||
};
|
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("VA416xx HAL blinky example");
|
rtt_init_print!();
|
||||||
|
rprintln!("VA416xx HAL blinky example");
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
let portg = PinsG::new(dp.portg);
|
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
||||||
let mut led = Output::new(portg.pg5, PinState::Low);
|
let mut led = portg.pg5.into_readable_push_pull_output();
|
||||||
loop {
|
loop {
|
||||||
cortex_m::asm::delay(2_000_000);
|
cortex_m::asm::delay(2_000_000);
|
||||||
led.toggle();
|
led.toggle().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::{adc::Adc, clock::ClockConfigurator, dac::Dac, pac, timer::CountdownTimer};
|
use va416xx_hal::{adc::Adc, dac::Dac, pac, prelude::*, timer::CountdownTimer};
|
||||||
|
|
||||||
const DAC_INCREMENT: u16 = 256;
|
const DAC_INCREMENT: u16 = 256;
|
||||||
|
|
||||||
@ -28,17 +25,21 @@ const APP_MODE: AppMode = AppMode::DacAndAdc;
|
|||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("VA416xx DAC/ADC example");
|
rtt_init_print!();
|
||||||
|
rprintln!("VA416xx DAC/ADC example");
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut dac = None;
|
let mut dac = None;
|
||||||
if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc {
|
if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc {
|
||||||
dac = Some(Dac::new(
|
dac = Some(Dac::new(
|
||||||
|
&mut dp.sysconfig,
|
||||||
dp.dac0,
|
dp.dac0,
|
||||||
va416xx_hal::dac::DacSettling::Apb2Times100,
|
va416xx_hal::dac::DacSettling::Apb2Times100,
|
||||||
&clocks,
|
&clocks,
|
||||||
@ -46,13 +47,13 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
let mut adc = None;
|
let mut adc = None;
|
||||||
if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc {
|
if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc {
|
||||||
adc = Some(Adc::new(dp.adc, &clocks));
|
adc = Some(Adc::new(&mut dp.sysconfig, dp.adc, &clocks));
|
||||||
}
|
}
|
||||||
let mut delay_provider = CountdownTimer::new(dp.tim0, &clocks);
|
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut current_val = 0;
|
let mut current_val = 0;
|
||||||
loop {
|
loop {
|
||||||
if let Some(dac) = &mut dac {
|
if let Some(dac) = &dac {
|
||||||
defmt::info!("loading DAC with value {}", current_val);
|
rprintln!("loading DAC with value {}", current_val);
|
||||||
dac.load_and_trigger_manually(current_val)
|
dac.load_and_trigger_manually(current_val)
|
||||||
.expect("loading DAC value failed");
|
.expect("loading DAC value failed");
|
||||||
if current_val + DAC_INCREMENT >= 4096 {
|
if current_val + DAC_INCREMENT >= 4096 {
|
||||||
@ -69,7 +70,7 @@ fn main() -> ! {
|
|||||||
let ch_value = adc
|
let ch_value = adc
|
||||||
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
|
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
|
||||||
.expect("reading ADC channel 0 failed");
|
.expect("reading ADC channel 0 failed");
|
||||||
defmt::info!("Received channel value {:?}", ch_value);
|
rprintln!("Received channel value {:?}", ch_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
delay_provider.delay_ms(500);
|
delay_provider.delay_ms(500);
|
||||||
|
@ -2,22 +2,20 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
|
||||||
|
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
|
|
||||||
|
use cortex_m::interrupt::Mutex;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use critical_section::Mutex;
|
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
|
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
|
||||||
use va416xx_hal::irq_router::enable_and_init_irq_router;
|
use va416xx_hal::pwm::CountdownTimer;
|
||||||
use va416xx_hal::pac::{self, interrupt};
|
use va416xx_hal::{
|
||||||
use va416xx_hal::timer::CountdownTimer;
|
pac::{self, interrupt},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
||||||
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
||||||
@ -27,40 +25,34 @@ static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
|||||||
#[link_section = ".sram1"]
|
#[link_section = ".sram1"]
|
||||||
static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new();
|
static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new();
|
||||||
|
|
||||||
// We can use statically allocated buffers for DMA transfers as well, and we can also place
|
|
||||||
// those into SRAM1.
|
|
||||||
#[link_section = ".sram1"]
|
|
||||||
static mut DMA_SRC_BUF: [u16; 36] = [0; 36];
|
|
||||||
#[link_section = ".sram1"]
|
|
||||||
static mut DMA_DEST_BUF: [u16; 36] = [0; 36];
|
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("VA416xx DMA example");
|
rtt_init_print!();
|
||||||
|
rprintln!("VA416xx DMA example");
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_and_init_irq_router();
|
|
||||||
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
|
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
|
||||||
// statically.
|
// statically.
|
||||||
let dma = Dma::new(
|
let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe {
|
||||||
dp.dma,
|
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK)
|
||||||
DmaCfg::default(),
|
})
|
||||||
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK),
|
|
||||||
)
|
|
||||||
.expect("error creating DMA");
|
.expect("error creating DMA");
|
||||||
let (mut dma0, _, _, _) = dma.split();
|
let (mut dma0, _, _, _) = dma.split();
|
||||||
let mut delay_ms = CountdownTimer::new(dp.tim0, &clocks);
|
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut src_buf_8_bit: [u8; 65] = [0; 65];
|
let mut src_buf_8_bit: [u8; 65] = [0; 65];
|
||||||
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
|
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
|
||||||
|
let mut src_buf_16_bit: [u16; 33] = [0; 33];
|
||||||
|
let mut dest_buf_16_bit: [u16; 33] = [0; 33];
|
||||||
let mut src_buf_32_bit: [u32; 17] = [0; 17];
|
let mut src_buf_32_bit: [u32; 17] = [0; 17];
|
||||||
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
|
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
|
||||||
loop {
|
loop {
|
||||||
// This example uses stack-allocated buffers.
|
|
||||||
transfer_example_8_bit(
|
transfer_example_8_bit(
|
||||||
&mut src_buf_8_bit,
|
&mut src_buf_8_bit,
|
||||||
&mut dest_buf_8_bit,
|
&mut dest_buf_8_bit,
|
||||||
@ -68,8 +60,12 @@ fn main() -> ! {
|
|||||||
&mut delay_ms,
|
&mut delay_ms,
|
||||||
);
|
);
|
||||||
delay_ms.delay_ms(500);
|
delay_ms.delay_ms(500);
|
||||||
// This example uses statically allocated buffers.
|
transfer_example_16_bit(
|
||||||
transfer_example_16_bit(&mut dma0, &mut delay_ms);
|
&mut src_buf_16_bit,
|
||||||
|
&mut dest_buf_16_bit,
|
||||||
|
&mut dma0,
|
||||||
|
&mut delay_ms,
|
||||||
|
);
|
||||||
delay_ms.delay_ms(500);
|
delay_ms.delay_ms(500);
|
||||||
transfer_example_32_bit(
|
transfer_example_32_bit(
|
||||||
&mut src_buf_32_bit,
|
&mut src_buf_32_bit,
|
||||||
@ -85,22 +81,20 @@ fn transfer_example_8_bit(
|
|||||||
src_buf: &mut [u8; 65],
|
src_buf: &mut [u8; 65],
|
||||||
dest_buf: &mut [u8; 65],
|
dest_buf: &mut [u8; 65],
|
||||||
dma0: &mut DmaChannel,
|
dma0: &mut DmaChannel,
|
||||||
delay: &mut CountdownTimer,
|
delay_ms: &mut CountdownTimer<pac::Tim0>,
|
||||||
) {
|
) {
|
||||||
(0..64).for_each(|i| {
|
(0..64).for_each(|i| {
|
||||||
src_buf[i] = i as u8;
|
src_buf[i] = i as u8;
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(false);
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
let dma_transfer = dma0
|
||||||
unsafe {
|
.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
|
||||||
dma0.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
|
|
||||||
.expect("error preparing transfer");
|
.expect("error preparing transfer");
|
||||||
}
|
|
||||||
// Enable all interrupts.
|
// Enable all interrupts.
|
||||||
// Safety: Not using mask based critical sections.
|
// Safety: Not using mask based critical sections.
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -111,12 +105,13 @@ fn transfer_example_8_bit(
|
|||||||
dma0.enable();
|
dma0.enable();
|
||||||
// We still need to manually trigger the DMA request.
|
// We still need to manually trigger the DMA request.
|
||||||
dma0.trigger_with_sw_request();
|
dma0.trigger_with_sw_request();
|
||||||
|
let dest_buf;
|
||||||
// Use polling for completion status.
|
// Use polling for completion status.
|
||||||
loop {
|
loop {
|
||||||
let mut dma_done = false;
|
let mut dma_done = false;
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
defmt::info!("DMA0 is active with 8 bit transfer");
|
rprintln!("DMA0 is active with 8 bit transfer");
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
}
|
}
|
||||||
if DMA_DONE_FLAG.borrow(cs).get() {
|
if DMA_DONE_FLAG.borrow(cs).get() {
|
||||||
@ -124,10 +119,12 @@ fn transfer_example_8_bit(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if dma_done {
|
if dma_done {
|
||||||
defmt::info!("8-bit transfer done");
|
rprintln!("8-bit transfer done");
|
||||||
|
// Safety: We checked for transfer completion.
|
||||||
|
dest_buf = unsafe { dma_transfer.release() };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay.delay_ms(1);
|
delay_ms.delay_ms(1);
|
||||||
}
|
}
|
||||||
(0..64).for_each(|i| {
|
(0..64).for_each(|i| {
|
||||||
assert_eq!(dest_buf[i], i as u8);
|
assert_eq!(dest_buf[i], i as u8);
|
||||||
@ -137,28 +134,26 @@ fn transfer_example_8_bit(
|
|||||||
dest_buf.fill(0);
|
dest_buf.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer) {
|
fn transfer_example_16_bit(
|
||||||
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) };
|
src_buf: &mut [u16; 33],
|
||||||
unsafe {
|
dest_buf: &mut [u16; 33],
|
||||||
|
dma0: &mut DmaChannel,
|
||||||
|
delay_ms: &mut CountdownTimer<pac::Tim0>,
|
||||||
|
) {
|
||||||
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
||||||
(0..32).for_each(|i| {
|
(0..32).for_each(|i| {
|
||||||
DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16;
|
src_buf[i] = (i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16;
|
||||||
});
|
});
|
||||||
}
|
cortex_m::interrupt::free(|cs| {
|
||||||
critical_section::with(|cs| {
|
|
||||||
DMA_DONE_FLAG.borrow(cs).set(false);
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
let dma_transfer = dma0
|
||||||
unsafe {
|
.prepare_mem_to_mem_transfer_16_bit(src_buf, dest_buf)
|
||||||
dma0.prepare_mem_to_mem_transfer_16_bit(
|
|
||||||
&*core::ptr::addr_of!(DMA_SRC_BUF[0..32]),
|
|
||||||
&mut dest_buf_ref[0..32],
|
|
||||||
)
|
|
||||||
.expect("error preparing transfer");
|
.expect("error preparing transfer");
|
||||||
}
|
dest_buf[5] = 2;
|
||||||
// Enable all interrupts.
|
// Enable all interrupts.
|
||||||
// Safety: Not using mask based critical sections.
|
// Safety: Not using mask based critical sections.
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -172,9 +167,9 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer)
|
|||||||
// Use polling for completion status.
|
// Use polling for completion status.
|
||||||
loop {
|
loop {
|
||||||
let mut dma_done = false;
|
let mut dma_done = false;
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
defmt::info!("DMA0 is active with 16-bit transfer");
|
rprintln!("DMA0 is active with 16-bit transfer");
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
}
|
}
|
||||||
if DMA_DONE_FLAG.borrow(cs).get() {
|
if DMA_DONE_FLAG.borrow(cs).get() {
|
||||||
@ -182,43 +177,40 @@ fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if dma_done {
|
if dma_done {
|
||||||
defmt::info!("16-bit transfer done");
|
rprintln!("16-bit transfer done");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay_ms.delay_ms(1);
|
delay_ms.delay_ms(1);
|
||||||
}
|
}
|
||||||
(0..32).for_each(|i| {
|
(0..32).for_each(|i| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dest_buf_ref[i],
|
dest_buf[i],
|
||||||
(i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16
|
(i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// Sentinel value, should be 0.
|
// Sentinel value, should be 0.
|
||||||
assert_eq!(dest_buf_ref[32], 0);
|
assert_eq!(dest_buf[32], 0);
|
||||||
dest_buf_ref.fill(0);
|
dest_buf.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_example_32_bit(
|
fn transfer_example_32_bit(
|
||||||
src_buf: &mut [u32; 17],
|
src_buf: &mut [u32; 17],
|
||||||
dest_buf: &mut [u32; 17],
|
dest_buf: &mut [u32; 17],
|
||||||
dma0: &mut DmaChannel,
|
dma0: &mut DmaChannel,
|
||||||
delay_ms: &mut CountdownTimer,
|
delay_ms: &mut CountdownTimer<pac::Tim0>,
|
||||||
) {
|
) {
|
||||||
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
||||||
(0..16).for_each(|i| {
|
(0..16).for_each(|i| {
|
||||||
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
|
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(false);
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
});
|
});
|
||||||
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
|
||||||
unsafe {
|
|
||||||
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
|
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
|
||||||
.expect("error preparing transfer");
|
.expect("error preparing transfer");
|
||||||
}
|
|
||||||
// Enable all interrupts.
|
// Enable all interrupts.
|
||||||
// Safety: Not using mask based critical sections.
|
// Safety: Not using mask based critical sections.
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -232,9 +224,9 @@ fn transfer_example_32_bit(
|
|||||||
// Use polling for completion status.
|
// Use polling for completion status.
|
||||||
loop {
|
loop {
|
||||||
let mut dma_done = false;
|
let mut dma_done = false;
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
defmt::info!("DMA0 is active with 32-bit transfer");
|
rprintln!("DMA0 is active with 32-bit transfer");
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
}
|
}
|
||||||
if DMA_DONE_FLAG.borrow(cs).get() {
|
if DMA_DONE_FLAG.borrow(cs).get() {
|
||||||
@ -242,7 +234,7 @@ fn transfer_example_32_bit(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if dma_done {
|
if dma_done {
|
||||||
defmt::info!("32-bit transfer done");
|
rprintln!("32-bit transfer done");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay_ms.delay_ms(1);
|
delay_ms.delay_ms(1);
|
||||||
@ -262,7 +254,7 @@ fn transfer_example_32_bit(
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn DMA_DONE0() {
|
fn DMA_DONE0() {
|
||||||
// Notify the main loop that the DMA transfer is finished.
|
// Notify the main loop that the DMA transfer is finished.
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_DONE_FLAG.borrow(cs).set(true);
|
DMA_DONE_FLAG.borrow(cs).set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -271,7 +263,7 @@ fn DMA_DONE0() {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn DMA_ACTIVE0() {
|
fn DMA_ACTIVE0() {
|
||||||
// Notify the main loop that the DMA 0 is active now.
|
// Notify the main loop that the DMA 0 is active now.
|
||||||
critical_section::with(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
DMA_ACTIVE_FLAG.borrow(cs).set(true);
|
DMA_ACTIVE_FLAG.borrow(cs).set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,17 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use accelerometer::{Accelerometer, RawAccelerometer};
|
use accelerometer::{Accelerometer, RawAccelerometer};
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
clock::ClockConfigurator,
|
|
||||||
i2c,
|
i2c,
|
||||||
pac::{self},
|
pac::{self},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
timer::CountdownTimer,
|
pwm::CountdownTimer,
|
||||||
};
|
};
|
||||||
use vorago_peb1::lis2dh12::{self, detect_i2c_addr, FullScale, Odr};
|
use vorago_peb1::lis2dh12::{self, detect_i2c_addr, FullScale, Odr};
|
||||||
|
|
||||||
@ -29,21 +25,25 @@ const DISPLAY_MODE: DisplayMode = DisplayMode::Normalized;
|
|||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
|
rtt_init_print!();
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
defmt::println!("-- Vorago PEB1 accelerometer example --");
|
rprintln!("-- Vorago PEB1 accelerometer example --");
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut i2c_master = i2c::I2cMaster::new(
|
let mut i2c_master = i2c::I2cMaster::new(
|
||||||
dp.i2c0,
|
dp.i2c0,
|
||||||
&clocks,
|
&mut dp.sysconfig,
|
||||||
i2c::MasterConfig::default(),
|
i2c::MasterConfig::default(),
|
||||||
|
&clocks,
|
||||||
i2c::I2cSpeed::Regular100khz,
|
i2c::I2cSpeed::Regular100khz,
|
||||||
)
|
)
|
||||||
.expect("creating I2C master failed");
|
.expect("creating I2C master failed");
|
||||||
let mut delay_provider = CountdownTimer::new(dp.tim1, &clocks);
|
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
|
||||||
// Detect the I2C address of the accelerometer by scanning all possible values.
|
// Detect the I2C address of the accelerometer by scanning all possible values.
|
||||||
let slave_addr = detect_i2c_addr(&mut i2c_master).expect("detecting I2C address failed");
|
let slave_addr = detect_i2c_addr(&mut i2c_master).expect("detecting I2C address failed");
|
||||||
// Create the accelerometer driver using the PEB1 BSP.
|
// Create the accelerometer driver using the PEB1 BSP.
|
||||||
@ -51,7 +51,7 @@ fn main() -> ! {
|
|||||||
.expect("creating accelerometer driver failed");
|
.expect("creating accelerometer driver failed");
|
||||||
let device_id = accelerometer.get_device_id().unwrap();
|
let device_id = accelerometer.get_device_id().unwrap();
|
||||||
accelerometer
|
accelerometer
|
||||||
.set_mode(lis2dh12::Mode::Normal)
|
.set_mode(lis2dh12::reg::Mode::Normal)
|
||||||
.expect("setting mode failed");
|
.expect("setting mode failed");
|
||||||
accelerometer
|
accelerometer
|
||||||
.set_odr(Odr::Hz100)
|
.set_odr(Odr::Hz100)
|
||||||
@ -63,7 +63,7 @@ fn main() -> ! {
|
|||||||
accelerometer
|
accelerometer
|
||||||
.enable_temp(true)
|
.enable_temp(true)
|
||||||
.expect("enabling temperature sensor failed");
|
.expect("enabling temperature sensor failed");
|
||||||
defmt::info!("Device ID: 0x{:02X}", device_id);
|
rprintln!("Device ID: 0x{:02X}", device_id);
|
||||||
// Start reading the accelerometer periodically.
|
// Start reading the accelerometer periodically.
|
||||||
loop {
|
loop {
|
||||||
let temperature = accelerometer
|
let temperature = accelerometer
|
||||||
@ -74,25 +74,13 @@ fn main() -> ! {
|
|||||||
let value = accelerometer
|
let value = accelerometer
|
||||||
.accel_norm()
|
.accel_norm()
|
||||||
.expect("reading normalized accelerometer data failed");
|
.expect("reading normalized accelerometer data failed");
|
||||||
defmt::info!(
|
rprintln!("Accel Norm F32x3: {:.06?} | Temp {} °C", value, temperature);
|
||||||
"Accel Norm F32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
|
|
||||||
value.x,
|
|
||||||
value.y,
|
|
||||||
value.z,
|
|
||||||
temperature
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
DisplayMode::Raw => {
|
DisplayMode::Raw => {
|
||||||
let value_raw = accelerometer
|
let value_raw = accelerometer
|
||||||
.accel_raw()
|
.accel_raw()
|
||||||
.expect("reading raw accelerometer data failed");
|
.expect("reading raw accelerometer data failed");
|
||||||
defmt::info!(
|
rprintln!("Accel Raw F32x3: {:?} | Temp {} °C", value_raw, temperature);
|
||||||
"Accel Raw I32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
|
|
||||||
value_raw.x,
|
|
||||||
value_raw.y,
|
|
||||||
value_raw.z,
|
|
||||||
temperature
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delay_provider.delay_ms(100);
|
delay_provider.delay_ms(100);
|
||||||
|
@ -1,45 +1,48 @@
|
|||||||
//! Simple PWM example
|
//! Simple PWM example
|
||||||
//!
|
|
||||||
//! Outputs a PWM waveform on pin PG2.
|
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::{delay::DelayNs, pwm::SetDutyCycle};
|
use embedded_hal::{delay::DelayNs, pwm::SetDutyCycle};
|
||||||
// Import panic provider.
|
use panic_rtt_target as _;
|
||||||
use panic_probe as _;
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
clock::ClockConfigurator,
|
gpio::PinsA,
|
||||||
pac,
|
pac,
|
||||||
pins::PinsG,
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{get_duty_from_percent, PwmA, PwmB, PwmPin},
|
pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin},
|
||||||
timer::CountdownTimer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("-- VA108xx PWM example application--");
|
rtt_init_print!();
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
rprintln!("-- VA108xx PWM example application--");
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let pinsg = PinsG::new(dp.portg);
|
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
|
||||||
let mut pwm = PwmPin::new(pinsg.pg2, dp.tim9, &clocks, 10.Hz()).unwrap();
|
let mut pwm = pwm::PwmPin::new(
|
||||||
let mut delay_timer = CountdownTimer::new(dp.tim0, &clocks);
|
(pinsa.pa3.into_funsel_1(), dp.tim3),
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
&clocks,
|
||||||
|
10.Hz(),
|
||||||
|
);
|
||||||
|
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut current_duty_cycle = 0.0;
|
let mut current_duty_cycle = 0.0;
|
||||||
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
pwm.enable();
|
pwm.enable();
|
||||||
|
|
||||||
// Delete type information, increased code readibility for the rest of the code
|
// Delete type information, increased code readibility for the rest of the code
|
||||||
|
let mut reduced_pin = ReducedPwmPin::from(pwm);
|
||||||
loop {
|
loop {
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
// Increase duty cycle continuously
|
// Increase duty cycle continuously
|
||||||
@ -48,10 +51,11 @@ fn main() -> ! {
|
|||||||
current_duty_cycle += 0.02;
|
current_duty_cycle += 0.02;
|
||||||
counter += 1;
|
counter += 1;
|
||||||
if counter % 10 == 0 {
|
if counter % 10 == 0 {
|
||||||
defmt::info!("current duty cycle: {}", current_duty_cycle);
|
rprintln!("current duty cycle: {}", current_duty_cycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
reduced_pin
|
||||||
|
.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +64,7 @@ fn main() -> ! {
|
|||||||
current_duty_cycle = 0.0;
|
current_duty_cycle = 0.0;
|
||||||
let mut upper_limit = 1.0;
|
let mut upper_limit = 1.0;
|
||||||
let mut lower_limit = 0.0;
|
let mut lower_limit = 0.0;
|
||||||
let mut pwmb: PwmPin<PwmB> = PwmPin::from(pwm);
|
let mut pwmb: ReducedPwmPin<PwmB> = ReducedPwmPin::from(reduced_pin);
|
||||||
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
|
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
|
||||||
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
||||||
while lower_limit < 0.5 {
|
while lower_limit < 0.5 {
|
||||||
@ -69,9 +73,9 @@ fn main() -> ! {
|
|||||||
upper_limit -= 0.01;
|
upper_limit -= 0.01;
|
||||||
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
|
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
|
||||||
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
||||||
defmt::info!("Lower limit: {}", pwmb.pwmb_lower_limit());
|
rprintln!("Lower limit: {}", pwmb.pwmb_lower_limit());
|
||||||
defmt::info!("Upper limit: {}", pwmb.pwmb_upper_limit());
|
rprintln!("Upper limit: {}", pwmb.pwmb_upper_limit());
|
||||||
}
|
}
|
||||||
pwm = PwmPin::<PwmA>::from(pwmb);
|
reduced_pin = ReducedPwmPin::<PwmA>::from(pwmb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
examples/simple/examples/rtt-log.rs
Normal file
36
examples/simple/examples/rtt-log.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Code to test RTT logger functionality.
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use cortex_m_rt::entry;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va416xx_hal::pac;
|
||||||
|
|
||||||
|
// Mask for the LED
|
||||||
|
const LED_PG5: u32 = 1 << 5;
|
||||||
|
|
||||||
|
#[entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
|
// Enable all peripheral clocks
|
||||||
|
dp.sysconfig
|
||||||
|
.peripheral_clk_enable()
|
||||||
|
.modify(|_, w| unsafe { w.bits(0xffffffff) });
|
||||||
|
dp.portg.dir().modify(|_, w| unsafe { w.bits(LED_PG5) });
|
||||||
|
dp.portg
|
||||||
|
.datamask()
|
||||||
|
.modify(|_, w| unsafe { w.bits(LED_PG5) });
|
||||||
|
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("VA416xx RTT Demo");
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
rprintln!("{}: Hello, world!", counter);
|
||||||
|
// Still toggle LED. If there are issues with the RTT log, the LED
|
||||||
|
// blinking ensures that the application is actually running.
|
||||||
|
dp.portg.togout().write(|w| unsafe { w.bits(LED_PG5) });
|
||||||
|
counter += 1;
|
||||||
|
cortex_m::asm::delay(10_000_000);
|
||||||
|
}
|
||||||
|
}
|
@ -3,31 +3,27 @@
|
|||||||
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
|
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
use embedded_hal::delay::DelayNs;
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::spi::{Mode, SpiBus, MODE_0};
|
use embedded_hal::spi::{Mode, SpiBus, MODE_0};
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
use va416xx_hal::spi::{Spi, TransferConfig};
|
||||||
use va416xx_hal::spi::{Spi, SpiClkConfig};
|
|
||||||
use va416xx_hal::timer::CountdownTimer;
|
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
|
gpio::{PinsB, PinsC},
|
||||||
pac,
|
pac,
|
||||||
pins::{PinsB, PinsC},
|
prelude::*,
|
||||||
spi::SpiConfig,
|
spi::SpiConfig,
|
||||||
time::Hertz,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum ExampleSelect {
|
pub enum ExampleSelect {
|
||||||
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
|
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
|
||||||
Loopback,
|
Loopback,
|
||||||
// You need to tie together MOSI/MISO in this mode.
|
// Send a test buffer and print everything received. You need to tie together MOSI/MISO in this
|
||||||
MosiMisoTiedTogether,
|
// mode.
|
||||||
|
TestBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback;
|
const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback;
|
||||||
@ -38,50 +34,63 @@ const FILL_WORD: u8 = 0x0f;
|
|||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("-- VA108xx SPI example application--");
|
rtt_init_print!();
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
rprintln!("-- VA108xx SPI example application--");
|
||||||
|
let cp = cortex_m::Peripherals::take().unwrap();
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut delay = CountdownTimer::new(dp.tim1, &clocks);
|
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
|
||||||
|
|
||||||
let pins_b = PinsB::new(dp.portb);
|
let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
|
||||||
let pins_c = PinsC::new(dp.portc);
|
let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
|
||||||
|
// Configure SPI1 pins.
|
||||||
|
let (sck, miso, mosi) = (
|
||||||
|
pins_b.pb15.into_funsel_1(),
|
||||||
|
pins_c.pc0.into_funsel_1(),
|
||||||
|
pins_c.pc1.into_funsel_1(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut spi_cfg = SpiConfig::default()
|
let mut spi_cfg = SpiConfig::default();
|
||||||
.clk_cfg(
|
|
||||||
SpiClkConfig::from_clks(&clocks, Hertz::from_raw(SPI_SPEED_KHZ))
|
|
||||||
.expect("invalid target clock"),
|
|
||||||
)
|
|
||||||
.mode(SPI_MODE)
|
|
||||||
.blockmode(BLOCKMODE);
|
|
||||||
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
||||||
spi_cfg = spi_cfg.loopback(true)
|
spi_cfg = spi_cfg.loopback(true)
|
||||||
}
|
}
|
||||||
|
let transfer_cfg =
|
||||||
|
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
|
||||||
// Create SPI peripheral.
|
// Create SPI peripheral.
|
||||||
let mut spi0 = Spi::new(dp.spi0, (pins_b.pb15, pins_c.pc0, pins_c.pc1), spi_cfg).unwrap();
|
let mut spi0 = Spi::new(
|
||||||
|
dp.spi0,
|
||||||
|
(sck, miso, mosi),
|
||||||
|
&clocks,
|
||||||
|
spi_cfg,
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
Some(&transfer_cfg.downgrade()),
|
||||||
|
);
|
||||||
spi0.set_fill_word(FILL_WORD);
|
spi0.set_fill_word(FILL_WORD);
|
||||||
loop {
|
loop {
|
||||||
let tx_buf: [u8; 4] = [1, 2, 3, 0];
|
let mut tx_buf: [u8; 3] = [1, 2, 3];
|
||||||
let mut rx_buf: [u8; 4] = [0; 4];
|
let mut rx_buf: [u8; 3] = [0; 3];
|
||||||
// Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up.
|
// Can't really verify correct reply here.
|
||||||
spi0.write(&[0x42, 0x43]).expect("write failed");
|
spi0.write(&[0x42]).expect("write failed");
|
||||||
|
// Need small delay.. otherwise we will read back the sent byte (which we don't want here).
|
||||||
|
// The write function will return as soon as all bytes were shifted out, ignoring the
|
||||||
|
// reply bytes.
|
||||||
|
delay_sysclk.delay_us(50);
|
||||||
|
// Because of the loopback mode, we should get back the fill word here.
|
||||||
|
spi0.read(&mut rx_buf[0..1]).unwrap();
|
||||||
|
assert_eq!(rx_buf[0], FILL_WORD);
|
||||||
|
|
||||||
// Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up.
|
spi0.transfer_in_place(&mut tx_buf)
|
||||||
spi0.read(&mut rx_buf[0..2]).unwrap();
|
|
||||||
|
|
||||||
// If the pins are tied together, we should received exactly what we send.
|
|
||||||
|
|
||||||
let mut inplace_buf = tx_buf;
|
|
||||||
spi0.transfer_in_place(&mut inplace_buf)
|
|
||||||
.expect("SPI transfer_in_place failed");
|
.expect("SPI transfer_in_place failed");
|
||||||
assert_eq!([1, 2, 3, 0], inplace_buf);
|
assert_eq!([1, 2, 3], tx_buf);
|
||||||
|
|
||||||
spi0.transfer(&mut rx_buf, &tx_buf)
|
spi0.transfer(&mut rx_buf, &tx_buf)
|
||||||
.expect("SPI transfer failed");
|
.expect("SPI transfer failed");
|
||||||
assert_eq!(rx_buf, [1, 2, 3, 0]);
|
assert_eq!(rx_buf, tx_buf);
|
||||||
delay.delay_ms(500);
|
delay_sysclk.delay_ms(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
//! MS and Second counter implemented using the TIM0 and TIM1 peripheral
|
//! MS and Second counter implemented using the TIM0 and TIM1 peripheral
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU32, Ordering};
|
use core::cell::Cell;
|
||||||
use cortex_m::asm;
|
use cortex_m::interrupt::Mutex;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::{
|
use va416xx_hal::{
|
||||||
clock::ClockConfigurator,
|
|
||||||
irq_router::enable_and_init_irq_router,
|
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
timer::CountdownTimer,
|
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -24,47 +20,49 @@ enum LibType {
|
|||||||
Hal,
|
Hal,
|
||||||
}
|
}
|
||||||
|
|
||||||
static MS_COUNTER: AtomicU32 = AtomicU32::new(0);
|
static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
|
||||||
static SEC_COUNTER: AtomicU32 = AtomicU32::new(0);
|
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
rtt_init_print!();
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
let mut last_ms = 0;
|
let mut last_ms = 0;
|
||||||
defmt::println!("-- Vorago system ticks using timers --");
|
rprintln!("-- Vorago system ticks using timers --");
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_and_init_irq_router();
|
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
let mut ms_timer = CountdownTimer::new(dp.tim0, &clocks);
|
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
|
||||||
ms_timer.enable_interrupt(true);
|
|
||||||
ms_timer.start(1.Hz());
|
|
||||||
let mut second_timer = CountdownTimer::new(dp.tim1, &clocks);
|
|
||||||
second_timer.enable_interrupt(true);
|
|
||||||
second_timer.start(1.Hz());
|
second_timer.start(1.Hz());
|
||||||
|
second_timer.listen();
|
||||||
loop {
|
loop {
|
||||||
let current_ms = MS_COUNTER.load(Ordering::Relaxed);
|
let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get());
|
||||||
if current_ms >= last_ms + 1000 {
|
if current_ms - last_ms >= 1000 {
|
||||||
// To prevent drift.
|
last_ms = current_ms;
|
||||||
last_ms += 1000;
|
rprintln!("MS counter: {}", current_ms);
|
||||||
defmt::info!("MS counter: {}", current_ms);
|
let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get());
|
||||||
let second = SEC_COUNTER.load(Ordering::Relaxed);
|
rprintln!("Second counter: {}", second);
|
||||||
defmt::info!("Second counter: {}", second);
|
|
||||||
}
|
}
|
||||||
asm::delay(1000);
|
cortex_m::asm::delay(10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn TIM0() {
|
fn TIM0() {
|
||||||
MS_COUNTER.fetch_add(1, Ordering::Relaxed);
|
default_ms_irq_handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn TIM1() {
|
fn TIM1() {
|
||||||
SEC_COUNTER.fetch_add(1, Ordering::Relaxed);
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
let mut sec = SEC_COUNTER.borrow(cs).get();
|
||||||
|
sec += 1;
|
||||||
|
SEC_COUNTER.borrow(cs).set(sec);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,53 +2,55 @@
|
|||||||
// echo mode
|
// echo mode
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal_nb::serial::Read;
|
use embedded_hal_nb::serial::Read;
|
||||||
use embedded_io::Write;
|
use embedded_io::Write;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
use va416xx_hal::clock::ClkgenExt;
|
||||||
use va416xx_hal::pins::PinsG;
|
|
||||||
use va416xx_hal::time::Hertz;
|
use va416xx_hal::time::Hertz;
|
||||||
use va416xx_hal::{pac, uart};
|
use va416xx_hal::{gpio::PinsG, pac, uart};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("-- VA416xx UART example application--");
|
rtt_init_print!();
|
||||||
|
rprintln!("-- VA416xx UART example application--");
|
||||||
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let gpiog = PinsG::new(dp.portg);
|
let gpiob = PinsG::new(&mut dp.sysconfig, dp.portg);
|
||||||
|
let tx = gpiob.pg0.into_funsel_1();
|
||||||
|
let rx = gpiob.pg1.into_funsel_1();
|
||||||
|
|
||||||
let uart0 = uart::Uart::new(
|
let uart0 = uart::Uart::new(
|
||||||
dp.uart0,
|
dp.uart0,
|
||||||
gpiog.pg0,
|
(tx, rx),
|
||||||
gpiog.pg1,
|
Hertz::from_raw(115200),
|
||||||
|
&mut dp.sysconfig,
|
||||||
&clocks,
|
&clocks,
|
||||||
Hertz::from_raw(115200).into(),
|
);
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let (mut tx, mut rx) = uart0.split();
|
let (mut tx, mut rx) = uart0.split();
|
||||||
writeln!(tx, "Hello World\n\r").unwrap();
|
writeln!(tx, "Hello World\n\r").unwrap();
|
||||||
loop {
|
loop {
|
||||||
// Echo what is received on the serial link.
|
// Echo what is received on the serial link.
|
||||||
match nb::block!(rx.read()) {
|
match nb::block!(rx.read()) {
|
||||||
Ok(recvd) => {
|
Ok(recvd) => {
|
||||||
// Infallible operation.
|
if let Err(e) = embedded_hal_nb::serial::Write::write(&mut tx, recvd) {
|
||||||
embedded_hal_nb::serial::Write::write(&mut tx, recvd).unwrap();
|
rprintln!("UART TX error: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
defmt::info!("UART RX error {:?}", e);
|
rprintln!("UART RX error {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
// Code to test the watchdog timer.
|
// Code to test the watchdog timer.
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU32, Ordering};
|
use core::cell::Cell;
|
||||||
|
use cortex_m::interrupt::Mutex;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use simple_examples::peb1;
|
use simple_examples::peb1;
|
||||||
use va416xx_hal::irq_router::enable_and_init_irq_router;
|
|
||||||
use va416xx_hal::pac::{self, interrupt};
|
use va416xx_hal::pac::{self, interrupt};
|
||||||
use va416xx_hal::wdt::Wdt;
|
use va416xx_hal::prelude::*;
|
||||||
|
use va416xx_hal::wdt::WdtController;
|
||||||
|
|
||||||
static WDT_INTRPT_COUNT: AtomicU32 = AtomicU32::new(0);
|
static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -30,39 +28,36 @@ const WDT_ROLLOVER_MS: u32 = 100;
|
|||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("-- VA416xx WDT example application--");
|
rtt_init_print!();
|
||||||
|
rprintln!("-- VA416xx WDT example application--");
|
||||||
let cp = cortex_m::Peripherals::take().unwrap();
|
let cp = cortex_m::Peripherals::take().unwrap();
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
||||||
// Use the external clock connected to XTAL_N.
|
// Use the external clock connected to XTAL_N.
|
||||||
let clocks = ClockConfigurator::new(dp.clkgen)
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
.freeze()
|
.freeze(&mut dp.sysconfig)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
enable_and_init_irq_router();
|
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
|
||||||
let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
|
|
||||||
|
|
||||||
let mut last_interrupt_counter = 0;
|
let mut last_interrupt_counter = 0;
|
||||||
let mut wdt_ctrl = Wdt::start(dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
|
let mut wdt_ctrl =
|
||||||
|
WdtController::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
|
||||||
wdt_ctrl.enable_reset();
|
wdt_ctrl.enable_reset();
|
||||||
let log_divisor = 25;
|
|
||||||
let mut counter: u32 = 0;
|
|
||||||
loop {
|
loop {
|
||||||
counter = counter.wrapping_add(1);
|
|
||||||
if counter % log_divisor == 0 {
|
|
||||||
defmt::info!("wdt example main loop alive");
|
|
||||||
}
|
|
||||||
if TEST_MODE != TestMode::AllowReset {
|
if TEST_MODE != TestMode::AllowReset {
|
||||||
wdt_ctrl.feed();
|
wdt_ctrl.feed();
|
||||||
}
|
}
|
||||||
let interrupt_counter = WDT_INTRPT_COUNT.load(Ordering::Relaxed);
|
let interrupt_counter = cortex_m::interrupt::free(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
|
||||||
if interrupt_counter > last_interrupt_counter {
|
if interrupt_counter > last_interrupt_counter {
|
||||||
defmt::info!("interrupt counter has increased to {}", interrupt_counter);
|
rprintln!("interrupt counter has increased to {}", interrupt_counter);
|
||||||
last_interrupt_counter = interrupt_counter;
|
last_interrupt_counter = interrupt_counter;
|
||||||
}
|
}
|
||||||
match TEST_MODE {
|
match TEST_MODE {
|
||||||
TestMode::FedByMain => delay.delay_ms(WDT_ROLLOVER_MS / 5),
|
TestMode::FedByMain => delay_sysclk.delay_ms(WDT_ROLLOVER_MS / 5),
|
||||||
TestMode::FedByIrq => delay.delay_ms(WDT_ROLLOVER_MS),
|
TestMode::FedByIrq => delay_sysclk.delay_ms(WDT_ROLLOVER_MS),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +66,11 @@ fn main() -> ! {
|
|||||||
#[interrupt]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn WATCHDOG() {
|
fn WATCHDOG() {
|
||||||
WDT_INTRPT_COUNT.fetch_add(1, Ordering::Relaxed);
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
WDT_INTRPT_COUNT
|
||||||
|
.borrow(cs)
|
||||||
|
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);
|
||||||
|
});
|
||||||
let wdt = unsafe { pac::WatchDog::steal() };
|
let wdt = unsafe { pac::WatchDog::steal() };
|
||||||
// Clear interrupt.
|
// Clear interrupt.
|
||||||
if TEST_MODE != TestMode::AllowReset {
|
if TEST_MODE != TestMode::AllowReset {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use panic_halt as _;
|
use panic_rtt_target as _;
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
|
1
flashloader/.gitignore
vendored
1
flashloader/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/venv
|
|
@ -1,22 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "flashloader"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m = "0.7"
|
|
||||||
embedded-io = "0.6"
|
|
||||||
defmt-rtt = "0.4"
|
|
||||||
defmt = "1"
|
|
||||||
panic-probe = { version = "1", features = ["defmt"] }
|
|
||||||
static_cell = "2"
|
|
||||||
satrs = { version = "0.3.0-alpha.0", default-features = false }
|
|
||||||
ringbuf = { version = "0.4", default-features = false }
|
|
||||||
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
|
|
||||||
spacepackets = { version = "0.13", default-features = false, features = ["defmt"] }
|
|
||||||
cobs = { version = "0.3", default-features = false }
|
|
||||||
|
|
||||||
va416xx-hal = { version = "0.5", features = ["va41630", "defmt"], path = "../va416xx-hal" }
|
|
||||||
|
|
||||||
rtic = { version = "2", features = ["thumbv7-backend"] }
|
|
||||||
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
|
|
@ -1,66 +0,0 @@
|
|||||||
VA416xx Flashloader Application
|
|
||||||
========
|
|
||||||
|
|
||||||
This flashloader shows a minimal example for a self-updatable Rust software which exposes
|
|
||||||
a simple PUS (CCSDS) interface to update the software. It also provides a Python application
|
|
||||||
called the `image-loader.py` which can be used to upload compiled images to the flashloader
|
|
||||||
application to write them to the NVM.
|
|
||||||
|
|
||||||
Please note that the both the application and the image loader are tailored towards usage
|
|
||||||
with the [bootloader provided by this repository](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader).
|
|
||||||
|
|
||||||
The software can quickly be adapted to interface with a real primary on-board software instead of
|
|
||||||
the Python script provided here to upload images because it uses a low-level CCSDS based packet
|
|
||||||
interface.
|
|
||||||
|
|
||||||
## Using the Python image loader
|
|
||||||
|
|
||||||
The Python image loader communicates with the Rust flashload application using a dedicated serial
|
|
||||||
port with a baudrate of 115200.
|
|
||||||
|
|
||||||
It is recommended to run the script in a dedicated virtual environment. For example, on UNIX
|
|
||||||
systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
|
|
||||||
and activate a virtual environment.
|
|
||||||
|
|
||||||
After that, you can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
to install all required dependencies.
|
|
||||||
|
|
||||||
After that, it is recommended to use `./image-load.py -h` to get an overview of some options.
|
|
||||||
The flash loader uses the UART0 interface of the VA416xx board to perform CCSDS based
|
|
||||||
communication. The Python image loader application will search for a file named `loader.toml` and
|
|
||||||
use the `serial_port` key to determine the serial port to use for serial communication.
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
You can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./image-loader.py -p
|
|
||||||
```
|
|
||||||
|
|
||||||
to send a ping an verify the connection.
|
|
||||||
|
|
||||||
You can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd flashloader/slot-a-blinky
|
|
||||||
cargo build --release
|
|
||||||
cd ../..
|
|
||||||
./image-loader.py -t a ./slot-a-blinky/target/thumbv7em-none-eabihf/release/slot-a-blinky
|
|
||||||
```
|
|
||||||
|
|
||||||
to build the slot A sample application and upload it to a running flash loader application
|
|
||||||
to write it to slot A.
|
|
||||||
|
|
||||||
You can use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./image-loader.py -c -t a
|
|
||||||
```
|
|
||||||
|
|
||||||
to corrupt the image A and test that it switches to image B after a failed CRC check instead.
|
|
@ -1,457 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from typing import List, Tuple
|
|
||||||
from spacepackets.ecss.defs import PusService
|
|
||||||
from spacepackets.ecss.tm import PusTm
|
|
||||||
from com_interface import ComInterface
|
|
||||||
import toml
|
|
||||||
import struct
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
import time
|
|
||||||
import enum
|
|
||||||
from com_interface.serial_base import SerialCfg
|
|
||||||
from com_interface.serial_cobs import SerialCobsComIF
|
|
||||||
from crcmod.predefined import PredefinedCrc
|
|
||||||
from spacepackets.ecss.tc import PusTc
|
|
||||||
from spacepackets.ecss.pus_verificator import PusVerificator, StatusField
|
|
||||||
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
|
|
||||||
from spacepackets.seqcount import SeqCountProvider
|
|
||||||
from pathlib import Path
|
|
||||||
import dataclasses
|
|
||||||
from elftools.elf.elffile import ELFFile
|
|
||||||
|
|
||||||
|
|
||||||
BAUD_RATE = 115200
|
|
||||||
|
|
||||||
BOOTLOADER_START_ADDR = 0x0
|
|
||||||
BOOTLOADER_END_ADDR = 0x4000
|
|
||||||
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 4
|
|
||||||
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4
|
|
||||||
|
|
||||||
APP_A_START_ADDR = 0x4000
|
|
||||||
APP_A_END_ADDR = 0x22000
|
|
||||||
# The actual size of the image which is relevant for CRC calculation.
|
|
||||||
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8
|
|
||||||
APP_A_CRC_ADDR = APP_A_END_ADDR - 4
|
|
||||||
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
|
|
||||||
|
|
||||||
APP_B_START_ADDR = 0x22000
|
|
||||||
APP_B_END_ADDR = 0x40000
|
|
||||||
# The actual size of the image which is relevant for CRC calculation.
|
|
||||||
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8
|
|
||||||
APP_B_CRC_ADDR = APP_B_END_ADDR - 4
|
|
||||||
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
|
|
||||||
|
|
||||||
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
|
|
||||||
|
|
||||||
CHUNK_SIZE = 896
|
|
||||||
|
|
||||||
MEMORY_SERVICE = 6
|
|
||||||
ACTION_SERVICE = 8
|
|
||||||
|
|
||||||
RAW_MEMORY_WRITE_SUBSERVICE = 2
|
|
||||||
BOOT_NVM_MEMORY_ID = 1
|
|
||||||
PING_PAYLOAD_SIZE = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ActionId(enum.IntEnum):
|
|
||||||
CORRUPT_APP_A = 128
|
|
||||||
CORRUPT_APP_B = 129
|
|
||||||
SET_BOOT_SLOT = 130
|
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class LoadableSegment:
|
|
||||||
name: str
|
|
||||||
offset: int
|
|
||||||
size: int
|
|
||||||
data: bytes
|
|
||||||
|
|
||||||
|
|
||||||
class Target(enum.Enum):
|
|
||||||
BOOTLOADER = 0
|
|
||||||
APP_A = 1
|
|
||||||
APP_B = 2
|
|
||||||
|
|
||||||
|
|
||||||
class AppSel(enum.IntEnum):
|
|
||||||
APP_A = 0
|
|
||||||
APP_B = 1
|
|
||||||
|
|
||||||
|
|
||||||
class ImageLoader:
|
|
||||||
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
|
|
||||||
self.com_if = com_if
|
|
||||||
self.verificator = verificator
|
|
||||||
|
|
||||||
def handle_boot_sel_cmd(self, target: AppSel):
|
|
||||||
_LOGGER.info("Sending ping command")
|
|
||||||
action_tc = PusTc(
|
|
||||||
apid=0x00,
|
|
||||||
service=PusService.S8_FUNC_CMD,
|
|
||||||
subservice=ActionId.SET_BOOT_SLOT,
|
|
||||||
seq_count=SEQ_PROVIDER.get_and_increment(),
|
|
||||||
app_data=bytes([target]),
|
|
||||||
)
|
|
||||||
self.verificator.add_tc(action_tc)
|
|
||||||
self.com_if.send(bytes(action_tc.pack()))
|
|
||||||
self.await_for_command_completion("boot image selection command")
|
|
||||||
|
|
||||||
def handle_ping_cmd(self):
|
|
||||||
_LOGGER.info("Sending ping command")
|
|
||||||
ping_tc = PusTc(
|
|
||||||
apid=0x00,
|
|
||||||
service=PusService.S17_TEST,
|
|
||||||
subservice=1,
|
|
||||||
seq_count=SEQ_PROVIDER.get_and_increment(),
|
|
||||||
app_data=bytes(PING_PAYLOAD_SIZE),
|
|
||||||
)
|
|
||||||
self.verificator.add_tc(ping_tc)
|
|
||||||
self.com_if.send(bytes(ping_tc.pack()))
|
|
||||||
self.await_for_command_completion("ping command")
|
|
||||||
|
|
||||||
def handle_corruption_cmd(self, target: Target):
|
|
||||||
if target == Target.BOOTLOADER:
|
|
||||||
_LOGGER.error("can not corrupt bootloader")
|
|
||||||
if target == Target.APP_A:
|
|
||||||
self.send_tc(
|
|
||||||
PusTc(
|
|
||||||
apid=0,
|
|
||||||
service=ACTION_SERVICE,
|
|
||||||
subservice=ActionId.CORRUPT_APP_A,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if target == Target.APP_B:
|
|
||||||
self.send_tc(
|
|
||||||
PusTc(
|
|
||||||
apid=0,
|
|
||||||
service=ACTION_SERVICE,
|
|
||||||
subservice=ActionId.CORRUPT_APP_B,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def await_for_command_completion(self, context: str):
|
|
||||||
done = False
|
|
||||||
now = time.time()
|
|
||||||
while time.time() - now < 2.0:
|
|
||||||
if self.com_if.data_available() == 0:
|
|
||||||
time.sleep(0.2)
|
|
||||||
continue
|
|
||||||
for reply in self.com_if.receive():
|
|
||||||
result = self.verificator.add_tm(
|
|
||||||
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
|
|
||||||
)
|
|
||||||
if result is not None and result.completed:
|
|
||||||
_LOGGER.info(f"received {context} reply")
|
|
||||||
done = True
|
|
||||||
if done:
|
|
||||||
break
|
|
||||||
if not done:
|
|
||||||
_LOGGER.warning(f"no {context} reply received")
|
|
||||||
|
|
||||||
def handle_flash_cmd(self, target: Target, file_path: Path) -> int:
|
|
||||||
loadable_segments = []
|
|
||||||
_LOGGER.info("Parsing ELF file for loadable sections")
|
|
||||||
total_size = 0
|
|
||||||
loadable_segments, total_size = create_loadable_segments(target, file_path)
|
|
||||||
segments_info_str(target, loadable_segments, total_size, file_path)
|
|
||||||
result = self._perform_flashing_algorithm(loadable_segments)
|
|
||||||
if result != 0:
|
|
||||||
return result
|
|
||||||
self._crc_and_app_size_postprocessing(target, total_size, loadable_segments)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def _perform_flashing_algorithm(
|
|
||||||
self,
|
|
||||||
loadable_segments: List[LoadableSegment],
|
|
||||||
) -> int:
|
|
||||||
# Perform the flashing algorithm.
|
|
||||||
for segment in loadable_segments:
|
|
||||||
segment_end = segment.offset + segment.size
|
|
||||||
current_addr = segment.offset
|
|
||||||
pos_in_segment = 0
|
|
||||||
while pos_in_segment < segment.size:
|
|
||||||
next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
|
|
||||||
data = segment.data[pos_in_segment : pos_in_segment + next_chunk_size]
|
|
||||||
next_packet = pack_memory_write_command(current_addr, data)
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Sending memory write command for address {current_addr:#08x} and data with "
|
|
||||||
f"length {len(data)}"
|
|
||||||
)
|
|
||||||
self.verificator.add_tc(next_packet)
|
|
||||||
self.com_if.send(bytes(next_packet.pack()))
|
|
||||||
current_addr += next_chunk_size
|
|
||||||
pos_in_segment += next_chunk_size
|
|
||||||
start_time = time.time()
|
|
||||||
while True:
|
|
||||||
if time.time() - start_time > 1.0:
|
|
||||||
_LOGGER.error("Timeout while waiting for reply")
|
|
||||||
return -1
|
|
||||||
data_available = self.com_if.data_available(0.1)
|
|
||||||
done = False
|
|
||||||
if not data_available:
|
|
||||||
continue
|
|
||||||
replies = self.com_if.receive()
|
|
||||||
for reply in replies:
|
|
||||||
tm = PusTm.unpack(reply, 0)
|
|
||||||
if tm.service != 1:
|
|
||||||
continue
|
|
||||||
service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
|
|
||||||
check_result = self.verificator.add_tm(service_1_tm)
|
|
||||||
# We could send after we have received the step reply, but that can
|
|
||||||
# somehow lead to overrun errors. I think it's okay to do it like
|
|
||||||
# this as long as the flash loader only uses polling..
|
|
||||||
if (
|
|
||||||
check_result is not None
|
|
||||||
and check_result.status.completed == StatusField.SUCCESS
|
|
||||||
):
|
|
||||||
done = True
|
|
||||||
|
|
||||||
# This is an optimized variant, but I think the small delay is not an issue.
|
|
||||||
"""
|
|
||||||
if (
|
|
||||||
check_result is not None
|
|
||||||
and check_result.status.step == StatusField.SUCCESS
|
|
||||||
and len(check_result.status.step_list) == 1
|
|
||||||
):
|
|
||||||
done = True
|
|
||||||
"""
|
|
||||||
self.verificator.remove_completed_entries()
|
|
||||||
if done:
|
|
||||||
break
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def _crc_and_app_size_postprocessing(
|
|
||||||
self,
|
|
||||||
target: Target,
|
|
||||||
total_size: int,
|
|
||||||
loadable_segments: List[LoadableSegment],
|
|
||||||
):
|
|
||||||
if target == Target.BOOTLOADER:
|
|
||||||
_LOGGER.info("Blanking the bootloader checksum")
|
|
||||||
# Blank the checksum. For the bootloader, the bootloader will calculate the
|
|
||||||
# checksum itself on the initial run.
|
|
||||||
checksum_write_packet = pack_memory_write_command(
|
|
||||||
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
|
|
||||||
)
|
|
||||||
self.send_tc(checksum_write_packet)
|
|
||||||
else:
|
|
||||||
crc_addr = None
|
|
||||||
size_addr = None
|
|
||||||
if target == Target.APP_A:
|
|
||||||
crc_addr = APP_A_CRC_ADDR
|
|
||||||
size_addr = APP_A_SIZE_ADDR
|
|
||||||
elif target == Target.APP_B:
|
|
||||||
crc_addr = APP_B_CRC_ADDR
|
|
||||||
size_addr = APP_B_SIZE_ADDR
|
|
||||||
assert crc_addr is not None
|
|
||||||
assert size_addr is not None
|
|
||||||
_LOGGER.info(f"Writing app size {total_size} at address {size_addr:#08x}")
|
|
||||||
size_write_packet = pack_memory_write_command(
|
|
||||||
size_addr, struct.pack("!I", total_size)
|
|
||||||
)
|
|
||||||
self.com_if.send(bytes(size_write_packet.pack()))
|
|
||||||
time.sleep(0.2)
|
|
||||||
crc_calc = PredefinedCrc("crc-32")
|
|
||||||
for segment in loadable_segments:
|
|
||||||
crc_calc.update(segment.data)
|
|
||||||
checksum = crc_calc.digest()
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
|
|
||||||
)
|
|
||||||
self.send_tc(pack_memory_write_command(crc_addr, checksum))
|
|
||||||
|
|
||||||
def send_tc(self, tc: PusTc):
|
|
||||||
self.com_if.send(bytes(tc.pack()))
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
print("Python VA416XX Image Loader Application")
|
|
||||||
logging.basicConfig(
|
|
||||||
format="[%(asctime)s] [%(levelname)s] %(message)s", level=logging.DEBUG
|
|
||||||
)
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog="image-loader", description="Python VA416XX Image Loader Application"
|
|
||||||
)
|
|
||||||
parser.add_argument("-p", "--ping", action="store_true", help="Send ping command")
|
|
||||||
parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target")
|
|
||||||
parser.add_argument(
|
|
||||||
"-t",
|
|
||||||
"--target",
|
|
||||||
choices=["bl", "a", "b"],
|
|
||||||
help="Target (Bootloader or slot A or B)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"path", nargs="?", default=None, help="Path to the App to flash"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
serial_port = None
|
|
||||||
if Path("loader.toml").exists():
|
|
||||||
with open("loader.toml", "r") as toml_file:
|
|
||||||
parsed_toml = toml.loads(toml_file.read())
|
|
||||||
if "serial_port" in parsed_toml:
|
|
||||||
serial_port = parsed_toml["serial_port"]
|
|
||||||
if serial_port is None:
|
|
||||||
serial_port = input("Please specify the serial port manually: ")
|
|
||||||
serial_cfg = SerialCfg(
|
|
||||||
com_if_id="ser_cobs",
|
|
||||||
serial_port=serial_port,
|
|
||||||
baud_rate=BAUD_RATE,
|
|
||||||
polling_frequency=0.1,
|
|
||||||
)
|
|
||||||
verificator = PusVerificator()
|
|
||||||
com_if = SerialCobsComIF(serial_cfg)
|
|
||||||
com_if.open()
|
|
||||||
target = None
|
|
||||||
if args.target == "bl":
|
|
||||||
target = Target.BOOTLOADER
|
|
||||||
elif args.target == "a":
|
|
||||||
target = Target.APP_A
|
|
||||||
elif args.target == "b":
|
|
||||||
target = Target.APP_B
|
|
||||||
image_loader = ImageLoader(com_if, verificator)
|
|
||||||
file_path = None
|
|
||||||
result = -1
|
|
||||||
if args.ping:
|
|
||||||
image_loader.handle_ping_cmd()
|
|
||||||
com_if.close()
|
|
||||||
return 0
|
|
||||||
if target:
|
|
||||||
if not args.corrupt:
|
|
||||||
if not args.path:
|
|
||||||
_LOGGER.error("App Path needs to be specified for the flash process")
|
|
||||||
file_path = Path(args.path)
|
|
||||||
if not file_path.exists():
|
|
||||||
_LOGGER.error("File does not exist")
|
|
||||||
if args.corrupt:
|
|
||||||
if not target:
|
|
||||||
_LOGGER.error("target for corruption command required")
|
|
||||||
com_if.close()
|
|
||||||
return -1
|
|
||||||
image_loader.handle_corruption_cmd(target)
|
|
||||||
else:
|
|
||||||
assert file_path is not None
|
|
||||||
assert target is not None
|
|
||||||
result = image_loader.handle_flash_cmd(target, file_path)
|
|
||||||
|
|
||||||
com_if.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def create_loadable_segments(
|
|
||||||
target: Target, file_path: Path
|
|
||||||
) -> Tuple[List[LoadableSegment], int]:
|
|
||||||
loadable_segments = []
|
|
||||||
total_size = 0
|
|
||||||
with open(file_path, "rb") as app_file:
|
|
||||||
elf_file = ELFFile(app_file)
|
|
||||||
|
|
||||||
for idx, segment in enumerate(elf_file.iter_segments("PT_LOAD")):
|
|
||||||
if segment.header.p_filesz == 0:
|
|
||||||
continue
|
|
||||||
# Basic validity checks of the base addresses.
|
|
||||||
if idx == 0:
|
|
||||||
if (
|
|
||||||
target == Target.BOOTLOADER
|
|
||||||
and segment.header.p_paddr != BOOTLOADER_START_ADDR
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
|
||||||
f"bootloader, expected {BOOTLOADER_START_ADDR}"
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
target == Target.APP_A
|
|
||||||
and segment.header.p_paddr != APP_A_START_ADDR
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
|
||||||
f"App A, expected {APP_A_START_ADDR}"
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
target == Target.APP_B
|
|
||||||
and segment.header.p_paddr != APP_B_START_ADDR
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
|
|
||||||
f"App B, expected {APP_B_START_ADDR}"
|
|
||||||
)
|
|
||||||
name = None
|
|
||||||
for section in elf_file.iter_sections():
|
|
||||||
if (
|
|
||||||
section.header.sh_offset == segment.header.p_offset
|
|
||||||
and section.header.sh_size > 0
|
|
||||||
):
|
|
||||||
name = section.name
|
|
||||||
if name is None:
|
|
||||||
_LOGGER.warning("no fitting section found for segment")
|
|
||||||
continue
|
|
||||||
# print(f"Segment Addr: {segment.header.p_paddr}")
|
|
||||||
# print(f"Segment Offset: {segment.header.p_offset}")
|
|
||||||
# print(f"Segment Filesize: {segment.header.p_filesz}")
|
|
||||||
loadable_segments.append(
|
|
||||||
LoadableSegment(
|
|
||||||
name=name,
|
|
||||||
offset=segment.header.p_paddr,
|
|
||||||
size=segment.header.p_filesz,
|
|
||||||
data=segment.data(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
total_size += segment.header.p_filesz
|
|
||||||
return loadable_segments, total_size
|
|
||||||
|
|
||||||
|
|
||||||
def segments_info_str(
|
|
||||||
target: Target,
|
|
||||||
loadable_segments: List[LoadableSegment],
|
|
||||||
total_size: int,
|
|
||||||
file_path: Path,
|
|
||||||
):
|
|
||||||
# Set context string and perform basic sanity checks.
|
|
||||||
if target == Target.BOOTLOADER:
|
|
||||||
if total_size > BOOTLOADER_MAX_SIZE:
|
|
||||||
_LOGGER.error(
|
|
||||||
f"provided bootloader app larger than allowed {total_size} bytes"
|
|
||||||
)
|
|
||||||
return -1
|
|
||||||
context_str = "Bootloader"
|
|
||||||
elif target == Target.APP_A:
|
|
||||||
if total_size > APP_A_MAX_SIZE:
|
|
||||||
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes")
|
|
||||||
return -1
|
|
||||||
context_str = "App Slot A"
|
|
||||||
elif target == Target.APP_B:
|
|
||||||
if total_size > APP_B_MAX_SIZE:
|
|
||||||
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes")
|
|
||||||
return -1
|
|
||||||
context_str = "App Slot B"
|
|
||||||
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})")
|
|
||||||
for idx, segment in enumerate(loadable_segments):
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and "
|
|
||||||
f"size {segment.size}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
|
|
||||||
app_data = bytearray()
|
|
||||||
app_data.append(BOOT_NVM_MEMORY_ID)
|
|
||||||
# N parameter is always 1 here.
|
|
||||||
app_data.append(1)
|
|
||||||
app_data.extend(struct.pack("!I", addr))
|
|
||||||
app_data.extend(struct.pack("!I", len(data)))
|
|
||||||
app_data.extend(data)
|
|
||||||
return PusTc(
|
|
||||||
apid=0,
|
|
||||||
service=MEMORY_SERVICE,
|
|
||||||
subservice=RAW_MEMORY_WRITE_SUBSERVICE,
|
|
||||||
seq_count=SEQ_PROVIDER.get_and_increment(),
|
|
||||||
app_data=bytes(app_data),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1 +0,0 @@
|
|||||||
serial_port = "/dev/ttyUSB0"
|
|
@ -1,5 +0,0 @@
|
|||||||
spacepackets == 0.28
|
|
||||||
com-interface == 0.1.0
|
|
||||||
toml == 0.10
|
|
||||||
pyelftools == 0.31
|
|
||||||
crcmod == 1.7
|
|
2
flashloader/slot-a-blinky/.gitignore
vendored
2
flashloader/slot-a-blinky/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
/target
|
|
||||||
/app.map
|
|
@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "slot-a-blinky"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
embedded-hal = "1"
|
|
||||||
va416xx-hal = { path = "0.4", features = ["va41630"] }
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
# This is problematic for stepping..
|
|
||||||
# opt-level = 'z' # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo build/run --release
|
|
||||||
[profile.release]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
|
|
||||||
[profile.small]
|
|
||||||
inherits = "release"
|
|
||||||
codegen-units = 1
|
|
||||||
debug-assertions = false # <-
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z' # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
# strip = true # Automatically strip symbols from the binary.
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot A with an offset at address 0x4000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00004000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
//! Simple blinky example using the HAL
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
use embedded_hal::digital::StatefulOutputPin;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va416xx_hal::{gpio::PinsG, pac};
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("VA416xx HAL blinky example for App Slot A");
|
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
|
||||||
let mut led = portg.pg5.into_readable_push_pull_output();
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::delay(1_000_000);
|
|
||||||
led.toggle().ok();
|
|
||||||
}
|
|
||||||
}
|
|
2
flashloader/slot-b-blinky/.gitignore
vendored
2
flashloader/slot-b-blinky/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
/target
|
|
||||||
/app.map
|
|
@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "slot-b-blinky"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cortex-m-rt = "0.7"
|
|
||||||
panic-rtt-target = { version = "0.1.3" }
|
|
||||||
rtt-target = { version = "0.5" }
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
|
||||||
embedded-hal = "1"
|
|
||||||
va416xx-hal = { path = "0.4", features = ["va41630"] }
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = true # <-
|
|
||||||
incremental = false
|
|
||||||
# This is problematic for stepping..
|
|
||||||
# opt-level = 'z' # <-
|
|
||||||
overflow-checks = true # <-
|
|
||||||
|
|
||||||
# cargo build/run --release
|
|
||||||
[profile.release]
|
|
||||||
codegen-units = 1
|
|
||||||
debug = 2
|
|
||||||
debug-assertions = false # <-
|
|
||||||
incremental = false
|
|
||||||
lto = 'fat'
|
|
||||||
opt-level = 3 # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
|
|
||||||
[profile.small]
|
|
||||||
inherits = "release"
|
|
||||||
codegen-units = 1
|
|
||||||
debug-assertions = false # <-
|
|
||||||
lto = true
|
|
||||||
opt-level = 'z' # <-
|
|
||||||
overflow-checks = false # <-
|
|
||||||
# strip = true # Automatically strip symbols from the binary.
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot B with an offset at address 0x22000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00022000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
//! Simple blinky example using the HAL
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
|
||||||
use embedded_hal::digital::StatefulOutputPin;
|
|
||||||
use panic_rtt_target as _;
|
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
|
||||||
use va416xx_hal::{gpio::PinsG, pac};
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
rtt_init_print!();
|
|
||||||
rprintln!("VA416xx HAL blinky example for App Slot B");
|
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
|
||||||
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
|
||||||
let mut led = portg.pg5.into_readable_push_pull_output();
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::delay(8_000_000);
|
|
||||||
led.toggle().ok();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,537 +0,0 @@
|
|||||||
//! Vorago flashloader which can be used to flash image A and image B via a simple
|
|
||||||
//! low-level CCSDS memory interface via a UART wire.
|
|
||||||
//!
|
|
||||||
//! This flash loader can be used after the bootloader was flashed to flash the images.
|
|
||||||
//! You can also use this as an starting application for a software update mechanism.
|
|
||||||
//!
|
|
||||||
//! Bootloader memory map
|
|
||||||
//!
|
|
||||||
//! * <0x0> Bootloader start <code up to 0x3FFE bytes>
|
|
||||||
//! * <0x3FFE> Bootloader CRC <halfword>
|
|
||||||
//! * <0x4000> App image A start <code up to 0x1DFFC (~120K) bytes>
|
|
||||||
//! * <0x21FFC> App image A CRC check length <halfword>
|
|
||||||
//! * <0x21FFE> App image A CRC check value <halfword>
|
|
||||||
//! * <0x22000> App image B start <code up to 0x1DFFC (~120K) bytes>
|
|
||||||
//! * <0x3FFFC> App image B CRC check length <halfword>
|
|
||||||
//! * <0x3FFFE> App image B CRC check value <halfword>
|
|
||||||
//! * <0x40000> <end>
|
|
||||||
#![no_main]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
|
|
||||||
|
|
||||||
const EXTCLK_FREQ: u32 = 40_000_000;
|
|
||||||
|
|
||||||
const MAX_TC_SIZE: usize = 1024;
|
|
||||||
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
|
|
||||||
|
|
||||||
const MAX_TM_SIZE: usize = 128;
|
|
||||||
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
|
|
||||||
|
|
||||||
const UART_BAUDRATE: u32 = 115200;
|
|
||||||
const BOOT_NVM_MEMORY_ID: u8 = 1;
|
|
||||||
const RX_DEBUGGING: bool = false;
|
|
||||||
const TX_DEBUGGING: bool = false;
|
|
||||||
|
|
||||||
pub enum ActionId {
|
|
||||||
CorruptImageA = 128,
|
|
||||||
CorruptImageB = 129,
|
|
||||||
}
|
|
||||||
pub trait WdtInterface {
|
|
||||||
fn feed(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OptWdt(Option<Wdt>);
|
|
||||||
|
|
||||||
impl WdtInterface for OptWdt {
|
|
||||||
fn feed(&self) {
|
|
||||||
if self.0.is_some() {
|
|
||||||
self.0.as_ref().unwrap().feed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use ringbuf::{
|
|
||||||
traits::{Consumer, Observer, Producer, SplitRef},
|
|
||||||
CachingCons, StaticProd, StaticRb,
|
|
||||||
};
|
|
||||||
use static_cell::StaticCell;
|
|
||||||
|
|
||||||
// Larger buffer for TC to be able to hold the possibly large memory write packets.
|
|
||||||
const BUF_RB_SIZE_TC: usize = 2048;
|
|
||||||
const SIZES_RB_SIZE_TC: usize = 16;
|
|
||||||
|
|
||||||
const BUF_RB_SIZE_TM: usize = 512;
|
|
||||||
const SIZES_RB_SIZE_TM: usize = 16;
|
|
||||||
|
|
||||||
// Ring buffers to handling variable sized telemetry
|
|
||||||
static BUF_RB_TM: StaticCell<StaticRb<u8, BUF_RB_SIZE_TM>> = StaticCell::new();
|
|
||||||
static SIZES_RB_TM: StaticCell<StaticRb<usize, SIZES_RB_SIZE_TM>> = StaticCell::new();
|
|
||||||
|
|
||||||
// Ring buffers to handling variable sized telecommands
|
|
||||||
static BUF_RB_TC: StaticCell<StaticRb<u8, BUF_RB_SIZE_TC>> = StaticCell::new();
|
|
||||||
static SIZES_RB_TC: StaticCell<StaticRb<usize, SIZES_RB_SIZE_TC>> = StaticCell::new();
|
|
||||||
|
|
||||||
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
|
||||||
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
|
|
||||||
pub sizes_prod: StaticProd<'static, usize, SIZES_LEN>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DataConsumer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
|
|
||||||
pub buf_cons: CachingCons<&'static StaticRb<u8, BUF_SIZE>>,
|
|
||||||
pub sizes_cons: CachingCons<&'static StaticRb<usize, SIZES_LEN>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
static CLOCKS: OnceCell<Clocks> = OnceCell::new();
|
|
||||||
|
|
||||||
pub const APP_A_START_ADDR: u32 = 0x4000;
|
|
||||||
pub const APP_A_END_ADDR: u32 = 0x22000;
|
|
||||||
pub const APP_B_START_ADDR: u32 = 0x22000;
|
|
||||||
pub const APP_B_END_ADDR: u32 = 0x40000;
|
|
||||||
|
|
||||||
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
|
|
||||||
mod app {
|
|
||||||
use super::*;
|
|
||||||
use cortex_m::asm;
|
|
||||||
use embedded_io::Write;
|
|
||||||
// Import panic provider.
|
|
||||||
use panic_probe as _;
|
|
||||||
// Import logger.
|
|
||||||
use defmt_rtt as _;
|
|
||||||
use rtic::Mutex;
|
|
||||||
use rtic_monotonics::systick::prelude::*;
|
|
||||||
use satrs::pus::verification::VerificationReportCreator;
|
|
||||||
use spacepackets::ecss::PusServiceId;
|
|
||||||
use spacepackets::ecss::{
|
|
||||||
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
|
|
||||||
};
|
|
||||||
use va416xx_hal::clock::ClockConfigurator;
|
|
||||||
use va416xx_hal::irq_router::enable_and_init_irq_router;
|
|
||||||
use va416xx_hal::uart::IrqContextTimeoutOrMaxSize;
|
|
||||||
use va416xx_hal::{
|
|
||||||
edac,
|
|
||||||
nvm::Nvm,
|
|
||||||
pac,
|
|
||||||
pins::PinsG,
|
|
||||||
uart::{self, Uart},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{setup_edac, EXTCLK_FREQ};
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum CobsReaderStates {
|
|
||||||
#[default]
|
|
||||||
WaitingForStart,
|
|
||||||
WatingForEnd,
|
|
||||||
FrameOverflow,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[local]
|
|
||||||
struct Local {
|
|
||||||
uart_rx: uart::RxWithInterrupt,
|
|
||||||
uart_tx: uart::Tx,
|
|
||||||
rx_context: IrqContextTimeoutOrMaxSize,
|
|
||||||
rom_spi: Option<pac::Spi3>,
|
|
||||||
// We handle all TM in one task.
|
|
||||||
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
|
||||||
// We consume all TC in one task.
|
|
||||||
tc_cons: DataConsumer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
|
||||||
// We produce all TC in one task.
|
|
||||||
tc_prod: DataProducer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
|
|
||||||
verif_reporter: VerificationReportCreator,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[shared]
|
|
||||||
struct Shared {
|
|
||||||
// Having this shared allows multiple tasks to generate telemetry.
|
|
||||||
tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
|
|
||||||
}
|
|
||||||
|
|
||||||
rtic_monotonics::systick_monotonic!(Mono, 10_000);
|
|
||||||
|
|
||||||
#[init]
|
|
||||||
fn init(mut cx: init::Context) -> (Shared, Local) {
|
|
||||||
defmt::println!("-- Vorago flashloader --");
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
// Use the external clock connected to XTAL_N.
|
|
||||||
let clocks = ClockConfigurator::new(cx.device.clkgen)
|
|
||||||
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
|
||||||
.freeze()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
enable_and_init_irq_router();
|
|
||||||
setup_edac(&mut cx.device.sysconfig);
|
|
||||||
|
|
||||||
let gpiog = PinsG::new(cx.device.portg);
|
|
||||||
|
|
||||||
let uart0 = Uart::new(
|
|
||||||
cx.device.uart0,
|
|
||||||
gpiog.pg0,
|
|
||||||
gpiog.pg1,
|
|
||||||
&clocks,
|
|
||||||
Hertz::from_raw(UART_BAUDRATE).into(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let (tx, rx) = uart0.split();
|
|
||||||
|
|
||||||
let verif_reporter = VerificationReportCreator::new(0).unwrap();
|
|
||||||
|
|
||||||
let (buf_prod_tm, buf_cons_tm) = BUF_RB_TM
|
|
||||||
.init(StaticRb::<u8, BUF_RB_SIZE_TM>::default())
|
|
||||||
.split_ref();
|
|
||||||
let (sizes_prod_tm, sizes_cons_tm) = SIZES_RB_TM
|
|
||||||
.init(StaticRb::<usize, SIZES_RB_SIZE_TM>::default())
|
|
||||||
.split_ref();
|
|
||||||
|
|
||||||
let (buf_prod_tc, buf_cons_tc) = BUF_RB_TC
|
|
||||||
.init(StaticRb::<u8, BUF_RB_SIZE_TC>::default())
|
|
||||||
.split_ref();
|
|
||||||
let (sizes_prod_tc, sizes_cons_tc) = SIZES_RB_TC
|
|
||||||
.init(StaticRb::<usize, SIZES_RB_SIZE_TC>::default())
|
|
||||||
.split_ref();
|
|
||||||
|
|
||||||
Mono::start(cx.core.SYST, clocks.sysclk().raw());
|
|
||||||
CLOCKS.set(clocks).unwrap();
|
|
||||||
|
|
||||||
let mut rx = rx.into_rx_with_irq();
|
|
||||||
let mut rx_context = IrqContextTimeoutOrMaxSize::new(MAX_TC_FRAME_SIZE);
|
|
||||||
rx.read_fixed_len_or_timeout_based_using_irq(&mut rx_context)
|
|
||||||
.expect("initiating UART RX failed");
|
|
||||||
pus_tc_handler::spawn().unwrap();
|
|
||||||
pus_tm_tx_handler::spawn().unwrap();
|
|
||||||
(
|
|
||||||
Shared {
|
|
||||||
tm_prod: DataProducer {
|
|
||||||
buf_prod: buf_prod_tm,
|
|
||||||
sizes_prod: sizes_prod_tm,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Local {
|
|
||||||
uart_rx: rx,
|
|
||||||
uart_tx: tx,
|
|
||||||
rx_context,
|
|
||||||
rom_spi: Some(cx.device.spi3),
|
|
||||||
tm_cons: DataConsumer {
|
|
||||||
buf_cons: buf_cons_tm,
|
|
||||||
sizes_cons: sizes_cons_tm,
|
|
||||||
},
|
|
||||||
tc_cons: DataConsumer {
|
|
||||||
buf_cons: buf_cons_tc,
|
|
||||||
sizes_cons: sizes_cons_tc,
|
|
||||||
},
|
|
||||||
tc_prod: DataProducer {
|
|
||||||
buf_prod: buf_prod_tc,
|
|
||||||
sizes_prod: sizes_prod_tc,
|
|
||||||
},
|
|
||||||
verif_reporter,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `shared` cannot be accessed from this context
|
|
||||||
#[idle]
|
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
|
||||||
loop {
|
|
||||||
asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the interrupt handler to read all bytes received on the UART0.
|
|
||||||
#[task(
|
|
||||||
binds = UART0_RX,
|
|
||||||
local = [
|
|
||||||
cnt: u32 = 0,
|
|
||||||
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
|
|
||||||
rx_context,
|
|
||||||
uart_rx,
|
|
||||||
tc_prod
|
|
||||||
],
|
|
||||||
)]
|
|
||||||
fn uart_rx_irq(cx: uart_rx_irq::Context) {
|
|
||||||
match cx
|
|
||||||
.local
|
|
||||||
.uart_rx
|
|
||||||
.on_interrupt_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
|
|
||||||
{
|
|
||||||
Ok(result) => {
|
|
||||||
if RX_DEBUGGING {
|
|
||||||
defmt::info!("RX Info: {:?}", cx.local.rx_context);
|
|
||||||
defmt::info!("RX Result: {:?}", result);
|
|
||||||
}
|
|
||||||
if result.complete() {
|
|
||||||
// Check frame validity (must have COBS format) and decode the frame.
|
|
||||||
// Currently, we expect a full frame or a frame received through a timeout
|
|
||||||
// to be one COBS frame. We could parse for multiple COBS packets in one
|
|
||||||
// frame, but the additional complexity is not necessary here..
|
|
||||||
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
|
|
||||||
let decoded_size =
|
|
||||||
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
|
|
||||||
if decoded_size.is_err() {
|
|
||||||
defmt::warn!("COBS decoding failed");
|
|
||||||
} else {
|
|
||||||
let decoded_size = decoded_size.unwrap();
|
|
||||||
if cx.local.tc_prod.sizes_prod.vacant_len() >= 1
|
|
||||||
&& cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size
|
|
||||||
{
|
|
||||||
// Should never fail, we checked there is enough space.
|
|
||||||
cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap();
|
|
||||||
cx.local
|
|
||||||
.tc_prod
|
|
||||||
.buf_prod
|
|
||||||
.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
|
|
||||||
} else {
|
|
||||||
defmt::warn!("COBS TC queue full");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defmt::warn!(
|
|
||||||
"COBS frame with invalid format, start and end bytes are not 0"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate next transfer.
|
|
||||||
cx.local
|
|
||||||
.uart_rx
|
|
||||||
.read_fixed_len_or_timeout_based_using_irq(cx.local.rx_context)
|
|
||||||
.expect("read operation failed");
|
|
||||||
}
|
|
||||||
if result.has_errors() {
|
|
||||||
defmt::warn!("UART error: {:?}", result.errors.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("UART error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
priority = 2,
|
|
||||||
local=[
|
|
||||||
tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
|
|
||||||
src_data_buf: [u8; 16] = [0; 16],
|
|
||||||
verif_buf: [u8; 32] = [0; 32],
|
|
||||||
tc_cons,
|
|
||||||
rom_spi,
|
|
||||||
verif_reporter
|
|
||||||
],
|
|
||||||
shared=[tm_prod]
|
|
||||||
)]
|
|
||||||
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
|
|
||||||
loop {
|
|
||||||
// Try to read a TC from the ring buffer.
|
|
||||||
let packet_len = cx.local.tc_cons.sizes_cons.try_pop();
|
|
||||||
if packet_len.is_none() {
|
|
||||||
// Small delay, TCs might arrive very quickly.
|
|
||||||
Mono::delay(20.millis()).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let packet_len = packet_len.unwrap();
|
|
||||||
defmt::info!("received packet with length {}", packet_len);
|
|
||||||
assert_eq!(
|
|
||||||
cx.local
|
|
||||||
.tc_cons
|
|
||||||
.buf_cons
|
|
||||||
.pop_slice(&mut cx.local.tc_buf[0..packet_len]),
|
|
||||||
packet_len
|
|
||||||
);
|
|
||||||
// Read a telecommand, now handle it.
|
|
||||||
handle_valid_pus_tc(&mut cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
|
|
||||||
let pus_tc = PusTcReader::new(cx.local.tc_buf);
|
|
||||||
if pus_tc.is_err() {
|
|
||||||
defmt::warn!("PUS TC error: {}", pus_tc.unwrap_err());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let (pus_tc, _) = pus_tc.unwrap();
|
|
||||||
let mut write_and_send = |tm: &PusTmCreator| {
|
|
||||||
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
|
|
||||||
cx.shared.tm_prod.lock(|prod| {
|
|
||||||
prod.sizes_prod.try_push(tm.len_written()).unwrap();
|
|
||||||
prod.buf_prod
|
|
||||||
.push_slice(&cx.local.verif_buf[0..written_size]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let token = cx.local.verif_reporter.add_tc(&pus_tc);
|
|
||||||
let (tm, accepted_token) = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
|
|
||||||
.expect("acceptance success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
|
|
||||||
let (tm, started_token) = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
|
|
||||||
.expect("acceptance success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
|
|
||||||
if pus_tc.service() == PusServiceId::Action as u8 {
|
|
||||||
let mut corrupt_image = |base_addr: u32| {
|
|
||||||
// Safety: We only use this for NVM handling and we only do NVM
|
|
||||||
// handling here.
|
|
||||||
let nvm = Nvm::new(
|
|
||||||
cx.local.rom_spi.take().unwrap(),
|
|
||||||
CLOCKS.get().as_ref().unwrap(),
|
|
||||||
);
|
|
||||||
let mut buf = [0u8; 4];
|
|
||||||
nvm.read_data(base_addr + 32, &mut buf);
|
|
||||||
buf[0] += 1;
|
|
||||||
nvm.write_data(base_addr + 32, &buf);
|
|
||||||
*cx.local.rom_spi = Some(nvm.release());
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
|
||||||
.expect("completion success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
};
|
|
||||||
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
|
|
||||||
defmt::info!("corrupting App Image A");
|
|
||||||
corrupt_image(APP_A_START_ADDR);
|
|
||||||
}
|
|
||||||
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
|
|
||||||
defmt::info!("corrupting App Image B");
|
|
||||||
corrupt_image(APP_B_START_ADDR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
|
|
||||||
defmt::info!("received ping TC");
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
|
||||||
.expect("completion success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.step_success(
|
|
||||||
cx.local.src_data_buf,
|
|
||||||
&started_token,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
&[],
|
|
||||||
EcssEnumU8::new(0),
|
|
||||||
)
|
|
||||||
.expect("step success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
// Raw memory write TC
|
|
||||||
if pus_tc.subservice() == 2 {
|
|
||||||
let app_data = pus_tc.app_data();
|
|
||||||
if app_data.len() < 10 {
|
|
||||||
defmt::warn!(
|
|
||||||
"app data for raw memory write is too short: {}",
|
|
||||||
app_data.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let memory_id = app_data[0];
|
|
||||||
if memory_id != BOOT_NVM_MEMORY_ID {
|
|
||||||
defmt::warn!("memory ID {} not supported", memory_id);
|
|
||||||
// TODO: Error reporting
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
|
|
||||||
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
|
|
||||||
if 10 + data_len as usize > app_data.len() {
|
|
||||||
defmt::warn!(
|
|
||||||
"invalid data length {} for raw mem write detected",
|
|
||||||
data_len
|
|
||||||
);
|
|
||||||
// TODO: Error reporting
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = &app_data[10..10 + data_len as usize];
|
|
||||||
defmt::info!("writing {} bytes at offset {} to NVM", data_len, offset);
|
|
||||||
// Safety: We only use this for NVM handling and we only do NVM
|
|
||||||
// handling here.
|
|
||||||
let nvm = Nvm::new(
|
|
||||||
cx.local.rom_spi.take().unwrap(),
|
|
||||||
CLOCKS.get().as_ref().unwrap(),
|
|
||||||
);
|
|
||||||
nvm.write_data(offset, data);
|
|
||||||
*cx.local.rom_spi = Some(nvm.release());
|
|
||||||
let tm = cx
|
|
||||||
.local
|
|
||||||
.verif_reporter
|
|
||||||
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
|
|
||||||
.expect("completion success failed");
|
|
||||||
write_and_send(&tm);
|
|
||||||
defmt::info!("NVM operation done");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(
|
|
||||||
priority = 1,
|
|
||||||
local=[
|
|
||||||
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
|
|
||||||
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
|
|
||||||
uart_tx,
|
|
||||||
tm_cons
|
|
||||||
],
|
|
||||||
shared=[]
|
|
||||||
)]
|
|
||||||
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
|
|
||||||
loop {
|
|
||||||
while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
|
|
||||||
let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
|
|
||||||
cx.local
|
|
||||||
.tm_cons
|
|
||||||
.buf_cons
|
|
||||||
.pop_slice(&mut cx.local.read_buf[0..next_size]);
|
|
||||||
cx.local.encoded_buf[0] = 0;
|
|
||||||
let send_size = cobs::encode(
|
|
||||||
&cx.local.read_buf[0..next_size],
|
|
||||||
&mut cx.local.encoded_buf[1..],
|
|
||||||
);
|
|
||||||
cx.local.encoded_buf[send_size + 1] = 0;
|
|
||||||
if TX_DEBUGGING {
|
|
||||||
defmt::debug!("UART TX: Sending data with size {}", send_size + 2);
|
|
||||||
}
|
|
||||||
cx.local
|
|
||||||
.uart_tx
|
|
||||||
.write_all(&cx.local.encoded_buf[0..send_size + 2])
|
|
||||||
.unwrap();
|
|
||||||
Mono::delay(2.millis()).await;
|
|
||||||
}
|
|
||||||
Mono::delay(50.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = EDAC_SBE, priority = 1)]
|
|
||||||
fn edac_sbe_isr(_cx: edac_sbe_isr::Context) {
|
|
||||||
// TODO: Send some command via UART for notification purposes. Also identify the problematic
|
|
||||||
// memory.
|
|
||||||
edac::clear_sbe_irq();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = EDAC_MBE, priority = 1)]
|
|
||||||
fn edac_mbe_isr(_cx: edac_mbe_isr::Context) {
|
|
||||||
// TODO: Send some command via UART for notification purposes.
|
|
||||||
edac::clear_mbe_irq();
|
|
||||||
// TODO: Reset like the vorago example?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = WATCHDOG, priority = 1)]
|
|
||||||
fn watchdog_isr(_cx: watchdog_isr::Context) {
|
|
||||||
let wdt = unsafe { pac::WatchDog::steal() };
|
|
||||||
// Clear interrupt.
|
|
||||||
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_edac(syscfg: &mut pac::Sysconfig) {
|
|
||||||
// The scrub values are based on the Vorago provided bootloader.
|
|
||||||
edac::enable_rom_scrub(syscfg, 125);
|
|
||||||
edac::enable_ram0_scrub(syscfg, 1000);
|
|
||||||
edac::enable_ram1_scrub(syscfg, 1000);
|
|
||||||
edac::enable_sbe_irq();
|
|
||||||
edac::enable_mbe_irq();
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Start the JLinkGDBServer while also specifying the JLinkScript file. The JLinkScript is necessary
|
|
||||||
# to disable ROM protection to allow flashing
|
|
||||||
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
|
|
||||||
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
|
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Start the JLinkGDBServer while also specifying the JLinkScript file. The JLinkScript is necessary
|
# Start the JLinkGDBServer while also specifying the JLinkScript file. The JLinkScript is necessary
|
||||||
# to disable ROM protection to allow flashing
|
# to disable ROM protection to allow flashing
|
||||||
JLinkGDBServer -select USB -device VA416xx -endian little -if SWD -speed 2000 \
|
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
|
||||||
-LocalhostOnly -vd
|
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
target remote localhost:2331
|
target remote localhost:2331
|
||||||
|
|
||||||
monitor reset
|
monitor halt
|
||||||
|
|
||||||
# *try* to stop at the user entry point (it might be gone due to inlining)
|
# *try* to stop at the user entry point (it might be gone due to inlining)
|
||||||
break main
|
break main
|
||||||
|
1
memory.x
1
memory.x
@ -18,5 +18,6 @@ _stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|||||||
SECTIONS {
|
SECTIONS {
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
.sram1 (NOLOAD) : ALIGN(8) {
|
||||||
*(.sram1 .sram1.*);
|
*(.sram1 .sram1.*);
|
||||||
|
. = ALIGN(4);
|
||||||
} > SRAM_1
|
} > SRAM_1
|
||||||
};
|
};
|
||||||
|
1
scripts/can-clk-calc/.gitignore
vendored
1
scripts/can-clk-calc/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/target
|
|
@ -1,9 +0,0 @@
|
|||||||
[workspace]
|
|
||||||
|
|
||||||
[package]
|
|
||||||
name = "can-clk-calc"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
va416xx-hal = { path = "../../va416xx-hal", features = ["alloc", "revb"], default-features = false }
|
|
@ -1,14 +0,0 @@
|
|||||||
use va416xx_hal::can::calculate_all_viable_clock_configs;
|
|
||||||
use va416xx_hal::time::Hertz;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let cfgs = calculate_all_viable_clock_configs(
|
|
||||||
Hertz::from_raw(20_000_000),
|
|
||||||
Hertz::from_raw(250_000),
|
|
||||||
0.75,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
for cfg in &cfgs {
|
|
||||||
println!("Config: {:#?}", cfg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Check if binary path was provided
|
|
||||||
if [ "$#" -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <path-to-binary>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BINARY="$1"
|
|
||||||
|
|
||||||
# Check if file exists
|
|
||||||
if [ ! -f "$BINARY" ]; then
|
|
||||||
echo "Error: File '$BINARY' not found."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run the command
|
|
||||||
defmt-print -e "$BINARY" tcp
|
|
@ -1,23 +0,0 @@
|
|||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot A with an offset at address 0x4000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00004000, LENGTH = 0x1DFF8
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
/* Special linker script for application slot B with an offset at address 0x22000 */
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00022000, LENGTH = 0x1DFF8
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/* Define sections for placing symbols into the extra memory regions above. */
|
|
||||||
/* This makes them accessible from code. */
|
|
||||||
SECTIONS {
|
|
||||||
.sram1 (NOLOAD) : ALIGN(8) {
|
|
||||||
*(.sram1 .sram1.*);
|
|
||||||
. = ALIGN(4);
|
|
||||||
} > SRAM_1
|
|
||||||
};
|
|
@ -1,21 +0,0 @@
|
|||||||
Change Log
|
|
||||||
=======
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
## [unreleased]
|
|
||||||
|
|
||||||
## [v0.1.1] 2025-03-07
|
|
||||||
|
|
||||||
- Bumped allowed HAL dependency to v0.5
|
|
||||||
|
|
||||||
## [v0.1.0] 2025-02-18
|
|
||||||
|
|
||||||
Initial release
|
|
||||||
|
|
||||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-embassy-v0.1.1...HEAD
|
|
||||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-embassy-v0.1.0...va416xx-embassy-v0.1.1
|
|
||||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/tag/va416xx-embassy-v0.1.0
|
|
@ -1,31 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "va416xx-embassy"
|
|
||||||
version = "0.1.1"
|
|
||||||
edition = "2021"
|
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
|
||||||
description = "Embassy-rs support for the Vorago VA416xx family of microcontrollers"
|
|
||||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
|
|
||||||
repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
|
|
||||||
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] }
|
|
||||||
va416xx-hal = { path = "../va416xx-hal" }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["irq-tim14-tim15"]
|
|
||||||
|
|
||||||
# This determines the reserved interrupt functions for the embassy time drivers. Only one
|
|
||||||
# is allowed to be selected!
|
|
||||||
irq-tim14-tim15 = ["_irqs-in-lib"]
|
|
||||||
irq-tim13-tim14 = ["_irqs-in-lib"]
|
|
||||||
# These TIMs are clocked slower!
|
|
||||||
irq-tim22-tim23 = ["_irqs-in-lib"]
|
|
||||||
|
|
||||||
# Private feature.
|
|
||||||
_irqs-in-lib = []
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
|
@ -1,10 +0,0 @@
|
|||||||
[](https://crates.io/crates/va416xx-embassy)
|
|
||||||
[](https://docs.rs/va416xx-embassy)
|
|
||||||
|
|
||||||
# Embassy-rs support for the Vorago VA416xx MCU family
|
|
||||||
|
|
||||||
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
|
|
||||||
VA416xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
|
|
||||||
peripherals provided by the VA416xx family for this purpose.
|
|
||||||
|
|
||||||
The documentation contains more information on how to use this crate.
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
|
||||||
cargo +nightly doc --open
|
|
@ -1,117 +0,0 @@
|
|||||||
//! # Embassy-rs support for the Vorago VA416xx MCU family
|
|
||||||
//!
|
|
||||||
//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
|
|
||||||
//! VA416xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
|
|
||||||
//! peripherals provided by the VA416xx family for this purpose.
|
|
||||||
//!
|
|
||||||
//! ## Usage
|
|
||||||
//!
|
|
||||||
//! This library only exposes the [embassy::init] method which sets up the time driver. This
|
|
||||||
//! function must be called once at the start of the application.
|
|
||||||
//!
|
|
||||||
//! This implementation requires two TIM peripherals provided by the VA108xx device.
|
|
||||||
//! The user can freely specify the two used TIM peripheral by passing the concrete TIM instances
|
|
||||||
//! into the [init] method. If the interrupt handlers are provided by the library, the ID of the
|
|
||||||
//! used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
|
|
||||||
//! can only be checked at run-time, and a run-time assertion will panic on the embassy
|
|
||||||
//! initialization in case of a missmatch.
|
|
||||||
//!
|
|
||||||
//! The application also requires two interrupt handlers to handle the timekeeper and alarm
|
|
||||||
//! interrupts. By default, this library will define the interrupt handler inside the library
|
|
||||||
//! itself by using the `irq-tim14-tim15` feature flag. This library exposes three combinations:
|
|
||||||
//!
|
|
||||||
//! - `irq-tim14-tim15`: Uses [pac::Interrupt::TIM14] for alarm and [pac::Interrupt::TIM15]
|
|
||||||
//! for timekeeper
|
|
||||||
//! - `irq-tim13-tim14`: Uses [pac::Interrupt::TIM13] for alarm and [pac::Interrupt::TIM14]
|
|
||||||
//! for timekeeper
|
|
||||||
//! - `irq-tim22-tim23`: Uses [pac::Interrupt::TIM22] for alarm and [pac::Interrupt::TIM23]
|
|
||||||
//! for timekeeper
|
|
||||||
//!
|
|
||||||
//! You can disable the default features and then specify one of the features above to use the
|
|
||||||
//! documented combination of IRQs. It is also possible to specify custom IRQs by importing and
|
|
||||||
//! using the [embassy_time_driver_irqs] macro to declare the IRQ handlers in the
|
|
||||||
//! application code. If this is done, [embassy::init_with_custom_irqs] must be used
|
|
||||||
//! method to pass the IRQ numbers to the library.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy)
|
|
||||||
#![no_std]
|
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
||||||
use va416xx_hal::{
|
|
||||||
clock::Clocks,
|
|
||||||
irq_router::enable_and_init_irq_router,
|
|
||||||
pac::{self, interrupt},
|
|
||||||
timer::{TimMarker, TIM_IRQ_OFFSET},
|
|
||||||
};
|
|
||||||
use vorago_shared_periphs::embassy::time_driver;
|
|
||||||
|
|
||||||
/// Macro to define the IRQ handlers for the time driver.
|
|
||||||
///
|
|
||||||
/// By default, the code generated by this macro will be defined inside the library depending on
|
|
||||||
/// the feature flags specified. However, the macro is exported to allow users to specify the
|
|
||||||
/// interrupt handlers themselves.
|
|
||||||
///
|
|
||||||
/// Please note that you have to explicitely import the [macro@va108xx_hal::pac::interrupt]
|
|
||||||
/// macro in the application code in case this macro is used there.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! embassy_time_driver_irqs {
|
|
||||||
(
|
|
||||||
timekeeper_irq = $timekeeper_irq:ident,
|
|
||||||
alarm_irq = $alarm_irq:ident
|
|
||||||
) => {
|
|
||||||
const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::$timekeeper_irq;
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn $timekeeper_irq() {
|
|
||||||
// Safety: We call it once here.
|
|
||||||
unsafe { $crate::time_driver().on_interrupt_timekeeping() }
|
|
||||||
}
|
|
||||||
|
|
||||||
const ALARM_IRQ: pac::Interrupt = pac::Interrupt::$alarm_irq;
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn $alarm_irq() {
|
|
||||||
// Safety: We call it once here.
|
|
||||||
unsafe { $crate::time_driver().on_interrupt_alarm() }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provide three combinations of IRQs for the time driver by default.
|
|
||||||
|
|
||||||
#[cfg(feature = "irq-tim14-tim15")]
|
|
||||||
embassy_time_driver_irqs!(timekeeper_irq = TIM15, alarm_irq = TIM14);
|
|
||||||
#[cfg(feature = "irq-tim13-tim14")]
|
|
||||||
embassy_time_driver_irqs!(timekeeper_irq = TIM14, alarm_irq = TIM13);
|
|
||||||
#[cfg(feature = "irq-tim22-tim23")]
|
|
||||||
embassy_time_driver_irqs!(timekeeper_irq = TIM23, alarm_irq = TIM22);
|
|
||||||
|
|
||||||
/// Initialization method for embassy
|
|
||||||
///
|
|
||||||
/// If the interrupt handlers are provided by the library, the ID of the
|
|
||||||
/// used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
|
|
||||||
/// can only be checked at run-time, and a run-time assertion will panic on the embassy
|
|
||||||
/// initialization in case of a missmatch.
|
|
||||||
pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
|
|
||||||
timekeeper: TimekeeperTim,
|
|
||||||
alarm: AlarmTim,
|
|
||||||
clocks: &Clocks,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "_irqs-in-lib")]
|
|
||||||
assert_eq!(
|
|
||||||
TimekeeperTim::ID.value(),
|
|
||||||
TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
|
|
||||||
"Timekeeper TIM and IRQ missmatch"
|
|
||||||
);
|
|
||||||
#[cfg(feature = "_irqs-in-lib")]
|
|
||||||
assert_eq!(
|
|
||||||
AlarmTim::ID.value(),
|
|
||||||
ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,
|
|
||||||
"Alarm TIM and IRQ missmatch"
|
|
||||||
);
|
|
||||||
enable_and_init_irq_router();
|
|
||||||
time_driver().__init(timekeeper, alarm, clocks)
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
Change Log
|
|
||||||
=======
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
# [unreleased]
|
|
||||||
|
|
||||||
# [v0.5.1] 2025-03-10
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- Fix `embedded_io` UART implementation to implement the documented contract properly.
|
|
||||||
The implementation will now block until at least one byte is available or can be written, unless
|
|
||||||
the send or receive buffer is empty.
|
|
||||||
|
|
||||||
# [v0.5.0] 2025-03-07
|
|
||||||
|
|
||||||
- Bugfix for I2C `TimingCfg::reg`
|
|
||||||
- Simplified UART error handling. All APIs are now infallible because writing to a FIFO or
|
|
||||||
reading from a FIFO never fails. Users can either poll errors using `Rx::poll_errors` or
|
|
||||||
`Uart::poll_rx_errors` / `UartBase::poll_rx_errors`, or detect errors using the provided
|
|
||||||
interrupt handlers.
|
|
||||||
|
|
||||||
# [v0.4.1] 2025-02-18
|
|
||||||
|
|
||||||
- Chip selection is not enforced anymore, but advised through documentation. This makes using
|
|
||||||
the HAL in libraries a lot easier.
|
|
||||||
|
|
||||||
# [v0.4.0] 2025-02-18
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
|
|
||||||
- GPIO API: Interrupt, pulse and filter and `set_datamask` and `clear_datamask` APIs are now
|
|
||||||
methods which mutable modify the pin instead of consuming and returning it.
|
|
||||||
- Simplified PWM module implementation.
|
|
||||||
- All error types now implement `core::error::Error` by using the `thiserror::Error` derive.
|
|
||||||
- `InvalidPinTypeError` now wraps the pin mode.
|
|
||||||
- I2C `TimingCfg` constructor now returns explicit error instead of generic Error.
|
|
||||||
Removed the timing configuration error type from the generic I2C error enumeration.
|
|
||||||
- `PinsA` and `PinsB` constructor do not expect an optional `pac::Ioconfig` argument anymore.
|
|
||||||
- `IrqCfg` renamed to `InterruptConfig`, kept alias for old name.
|
|
||||||
- All library provided interrupt handlers now start with common prefix `on_interrupt_*`
|
|
||||||
- `RxWithIrq` renamed to `RxWithInterrupt`
|
|
||||||
- `Rx::into_rx_with_irq` does not expect any arguments any more.
|
|
||||||
- `filter_type` renamed to `configure_filter_type`.
|
|
||||||
- `level_irq` renamed to `configure_level_interrupt`.
|
|
||||||
- `edge_irq` renamed to `configure_edge_interrupt`.
|
|
||||||
- UART interrupt management is now handled by the main constructor instead of later stages to
|
|
||||||
statically ensure one interrupt vector for the UART peripheral. `Uart::new` expects an
|
|
||||||
optional `InterruptConfig` argument.
|
|
||||||
- `enable_interrupt` and `disable_interrupt` renamed to `enable_nvic_interrupt` and
|
|
||||||
`disable_nvic_interrupt` to distinguish them from peripheral interrupts more clearly.
|
|
||||||
- `port_mux` renamed to `port_function_select`
|
|
||||||
- Renamed `IrqUartErrors` to `UartErrors`.
|
|
||||||
|
|
||||||
## Added
|
|
||||||
|
|
||||||
- Add `downgrade` method for `Pin` and `upgrade` method for `DynPin` as explicit conversion
|
|
||||||
methods.
|
|
||||||
- Asynchronous GPIO support.
|
|
||||||
- Asynchronous UART TX support.
|
|
||||||
- Asynchronous UART RX support.
|
|
||||||
- Add new `get_tim_raw` unsafe method to retrieve TIM peripheral blocks.
|
|
||||||
- `Uart::with_with_interrupt` and `Uart::new_without_interrupt`
|
|
||||||
- A lot of missing `defmt::Format` implementations.
|
|
||||||
|
|
||||||
# [v0.3.0] 2024-30-09
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
|
|
||||||
- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now
|
|
||||||
only expects a configuration structure and the transfer configuration needs to be applied in a
|
|
||||||
separate step.
|
|
||||||
- Added an additional way to read the UART RX with IRQs. The module documentation provides
|
|
||||||
more information.
|
|
||||||
- Made the UART with IRQ API more flexible for future additions.
|
|
||||||
- Improved UART API result and error handling, added low level API to read from and write
|
|
||||||
to the FIFO directly
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- Fixes for SPI peripheral: Flush implementation was incorrect and should now flush properly.
|
|
||||||
- Fixes for SPI example
|
|
||||||
- Fixes for RTIC example
|
|
||||||
|
|
||||||
# [v0.2.0] 2024-09-18
|
|
||||||
|
|
||||||
- Documentation improvements
|
|
||||||
- Improved UART typing support: Validity of passed pins is now checked properly
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
|
|
||||||
- Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be
|
|
||||||
selected for HAL compilation to work properly
|
|
||||||
- Adaptions for the UART IRQ feature which are now only implemented for the RX part of the UART.
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- Small fixes and improvements for ADC drivers
|
|
||||||
- Fixes for the SPI implementation where the clock divider values were not calculated
|
|
||||||
correctly
|
|
||||||
- Fixes for UART IRQ handler implementation
|
|
||||||
- Add new IRQ router initialization method `irq_router::enable_and_init_irq_router`. This method
|
|
||||||
also sets the initial values of some registers to 0 where the datasheet and the actual reset
|
|
||||||
value are inconsistent, which can lead to weird bugs like IRQs not being triggered properly.
|
|
||||||
|
|
||||||
## Added
|
|
||||||
|
|
||||||
- Added basic DMA driver
|
|
||||||
- Added basic EDAC module
|
|
||||||
- Added bootloader and flashloader example application
|
|
||||||
- Added NVM module which exposes a simple API to write to the NVM memory used for the boot process
|
|
||||||
|
|
||||||
# [v0.1.0] 2024-07-01
|
|
||||||
|
|
||||||
- Initial release with basic HAL drivers
|
|
||||||
|
|
||||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.5.0...HEAD
|
|
||||||
[v0.5.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.5.0...va416xx-hal-v0.5.1
|
|
||||||
[v0.5.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.4.1...va416xx-hal-v0.5.0
|
|
||||||
[v0.4.1]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.4.0...va416xx-hal-v0.4.1
|
|
||||||
[v0.4.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.3.0...va416xx-hal-v0.4.0
|
|
||||||
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.2.0...va108xx-hal-v0.3.0
|
|
||||||
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/compare/va416xx-hal-v0.1.0...va108xx-hal-v0.2.0
|
|
||||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/tag/va416xx-hal-v0.1.0
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "va416xx-hal"
|
name = "va416xx-hal"
|
||||||
version = "0.5.1"
|
version = "0.1.0"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "HAL for the Vorago VA416xx family of MCUs"
|
description = "HAL for the Vorago VA416xx family of MCUs"
|
||||||
@ -12,40 +12,35 @@ categories = ["embedded", "no-std", "hardware-support"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
|
|
||||||
derive-mmio = { version = "0.4", git = "https://github.com/knurling-rs/derive-mmio.git" }
|
|
||||||
static_assertions = "1.1"
|
|
||||||
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] }
|
|
||||||
|
|
||||||
libm = "0.2"
|
|
||||||
nb = "1"
|
nb = "1"
|
||||||
|
paste = "1"
|
||||||
|
embedded-hal-nb = "1"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
|
embedded-io = "0.6"
|
||||||
|
embedded-dma = "0.2"
|
||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
|
typenum = "1"
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
bitbybit = "1.3"
|
bitfield = "0.15"
|
||||||
arbitrary-int = "1.3"
|
|
||||||
fugit = "0.3"
|
|
||||||
embedded-can = "0.4"
|
|
||||||
embassy-sync = "0.6"
|
|
||||||
thiserror = { version = "2", default-features = false }
|
|
||||||
|
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
|
fugit = "0.3"
|
||||||
|
delegate = "0.12"
|
||||||
|
|
||||||
|
[dependencies.void]
|
||||||
|
version = "1"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.va416xx]
|
||||||
|
default-features = false
|
||||||
|
version = "0.2"
|
||||||
|
features = ["critical-section"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["rt", "revb"]
|
default = ["rt", "revb"]
|
||||||
rt = ["va416xx/rt"]
|
rt = ["va416xx/rt"]
|
||||||
alloc = []
|
defmt = ["dep:defmt", "fugit/defmt"]
|
||||||
defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"]
|
|
||||||
|
|
||||||
va41630 = ["device-selected"]
|
|
||||||
va41620 = ["device-selected"]
|
|
||||||
|
|
||||||
va41629 = ["device-selected"]
|
|
||||||
va41628 = ["device-selected", "vorago-shared-periphs/va41628"]
|
|
||||||
|
|
||||||
device-selected = []
|
|
||||||
revb = []
|
revb = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["va41630", "defmt"]
|
all-features = true
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
rustdoc-args = ["--generate-link-to-definition"]
|
||||||
|
@ -11,17 +11,6 @@ raw PAC. This crate also implements traits specified by the
|
|||||||
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
||||||
various drivers in the embedded rust ecosystem.
|
various drivers in the embedded rust ecosystem.
|
||||||
|
|
||||||
It is generally advised to enable ONE of the following device features to use this crate
|
|
||||||
depending on which chip you are using:
|
|
||||||
|
|
||||||
- `va41630`
|
|
||||||
- `va41629`
|
|
||||||
- `va41628`
|
|
||||||
- `va41620`
|
|
||||||
|
|
||||||
If no chip is specified, only access to APIs which are common for all families or
|
|
||||||
which are not disabled for specific families is granted.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
Building an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
|
||||||
@ -33,6 +22,12 @@ rustup target add thumbv7em-none-eabihf
|
|||||||
|
|
||||||
After that, you can use `cargo build` to build the development version of the crate.
|
After that, you can use `cargo build` to build the development version of the crate.
|
||||||
|
|
||||||
|
If you have not done this yet, it is recommended to read some of the excellent resources
|
||||||
|
available to learn Rust:
|
||||||
|
|
||||||
|
- [Rust Embedded Book](https://docs.rust-embedded.org/book/)
|
||||||
|
- [Rust Discovery Book](https://docs.rust-embedded.org/discovery/)
|
||||||
|
|
||||||
## Setting up your own binary crate
|
## Setting up your own binary crate
|
||||||
|
|
||||||
If you have a custom board, you might be interested in setting up a new binary crate for your
|
If you have a custom board, you might be interested in setting up a new binary crate for your
|
||||||
@ -61,18 +56,10 @@ is contained within the
|
|||||||
|
|
||||||
[dependencies.va416xx-hal]
|
[dependencies.va416xx-hal]
|
||||||
version = "<Most Recent Version>"
|
version = "<Most Recent Version>"
|
||||||
features = ["va41630"]
|
features = ["rt"]
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Build the application with `cargo build`
|
6. Build the application with `cargo build`
|
||||||
|
|
||||||
7. Flashing the board might work differently for different boards and there is usually
|
7. Flashing the board might work differently for different boards and there is usually
|
||||||
more than one way. You can find example instructions in primary README.
|
more than one way. You can find example instructions in primary README.
|
||||||
|
|
||||||
## Embedded Rust
|
|
||||||
|
|
||||||
If you have not done this yet, it is recommended to read some of the excellent resources
|
|
||||||
available to learn Rust:
|
|
||||||
|
|
||||||
- [Rust Embedded Book](https://docs.rust-embedded.org/book/)
|
|
||||||
- [Rust Discovery Book](https://docs.rust-embedded.org/discovery/)
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
|
||||||
cargo +nightly doc --features "defmt va41630" --open
|
|
@ -1,16 +1,10 @@
|
|||||||
//! Analog to Digital Converter (ADC) driver.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
|
|
||||||
//! - [ADC](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/adc.rs)
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::clock::Clocks;
|
use crate::clock::Clocks;
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
|
|
||||||
|
|
||||||
pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000);
|
pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000);
|
||||||
pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000);
|
pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000);
|
||||||
@ -52,8 +46,6 @@ pub enum ChannelSelect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
/// This structure is used by the ADC multi-select API to
|
|
||||||
/// allow selecting multiple channels in a convenient manner.
|
|
||||||
pub struct MultiChannelSelect: u16 {
|
pub struct MultiChannelSelect: u16 {
|
||||||
const AnIn0 = 1;
|
const AnIn0 = 1;
|
||||||
const AnIn1 = 1 << 1;
|
const AnIn1 = 1 << 1;
|
||||||
@ -74,28 +66,34 @@ bitflags::bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
#[error("ADC empty error")]
|
|
||||||
pub struct AdcEmptyError;
|
pub struct AdcEmptyError;
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
#[error("invalid channel range error")]
|
|
||||||
pub struct InvalidChannelRangeError;
|
pub struct InvalidChannelRangeError;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
#[error("buffer too small")]
|
|
||||||
pub struct BufferTooSmallError;
|
pub struct BufferTooSmallError;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum AdcRangeReadError {
|
pub enum AdcRangeReadError {
|
||||||
#[error("invalid channel range: {0}")]
|
InvalidChannelRange(InvalidChannelRangeError),
|
||||||
InvalidChannelRange(#[from] InvalidChannelRangeError),
|
BufferTooSmall(BufferTooSmallError),
|
||||||
#[error("buffer too small: {0}")]
|
}
|
||||||
BufferTooSmall(#[from] BufferTooSmallError),
|
|
||||||
|
impl From<InvalidChannelRangeError> for AdcRangeReadError {
|
||||||
|
fn from(value: InvalidChannelRangeError) -> Self {
|
||||||
|
AdcRangeReadError::InvalidChannelRange(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BufferTooSmallError> for AdcRangeReadError {
|
||||||
|
fn from(value: BufferTooSmallError) -> Self {
|
||||||
|
AdcRangeReadError::BufferTooSmall(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
@ -131,18 +129,6 @@ impl ChannelValue {
|
|||||||
pub enum ChannelTagEnabled {}
|
pub enum ChannelTagEnabled {}
|
||||||
pub enum ChannelTagDisabled {}
|
pub enum ChannelTagDisabled {}
|
||||||
|
|
||||||
/// ADC driver structure.
|
|
||||||
///
|
|
||||||
/// Currently, this structure supports three primary ways to measure channel value(s):
|
|
||||||
///
|
|
||||||
/// * Trigger and read a single value
|
|
||||||
/// * Trigger and read a range of ADC values using the sweep functionality
|
|
||||||
/// * Trigger and read multiple ADC values using the sweep functionality
|
|
||||||
///
|
|
||||||
/// The ADC channel tag feature is enabled or disabled at compile time using the
|
|
||||||
/// [ChannelTagEnabled] and [ChannelTagDisabled]. The [Adc::new] method returns a driver instance
|
|
||||||
/// with the channel tag enabled, while the [Adc::new_with_channel_tag] method can be used to
|
|
||||||
/// return an instance with the channel tag enabled.
|
|
||||||
pub struct Adc<TagEnabled = ChannelTagDisabled> {
|
pub struct Adc<TagEnabled = ChannelTagDisabled> {
|
||||||
adc: pac::Adc,
|
adc: pac::Adc,
|
||||||
phantom: PhantomData<TagEnabled>,
|
phantom: PhantomData<TagEnabled>,
|
||||||
@ -151,8 +137,8 @@ pub struct Adc<TagEnabled = ChannelTagDisabled> {
|
|||||||
impl Adc<ChannelTagEnabled> {}
|
impl Adc<ChannelTagEnabled> {}
|
||||||
|
|
||||||
impl Adc<ChannelTagDisabled> {
|
impl Adc<ChannelTagDisabled> {
|
||||||
pub fn new(adc: pac::Adc, clocks: &Clocks) -> Self {
|
pub fn new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, clocks: &Clocks) -> Self {
|
||||||
Self::generic_new(adc, clocks)
|
Self::generic_new(syscfg, adc, clocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result<u16, AdcEmptyError> {
|
pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result<u16, AdcEmptyError> {
|
||||||
@ -168,49 +154,43 @@ impl Adc<ChannelTagDisabled> {
|
|||||||
lower_bound_idx: u8,
|
lower_bound_idx: u8,
|
||||||
upper_bound_idx: u8,
|
upper_bound_idx: u8,
|
||||||
rx_buf: &mut [u16],
|
rx_buf: &mut [u16],
|
||||||
) -> Result<usize, AdcRangeReadError> {
|
) -> Result<(), AdcRangeReadError> {
|
||||||
self.generic_prepare_range_sweep_and_wait_until_ready(
|
self.generic_prepare_range_sweep_and_wait_until_ready(
|
||||||
lower_bound_idx,
|
lower_bound_idx,
|
||||||
upper_bound_idx,
|
upper_bound_idx,
|
||||||
rx_buf.len(),
|
rx_buf.len(),
|
||||||
)?;
|
)?;
|
||||||
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits();
|
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
|
||||||
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
|
|
||||||
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
||||||
}
|
}
|
||||||
Ok(fifo_entry_count as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a sweep for selected ADC channels.
|
|
||||||
///
|
|
||||||
/// Returns the number of read values which were written to the passed RX buffer.
|
|
||||||
pub fn sweep_and_read_multiselect(
|
pub fn sweep_and_read_multiselect(
|
||||||
&self,
|
&self,
|
||||||
ch_select: MultiChannelSelect,
|
ch_select: MultiChannelSelect,
|
||||||
rx_buf: &mut [u16],
|
rx_buf: &mut [u16],
|
||||||
) -> Result<usize, BufferTooSmallError> {
|
) -> Result<(), BufferTooSmallError> {
|
||||||
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
||||||
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits();
|
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
|
||||||
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
|
|
||||||
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff;
|
||||||
}
|
}
|
||||||
Ok(fifo_entry_count as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
|
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
|
||||||
self.generic_try_read_single_value()
|
self.generic_try_read_single_value()
|
||||||
.map(|v| v.map(|v| v & 0xfff))
|
.map(|v| v.map(|v| v & 0xfff))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn channel_tag_enabled(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Adc<ChannelTagEnabled> {
|
impl Adc<ChannelTagEnabled> {
|
||||||
pub fn new_with_channel_tag(adc: pac::Adc, clocks: &Clocks) -> Self {
|
pub fn new_with_channel_tag(
|
||||||
let mut adc = Self::generic_new(adc, clocks);
|
syscfg: &mut pac::Sysconfig,
|
||||||
|
adc: pac::Adc,
|
||||||
|
clocks: &Clocks,
|
||||||
|
) -> Self {
|
||||||
|
let mut adc = Self::generic_new(syscfg, adc, clocks);
|
||||||
adc.enable_channel_tag();
|
adc.enable_channel_tag();
|
||||||
adc
|
adc
|
||||||
}
|
}
|
||||||
@ -250,21 +230,17 @@ impl Adc<ChannelTagEnabled> {
|
|||||||
Ok(fifo_entry_count as usize)
|
Ok(fifo_entry_count as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a sweep for selected ADC channels.
|
|
||||||
///
|
|
||||||
/// Returns the number of read values which were written to the passed RX buffer.
|
|
||||||
pub fn sweep_and_read_multiselect(
|
pub fn sweep_and_read_multiselect(
|
||||||
&self,
|
&self,
|
||||||
ch_select: MultiChannelSelect,
|
ch_select: MultiChannelSelect,
|
||||||
rx_buf: &mut [ChannelValue],
|
rx_buf: &mut [ChannelValue],
|
||||||
) -> Result<usize, BufferTooSmallError> {
|
) -> Result<(), BufferTooSmallError> {
|
||||||
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
|
||||||
let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits();
|
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
|
||||||
for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) {
|
|
||||||
rx_buf[i as usize] =
|
rx_buf[i as usize] =
|
||||||
self.create_channel_value(self.adc.fifo_data().read().bits() as u16);
|
self.create_channel_value(self.adc.fifo_data().read().bits() as u16);
|
||||||
}
|
}
|
||||||
Ok(fifo_entry_count as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -274,16 +250,11 @@ impl Adc<ChannelTagEnabled> {
|
|||||||
channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(),
|
channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn channel_tag_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TagEnabled> Adc<TagEnabled> {
|
impl<TagEnabled> Adc<TagEnabled> {
|
||||||
fn generic_new(adc: pac::Adc, _clocks: &Clocks) -> Self {
|
fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self {
|
||||||
enable_peripheral_clock(PeripheralSelect::Adc);
|
syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc);
|
||||||
adc.ctrl().write(|w| unsafe { w.bits(0) });
|
adc.ctrl().write(|w| unsafe { w.bits(0) });
|
||||||
let adc = Self {
|
let adc = Self {
|
||||||
adc,
|
adc,
|
||||||
@ -303,6 +274,11 @@ impl<TagEnabled> Adc<TagEnabled> {
|
|||||||
self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit());
|
self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn channel_tag_enabled(&self) -> bool {
|
||||||
|
self.adc.ctrl().read().chan_tag_en().bit_is_set()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_fifo(&self) {
|
pub fn clear_fifo(&self) {
|
||||||
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
|
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
|
||||||
@ -350,6 +326,8 @@ impl<TagEnabled> Adc<TagEnabled> {
|
|||||||
ch_select |= 1 << i;
|
ch_select |= 1 << i;
|
||||||
}
|
}
|
||||||
self.generic_trigger_sweep(ch_select);
|
self.generic_trigger_sweep(ch_select);
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
cortex_m::asm::nop();
|
||||||
while self.adc.status().read().adc_busy().bit_is_set() {
|
while self.adc.status().read().adc_busy().bit_is_set() {
|
||||||
cortex_m::asm::nop();
|
cortex_m::asm::nop();
|
||||||
}
|
}
|
||||||
|
@ -1,311 +0,0 @@
|
|||||||
use core::{
|
|
||||||
future::Future,
|
|
||||||
sync::atomic::{AtomicU8, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::can::regs::BufferState;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
regs::{DiagnosticRegister, InterruptClear, MmioCan, StatusPending},
|
|
||||||
CanChannelLowLevel, CanFrame, CanId, InvalidBufferIndexError,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TxChannelState {
|
|
||||||
Unconfigured = 0,
|
|
||||||
Idle = 1,
|
|
||||||
TxDataFrame = 2,
|
|
||||||
TxRtrTransmission = 3,
|
|
||||||
TxRtrReception = 4,
|
|
||||||
Finished = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
static TX_STATES: [AtomicU8; 15] = [const { AtomicU8::new(0) }; 15];
|
|
||||||
static TX_WAKERS: [embassy_sync::waitqueue::AtomicWaker; 15] =
|
|
||||||
[const { embassy_sync::waitqueue::AtomicWaker::new() }; 15];
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum TxEventId {
|
|
||||||
/// Buffer state went from [BufferState::TxOnce] to [BufferState::TxNotActive].
|
|
||||||
TxDataFrame,
|
|
||||||
/// Buffer state went from [BufferState::TxOnce] to [BufferState::TxNotActive] for a remote
|
|
||||||
/// frame (RTR bit set). Channel might be in reception mode [BufferState::RxReady] now.
|
|
||||||
TxRemoteFrame,
|
|
||||||
/// A response to a remote frame was performed successfully, and the buffer state went from
|
|
||||||
/// [BufferState::TxOnceRtr] to [BufferState::TxRtr].
|
|
||||||
RtrResponse,
|
|
||||||
/// A remote frame was received and the transmission of a response frame was scheduled. The
|
|
||||||
/// buffer state went from [BufferState::TxRtr] to [BufferState::TxOnceRtr].
|
|
||||||
TransmitScheduling,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InterruptResult {
|
|
||||||
NoInterrupt,
|
|
||||||
ReceivedFrame {
|
|
||||||
channel_index: usize,
|
|
||||||
frame: CanFrame,
|
|
||||||
},
|
|
||||||
TransmissionEvent {
|
|
||||||
channel_index: usize,
|
|
||||||
id: TxEventId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum InterruptError {
|
|
||||||
UnexpectedError,
|
|
||||||
InvalidInterruptId(StatusPending),
|
|
||||||
InvalidStatus(u8),
|
|
||||||
UnexpectedState(BufferState),
|
|
||||||
CanError(DiagnosticRegister),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This interrupt handler allow asynchronous transmission and reception of CAN frames.
|
|
||||||
///
|
|
||||||
/// This handler will re-configure a channel to [BufferState::RxReady] after successfull reception
|
|
||||||
/// of a frame without disabling the interrupts, assuming that the user wants to immediately
|
|
||||||
/// receive the next frame on the channel.
|
|
||||||
/// The user should re-configure the buffer state to [BufferState::RxNotActive] if the reception
|
|
||||||
/// should be disabled.
|
|
||||||
///
|
|
||||||
/// The handler will re-configure a channel to [BufferState::TxNotActive] instead of
|
|
||||||
/// [BufferState::RxReady] if the completed frame transmission was a remote frame and after
|
|
||||||
/// successfully having received a response to that remote frame. The assumption is that this
|
|
||||||
/// channel is used to request more frames. If the argument `reconfigure_tx_rtr_to_tx` is set to
|
|
||||||
/// true, the channel will automatically be configured back to [BufferState::TxNotActive] with
|
|
||||||
/// interrupts for the respective channel disabled after transmission of a remote frame.
|
|
||||||
///
|
|
||||||
/// The handler will not disable the interrupts realted to the TX RTR and TX RTR ONCE auto-response
|
|
||||||
/// functionality of the CAN peripheral. It will report the event type to the caller via the
|
|
||||||
/// [TxEventId] enumeration.
|
|
||||||
pub fn on_interrupt_can(
|
|
||||||
id: CanId,
|
|
||||||
reconfigure_tx_rtr_to_tx: bool,
|
|
||||||
) -> Result<InterruptResult, InterruptError> {
|
|
||||||
let mut regs = unsafe { id.steal_regs() };
|
|
||||||
// Check if any interrupts are enabled.
|
|
||||||
let ie = regs.read_ien();
|
|
||||||
if ie.raw_value() == 0 {
|
|
||||||
return Ok(InterruptResult::NoInterrupt);
|
|
||||||
}
|
|
||||||
let pending_id = regs.read_status_pending();
|
|
||||||
if pending_id.interrupt_id().is_none() {
|
|
||||||
regs.write_iclr(InterruptClear::new_with_raw_value(0xFFFF_FFFF));
|
|
||||||
return Err(InterruptError::InvalidInterruptId(pending_id));
|
|
||||||
}
|
|
||||||
match pending_id.interrupt_id().unwrap() {
|
|
||||||
super::regs::CanInterruptId::None => Ok(InterruptResult::NoInterrupt),
|
|
||||||
super::regs::CanInterruptId::Error => Err(InterruptError::CanError(regs.read_diag())),
|
|
||||||
super::regs::CanInterruptId::Buffer(idx) => {
|
|
||||||
let mut channel = unsafe { CanChannelLowLevel::steal_unchecked(id, idx) };
|
|
||||||
let status = channel.read_state();
|
|
||||||
if status.is_err() {
|
|
||||||
let mut clr = InterruptClear::new_with_raw_value(0);
|
|
||||||
clr.set_buffer(idx, true);
|
|
||||||
regs.write_iclr(clr);
|
|
||||||
regs.modify_ien(|mut val| {
|
|
||||||
val.set_buffer(idx, false);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
return Err(InterruptError::InvalidStatus(status.unwrap_err()));
|
|
||||||
}
|
|
||||||
let buf_state = status.unwrap();
|
|
||||||
if buf_state == BufferState::TxNotActive {
|
|
||||||
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
|
|
||||||
clear_and_disable_interrupt(&mut regs, idx);
|
|
||||||
// Handle reading frames, updating states etc.
|
|
||||||
if tx_state == TxChannelState::TxDataFrame as u8 {
|
|
||||||
// Transmission complete.
|
|
||||||
TX_STATES[idx].store(TxChannelState::Finished as u8, Ordering::Relaxed);
|
|
||||||
TX_WAKERS[idx].wake();
|
|
||||||
return Ok(InterruptResult::TransmissionEvent {
|
|
||||||
channel_index: idx,
|
|
||||||
id: TxEventId::TxDataFrame,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if buf_state == BufferState::RxReady {
|
|
||||||
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
|
|
||||||
if tx_state == TxChannelState::TxRtrTransmission as u8 {
|
|
||||||
if reconfigure_tx_rtr_to_tx {
|
|
||||||
channel.write_state(BufferState::TxNotActive);
|
|
||||||
clear_and_disable_interrupt(&mut regs, idx);
|
|
||||||
// Transmission complete.
|
|
||||||
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
|
|
||||||
} else {
|
|
||||||
// Do not disable interrupt, channel is now used to receive the frame.
|
|
||||||
clear_interrupt(&mut regs, idx);
|
|
||||||
// Transmission complete.
|
|
||||||
TX_STATES[idx]
|
|
||||||
.store(TxChannelState::TxRtrReception as u8, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
TX_WAKERS[idx].wake();
|
|
||||||
return Ok(InterruptResult::TransmissionEvent {
|
|
||||||
channel_index: idx,
|
|
||||||
id: TxEventId::TxRemoteFrame,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if buf_state == BufferState::RxOverrun || buf_state == BufferState::RxFull {
|
|
||||||
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
|
|
||||||
// Do not disable interrupt and assume continuous reception.
|
|
||||||
clear_interrupt(&mut regs, idx);
|
|
||||||
let frame = channel.read_frame_unchecked();
|
|
||||||
if tx_state == TxChannelState::TxRtrReception as u8 {
|
|
||||||
// Reception of response complete. We can release the channel for TX (or RX)
|
|
||||||
// usage again.
|
|
||||||
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
|
|
||||||
channel.write_state(BufferState::TxNotActive);
|
|
||||||
} else {
|
|
||||||
// Assume continous reception of frames.
|
|
||||||
channel.write_state(BufferState::RxReady);
|
|
||||||
}
|
|
||||||
return Ok(InterruptResult::ReceivedFrame {
|
|
||||||
channel_index: idx,
|
|
||||||
frame,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if buf_state == BufferState::TxRtr {
|
|
||||||
// Do not disable interrupt and assume continuous transmission.
|
|
||||||
clear_interrupt(&mut regs, idx);
|
|
||||||
return Ok(InterruptResult::TransmissionEvent {
|
|
||||||
channel_index: idx,
|
|
||||||
id: TxEventId::RtrResponse,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if buf_state == BufferState::TxOnceRtr {
|
|
||||||
// Do not disable interrupt and assume continuous transmission.
|
|
||||||
clear_interrupt(&mut regs, idx);
|
|
||||||
return Ok(InterruptResult::TransmissionEvent {
|
|
||||||
channel_index: idx,
|
|
||||||
id: TxEventId::TransmitScheduling,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(InterruptError::UnexpectedState(buf_state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn clear_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
|
|
||||||
let mut clr = InterruptClear::new_with_raw_value(0);
|
|
||||||
clr.set_buffer(idx, true);
|
|
||||||
regs.write_iclr(clr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn clear_and_disable_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
|
|
||||||
clear_interrupt(regs, idx);
|
|
||||||
regs.modify_ien(|mut val| {
|
|
||||||
val.set_buffer(idx, false);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("all channels are unconfigured, none available for TX")]
|
|
||||||
pub struct AllTxChannelsUnconfiguredError;
|
|
||||||
|
|
||||||
pub struct CanTxFuture(usize);
|
|
||||||
|
|
||||||
impl Future for CanTxFuture {
|
|
||||||
type Output = ();
|
|
||||||
|
|
||||||
fn poll(
|
|
||||||
self: core::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut core::task::Context<'_>,
|
|
||||||
) -> core::task::Poll<Self::Output> {
|
|
||||||
TX_WAKERS[self.0].register(cx.waker());
|
|
||||||
if TX_STATES[self.0].load(Ordering::Relaxed) == TxChannelState::Finished as u8 {
|
|
||||||
TX_STATES[self.0].store(TxChannelState::Idle as u8, Ordering::Relaxed);
|
|
||||||
return core::task::Poll::Ready(());
|
|
||||||
}
|
|
||||||
core::task::Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanTxFuture {
|
|
||||||
pub fn new(frame: CanFrame) -> nb::Result<Self, AllTxChannelsUnconfiguredError> {
|
|
||||||
let mut channel_is_free = [false; 15];
|
|
||||||
let mut all_channels_unused = true;
|
|
||||||
for (idx, state) in TX_STATES.iter().enumerate() {
|
|
||||||
let state = state.load(Ordering::Relaxed);
|
|
||||||
if state == TxChannelState::Idle as u8 {
|
|
||||||
channel_is_free[idx] = true;
|
|
||||||
}
|
|
||||||
if state != TxChannelState::Unconfigured as u8 {
|
|
||||||
all_channels_unused = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if channel_is_free.iter().all(|&x| !x) {
|
|
||||||
return Err(nb::Error::WouldBlock);
|
|
||||||
}
|
|
||||||
if all_channels_unused {
|
|
||||||
return Err(nb::Error::Other(AllTxChannelsUnconfiguredError));
|
|
||||||
}
|
|
||||||
let free_channel_id = channel_is_free.iter().position(|&x| x).unwrap();
|
|
||||||
let mut channel =
|
|
||||||
unsafe { CanChannelLowLevel::steal_unchecked(CanId::Can0, free_channel_id) };
|
|
||||||
TX_STATES[free_channel_id].store(TxChannelState::TxDataFrame as u8, Ordering::Relaxed);
|
|
||||||
channel.write_state(BufferState::TxNotActive);
|
|
||||||
channel.transmit_frame_unchecked(frame);
|
|
||||||
channel.clear_interrupt();
|
|
||||||
channel.enable_interrupt(true);
|
|
||||||
channel.enable_error_interrupt(true);
|
|
||||||
Ok(CanTxFuture(free_channel_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum ChannelConfigError {
|
|
||||||
#[error("channel is busy")]
|
|
||||||
Busy,
|
|
||||||
#[error("invalid offset: {0}")]
|
|
||||||
Offset(#[from] InvalidBufferIndexError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CanTxAsync;
|
|
||||||
|
|
||||||
impl CanTxAsync {
|
|
||||||
pub fn new(can: &mut super::Can) -> Self {
|
|
||||||
can.clear_interrupts();
|
|
||||||
can.enable_nvic_interrupt();
|
|
||||||
CanTxAsync
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure_channel(&mut self, channel_idx: usize) -> Result<(), ChannelConfigError> {
|
|
||||||
if channel_idx >= TX_STATES.len() {
|
|
||||||
return Err(ChannelConfigError::Offset(InvalidBufferIndexError(
|
|
||||||
channel_idx,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let state = TX_STATES[channel_idx].load(Ordering::Relaxed);
|
|
||||||
if state != TxChannelState::Idle as u8 && state != TxChannelState::Unconfigured as u8 {
|
|
||||||
return Err(ChannelConfigError::Busy);
|
|
||||||
}
|
|
||||||
TX_STATES[channel_idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a transmission and returns the future which can be polled to completion.
|
|
||||||
pub fn start_transmit(
|
|
||||||
&mut self,
|
|
||||||
frame: CanFrame,
|
|
||||||
) -> nb::Result<CanTxFuture, AllTxChannelsUnconfiguredError> {
|
|
||||||
CanTxFuture::new(frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls [Self::start_transmit] and awaits the returned future to completion immediately.
|
|
||||||
pub async fn transmit(
|
|
||||||
&mut self,
|
|
||||||
frame: CanFrame,
|
|
||||||
) -> nb::Result<(), AllTxChannelsUnconfiguredError> {
|
|
||||||
self.start_transmit(frame)?.await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
pub use embedded_can::{ExtendedId, Id, StandardId};
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("invalid data size error {0}")]
|
|
||||||
pub struct InvalidDataSizeError(usize);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct CanFrameNormal {
|
|
||||||
id: embedded_can::Id,
|
|
||||||
size: usize,
|
|
||||||
data: [u8; 8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanFrameNormal {
|
|
||||||
pub fn new(id: embedded_can::Id, data: &[u8]) -> Result<Self, InvalidDataSizeError> {
|
|
||||||
if data.len() > 8 {
|
|
||||||
return Err(InvalidDataSizeError(data.len()));
|
|
||||||
}
|
|
||||||
let size = data.len();
|
|
||||||
let mut data_array = [0; 8];
|
|
||||||
data_array[0..size].copy_from_slice(data);
|
|
||||||
Ok(Self {
|
|
||||||
id,
|
|
||||||
size,
|
|
||||||
data: data_array,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> embedded_can::Id {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn data(&self) -> &[u8] {
|
|
||||||
&self.data[0..self.dlc()]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn dlc(&self) -> usize {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct CanFrameRtr {
|
|
||||||
id: embedded_can::Id,
|
|
||||||
dlc: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanFrameRtr {
|
|
||||||
pub fn new(id: embedded_can::Id, dlc: usize) -> Self {
|
|
||||||
Self { id, dlc }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> embedded_can::Id {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dlc(&self) -> usize {
|
|
||||||
self.dlc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum CanFrame {
|
|
||||||
Normal(CanFrameNormal),
|
|
||||||
Rtr(CanFrameRtr),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CanFrameNormal> for CanFrame {
|
|
||||||
fn from(value: CanFrameNormal) -> Self {
|
|
||||||
Self::Normal(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CanFrameRtr> for CanFrame {
|
|
||||||
fn from(value: CanFrameRtr) -> Self {
|
|
||||||
Self::Rtr(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl embedded_can::Frame for CanFrame {
|
|
||||||
fn new(id: impl Into<embedded_can::Id>, data: &[u8]) -> Option<Self> {
|
|
||||||
if data.len() > 8 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let id: embedded_can::Id = id.into();
|
|
||||||
Some(Self::Normal(CanFrameNormal::new(id, data).unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_remote(id: impl Into<embedded_can::Id>, dlc: usize) -> Option<Self> {
|
|
||||||
let id: embedded_can::Id = id.into();
|
|
||||||
Some(Self::Rtr(CanFrameRtr::new(id, dlc)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_extended(&self) -> bool {
|
|
||||||
match self.id() {
|
|
||||||
embedded_can::Id::Extended(_) => true,
|
|
||||||
embedded_can::Id::Standard(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_remote_frame(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
CanFrame::Normal(_) => false,
|
|
||||||
CanFrame::Rtr(_) => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> embedded_can::Id {
|
|
||||||
match self {
|
|
||||||
CanFrame::Normal(can_frame_normal) => can_frame_normal.id(),
|
|
||||||
CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.id(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dlc(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
CanFrame::Normal(can_frame_normal) => can_frame_normal.dlc(),
|
|
||||||
CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.dlc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> &[u8] {
|
|
||||||
match self {
|
|
||||||
CanFrame::Normal(can_frame_normal) => can_frame_normal.data(),
|
|
||||||
CanFrame::Rtr(_) => &[],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,317 +0,0 @@
|
|||||||
use arbitrary_int::{u11, u15, u3, u4, Number};
|
|
||||||
use embedded_can::Frame;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
regs::{
|
|
||||||
self, BaseId, BufStatusAndControl, BufferState, ExtendedId, MmioCanMsgBuf, TwoBytesData,
|
|
||||||
},
|
|
||||||
CanFrame, CanFrameNormal, CanFrameRtr, CanId, InvalidBufferIndexError,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct CanChannelLowLevel {
|
|
||||||
id: CanId,
|
|
||||||
/// Message buffer index.
|
|
||||||
idx: usize,
|
|
||||||
msg_buf: MmioCanMsgBuf<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Debug for CanChannelLowLevel {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.debug_struct("CanChannel")
|
|
||||||
.field("can_id", &self.id)
|
|
||||||
.field("idx", &self.idx)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanChannelLowLevel {
|
|
||||||
/// Steal a low level instance of a CAN channel.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Circumvents ownership and safety guarantees of the HAL.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn steal(can: CanId, idx: usize) -> Result<Self, InvalidBufferIndexError> {
|
|
||||||
if idx > 14 {
|
|
||||||
return Err(InvalidBufferIndexError(idx));
|
|
||||||
}
|
|
||||||
let msg_buf = unsafe { can.steal_regs().steal_cmbs_unchecked(idx) };
|
|
||||||
Ok(Self {
|
|
||||||
id: can,
|
|
||||||
idx,
|
|
||||||
msg_buf,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Steal a low level instance of a CAN channel without and index checks.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Does not perform any bound checks. Passing an invalid index of 15 or higher leads to
|
|
||||||
/// undefined behaviour.
|
|
||||||
#[inline]
|
|
||||||
pub const unsafe fn steal_unchecked(can: CanId, idx: usize) -> Self {
|
|
||||||
if idx > 14 {
|
|
||||||
panic!("invalid buffer index for CAN low level channel");
|
|
||||||
}
|
|
||||||
let msg_buf = unsafe { can.steal_regs().steal_cmbs_unchecked(idx) };
|
|
||||||
Self {
|
|
||||||
id: can,
|
|
||||||
idx,
|
|
||||||
msg_buf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Allows to create an aribtrary amoutn of driver handles to the same message block, which
|
|
||||||
/// might lead to data races on invalid usage.
|
|
||||||
#[inline]
|
|
||||||
pub const unsafe fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
id: self.id,
|
|
||||||
idx: self.idx,
|
|
||||||
msg_buf: unsafe { self.msg_buf.clone() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.msg_buf.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_state(&self) -> Result<BufferState, u8> {
|
|
||||||
self.msg_buf.read_stat_ctrl().state()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn write_state(&mut self, buffer_state: BufferState) {
|
|
||||||
self.msg_buf.modify_stat_ctrl(|mut val| {
|
|
||||||
val.set_state(buffer_state);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure_for_transmission(&mut self, tx_priority: Option<u4>) {
|
|
||||||
self.msg_buf.modify_stat_ctrl(|mut val| {
|
|
||||||
val.set_dlc(u4::new(0));
|
|
||||||
if let Some(tx_priority) = tx_priority {
|
|
||||||
val.set_priority(tx_priority);
|
|
||||||
}
|
|
||||||
val.set_state(BufferState::TxNotActive);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_standard_id(&mut self, standard_id: embedded_can::StandardId, set_rtr: bool) {
|
|
||||||
let mut id1_reg = standard_id.as_raw() << 5;
|
|
||||||
if set_rtr {
|
|
||||||
id1_reg |= 1 << 4;
|
|
||||||
}
|
|
||||||
self.msg_buf
|
|
||||||
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_extended_id(&mut self, extended_id: embedded_can::ExtendedId, set_rtr: bool) {
|
|
||||||
let id_raw = extended_id.as_raw();
|
|
||||||
let id1_reg = (((id_raw >> 18) & 0x7FF) << 4) as u16 | ((id_raw >> 15) & 0b111) as u16;
|
|
||||||
self.msg_buf
|
|
||||||
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
|
|
||||||
let id0_reg = ((id_raw & 0x7FFF) << 1) as u16 | set_rtr as u16;
|
|
||||||
self.msg_buf
|
|
||||||
.write_id0(ExtendedId::new_with_raw_value(id0_reg as u32));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure_for_reception(&mut self) {
|
|
||||||
self.msg_buf.write_stat_ctrl(
|
|
||||||
BufStatusAndControl::builder()
|
|
||||||
.with_dlc(u4::new(0))
|
|
||||||
.with_priority(u4::new(0))
|
|
||||||
.with_state(BufferState::RxReady)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transmit_frame_unchecked(&mut self, frame: CanFrame) {
|
|
||||||
let is_remote = frame.is_remote_frame();
|
|
||||||
self.write_id(frame.id(), is_remote);
|
|
||||||
let dlc = frame.dlc();
|
|
||||||
self.msg_buf.modify_stat_ctrl(|mut ctrl| {
|
|
||||||
ctrl.set_dlc(u4::new(dlc as u8));
|
|
||||||
ctrl
|
|
||||||
});
|
|
||||||
if !is_remote {
|
|
||||||
self.msg_buf
|
|
||||||
.write_data0(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.msg_buf
|
|
||||||
.write_data1(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.msg_buf
|
|
||||||
.write_data2(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.msg_buf
|
|
||||||
.write_data3(TwoBytesData::new_with_raw_value(0));
|
|
||||||
for idx in 0..dlc {
|
|
||||||
match idx {
|
|
||||||
0 => self.msg_buf.modify_data0(|mut val| {
|
|
||||||
val.set_data_upper_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
1 => self.msg_buf.modify_data0(|mut val| {
|
|
||||||
val.set_data_lower_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
2 => self.msg_buf.modify_data1(|mut val| {
|
|
||||||
val.set_data_upper_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
3 => self.msg_buf.modify_data1(|mut val| {
|
|
||||||
val.set_data_lower_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
4 => self.msg_buf.modify_data2(|mut val| {
|
|
||||||
val.set_data_upper_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
5 => self.msg_buf.modify_data2(|mut val| {
|
|
||||||
val.set_data_lower_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
6 => self.msg_buf.modify_data3(|mut val| {
|
|
||||||
val.set_data_upper_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
7 => self.msg_buf.modify_data3(|mut val| {
|
|
||||||
val.set_data_lower_byte(frame.data()[idx]);
|
|
||||||
val
|
|
||||||
}),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.write_state(BufferState::TxOnce);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn clear_interrupt(&mut self) {
|
|
||||||
let mut regs = unsafe { self.id.steal_regs() };
|
|
||||||
let mut clear = regs::InterruptClear::new_with_raw_value(0);
|
|
||||||
clear.set_buffer(self.idx, true);
|
|
||||||
regs.write_iclr(clear);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_error_interrupt(&mut self, enable_translation: bool) {
|
|
||||||
let mut regs = unsafe { self.id.steal_regs() };
|
|
||||||
if enable_translation {
|
|
||||||
regs.modify_icen(|mut val| {
|
|
||||||
val.set_error(true);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
regs.modify_ien(|mut val| {
|
|
||||||
val.set_error(true);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_interrupt(&mut self, enable_translation: bool) {
|
|
||||||
let mut regs = unsafe { self.id.steal_regs() };
|
|
||||||
if enable_translation {
|
|
||||||
regs.modify_icen(|mut val| {
|
|
||||||
val.set_buffer(self.idx, true);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
regs.modify_ien(|mut val| {
|
|
||||||
val.set_buffer(self.idx, true);
|
|
||||||
val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_id(&mut self, id: embedded_can::Id, is_remote: bool) {
|
|
||||||
match id {
|
|
||||||
embedded_can::Id::Standard(standard_id) => {
|
|
||||||
self.msg_buf.write_id1(
|
|
||||||
BaseId::builder()
|
|
||||||
.with_mask_28_18(u11::new(standard_id.as_raw()))
|
|
||||||
.with_rtr_or_srr(is_remote)
|
|
||||||
.with_ide(false)
|
|
||||||
.with_mask_17_15(u3::new(0))
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
self.msg_buf.write_id0(ExtendedId::new_with_raw_value(0));
|
|
||||||
}
|
|
||||||
embedded_can::Id::Extended(extended_id) => {
|
|
||||||
let id_raw = extended_id.as_raw();
|
|
||||||
self.msg_buf.write_id1(
|
|
||||||
BaseId::builder()
|
|
||||||
.with_mask_28_18(u11::new(((id_raw >> 18) & 0x7FF) as u16))
|
|
||||||
.with_rtr_or_srr(true)
|
|
||||||
.with_ide(true)
|
|
||||||
.with_mask_17_15(u3::new(((id_raw >> 15) & 0b111) as u8))
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
self.msg_buf.write_id0(
|
|
||||||
ExtendedId::builder()
|
|
||||||
.with_mask_14_0(u15::new((id_raw & 0x7FFF) as u16))
|
|
||||||
.with_xrtr(is_remote)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a received CAN frame from the message buffer.
|
|
||||||
///
|
|
||||||
/// This function does not check whether the pre-requisites for reading a CAN frame were
|
|
||||||
/// met and assumes this was already checked by the user.
|
|
||||||
pub fn read_frame_unchecked(&self) -> CanFrame {
|
|
||||||
let id0 = self.msg_buf.read_id0();
|
|
||||||
let id1 = self.msg_buf.read_id1();
|
|
||||||
let data0 = self.msg_buf.read_data0();
|
|
||||||
let data1 = self.msg_buf.read_data1();
|
|
||||||
let data2 = self.msg_buf.read_data2();
|
|
||||||
let data3 = self.msg_buf.read_data3();
|
|
||||||
let mut data: [u8; 8] = [0; 8];
|
|
||||||
let mut read_data = |dlc: u4| {
|
|
||||||
(0..dlc.as_usize()).for_each(|i| match i {
|
|
||||||
0 => data[i] = data0.data_upper_byte().as_u8(),
|
|
||||||
1 => data[i] = data0.data_lower_byte().as_u8(),
|
|
||||||
2 => data[i] = data1.data_upper_byte().as_u8(),
|
|
||||||
3 => data[i] = data1.data_lower_byte().as_u8(),
|
|
||||||
4 => data[i] = data2.data_upper_byte().as_u8(),
|
|
||||||
5 => data[i] = data2.data_lower_byte().as_u8(),
|
|
||||||
6 => data[i] = data3.data_upper_byte().as_u8(),
|
|
||||||
7 => data[i] = data3.data_lower_byte().as_u8(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let (id, rtr) = if !id1.ide() {
|
|
||||||
let id = embedded_can::Id::Standard(
|
|
||||||
embedded_can::StandardId::new(id1.mask_28_18().as_u16()).unwrap(),
|
|
||||||
);
|
|
||||||
if id1.rtr_or_srr() {
|
|
||||||
(id, true)
|
|
||||||
} else {
|
|
||||||
(id, false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let id_raw = (id1.mask_28_18().as_u32() << 18)
|
|
||||||
| (id1.mask_17_15().as_u32() << 15)
|
|
||||||
| id0.mask_14_0().as_u32();
|
|
||||||
let id = embedded_can::Id::Extended(embedded_can::ExtendedId::new(id_raw).unwrap());
|
|
||||||
if id0.xrtr() {
|
|
||||||
(id, true)
|
|
||||||
} else {
|
|
||||||
(id, false)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if rtr {
|
|
||||||
CanFrameRtr::new(id, self.msg_buf.read_stat_ctrl().dlc().as_usize()).into()
|
|
||||||
} else {
|
|
||||||
let dlc = self.msg_buf.read_stat_ctrl().dlc();
|
|
||||||
read_data(dlc);
|
|
||||||
CanFrameNormal::new(id, &data[0..dlc.as_usize()])
|
|
||||||
.unwrap()
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,830 +0,0 @@
|
|||||||
//! # CAN peripheral driver.
|
|
||||||
//!
|
|
||||||
//! The VA416xx CAN module is based on the CP3UB26 module.
|
|
||||||
//!
|
|
||||||
//! Using the CAN bus generally involves the following steps:
|
|
||||||
//!
|
|
||||||
//! 1. Create a [Can] instance
|
|
||||||
//! 2. The [CanChannels] resource management singleton can be retrieved by using
|
|
||||||
//! [Can::take_channels].
|
|
||||||
//! 3. Individual [CanRx] and [CanTx] channels can be created using the [CanChannels::take]
|
|
||||||
//! function. These allow to send or receive CAN frames on individual channels.
|
|
||||||
//! 4. The [asynch::CanTxAsync] structure can be created to transmit frames asynchronously.
|
|
||||||
//! The [asynch::on_interrupt_can] function should be called in the user interrupt handler
|
|
||||||
//! for CAN0 and CAN1 for this to work properly. The interrupt handler can also take care of
|
|
||||||
//! receiving frames on [CanRx] channels with enabled interrupts.
|
|
||||||
//!
|
|
||||||
//! # Example
|
|
||||||
//!
|
|
||||||
//! - [CAN example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/can.rs)
|
|
||||||
use core::sync::atomic::AtomicBool;
|
|
||||||
|
|
||||||
use arbitrary_int::{u11, u15, u2, u3, u4, u7, Number};
|
|
||||||
use embedded_can::Frame;
|
|
||||||
use ll::CanChannelLowLevel;
|
|
||||||
use regs::{BaseId, BufferState, Control, MmioCan, TimingConfig};
|
|
||||||
use vorago_shared_periphs::enable_nvic_interrupt;
|
|
||||||
|
|
||||||
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
|
|
||||||
use libm::roundf;
|
|
||||||
|
|
||||||
pub mod frame;
|
|
||||||
pub use frame::*;
|
|
||||||
|
|
||||||
pub mod asynch;
|
|
||||||
pub mod ll;
|
|
||||||
pub mod regs;
|
|
||||||
|
|
||||||
pub const PRESCALER_MIN: u8 = 2;
|
|
||||||
pub const PRESCALER_MAX: u8 = 128;
|
|
||||||
/// 1 is the minimum value, but not recommended by Vorago.
|
|
||||||
pub const TSEG1_MIN: u8 = 1;
|
|
||||||
pub const TSEG1_MAX: u8 = 16;
|
|
||||||
pub const TSEG2_MAX: u8 = 8;
|
|
||||||
/// In addition, SJW may not be larger than TSEG2.
|
|
||||||
pub const SJW_MAX: u8 = 4;
|
|
||||||
|
|
||||||
pub const MIN_SAMPLE_POINT: f32 = 0.5;
|
|
||||||
pub const MAX_BITRATE_DEVIATION: f32 = 0.005;
|
|
||||||
|
|
||||||
static CHANNELS_TAKEN: [AtomicBool; 2] = [AtomicBool::new(false), AtomicBool::new(false)];
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub enum CanId {
|
|
||||||
Can0 = 0,
|
|
||||||
Can1 = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanId {
|
|
||||||
/// Steal the register block for the CAN ID.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// See safety of the [regs::Can::new_mmio_fixed_0].
|
|
||||||
#[inline]
|
|
||||||
pub const unsafe fn steal_regs(&self) -> regs::MmioCan<'static> {
|
|
||||||
match self {
|
|
||||||
CanId::Can0 => unsafe { regs::Can::new_mmio_fixed_0() },
|
|
||||||
CanId::Can1 => unsafe { regs::Can::new_mmio_fixed_1() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub const fn irq_id(&self) -> va416xx::Interrupt {
|
|
||||||
match self {
|
|
||||||
CanId::Can0 => va416xx::Interrupt::CAN0,
|
|
||||||
CanId::Can1 => va416xx::Interrupt::CAN1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sample point between 0 and 1.0 for the given time segments.
|
|
||||||
pub const fn calculate_sample_point(tseg1: u8, tseg2: u8) -> f32 {
|
|
||||||
let tseg1_val = tseg1 as f32;
|
|
||||||
(tseg1_val + 1.0) / (1.0 + tseg1_val + tseg2 as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ClockConfig {
|
|
||||||
prescaler: u8,
|
|
||||||
tseg1: u8,
|
|
||||||
tseg2: u8,
|
|
||||||
sjw: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockConfig {
|
|
||||||
/// New clock configuration from the raw configuration values.
|
|
||||||
///
|
|
||||||
/// The values specified here are not the register values, but the actual numerical values
|
|
||||||
/// relevant for calculations.
|
|
||||||
///
|
|
||||||
/// The values have the following requirements:
|
|
||||||
///
|
|
||||||
/// - Prescaler must be between 2 and 128.
|
|
||||||
/// - TSEG1 must be smaller than 16 and should be larger than 1.
|
|
||||||
/// - TSEG2 must be smaller than 8 and small enough so that the calculated sample point
|
|
||||||
/// is larger than 0.5 (50 %).
|
|
||||||
/// - SJW (Synchronization Jump Width) must be smaller than the smaller of the time segment
|
|
||||||
/// configuration values and smaller than 4.
|
|
||||||
pub fn new(prescaler: u8, tseg1: u8, tseg2: u8, sjw: u8) -> Result<Self, ClockConfigError> {
|
|
||||||
if !(PRESCALER_MIN..=PRESCALER_MAX).contains(&prescaler.value()) {
|
|
||||||
return Err(ClockConfigError::CanNotFindPrescaler);
|
|
||||||
}
|
|
||||||
if tseg1 == 0 || tseg2 == 0 {
|
|
||||||
return Err(ClockConfigError::TsegIsZero);
|
|
||||||
}
|
|
||||||
if tseg1 > TSEG1_MAX {
|
|
||||||
return Err(ClockConfigError::InvalidTseg1);
|
|
||||||
}
|
|
||||||
if tseg2 > TSEG2_MAX {
|
|
||||||
return Err(ClockConfigError::InvalidTseg2);
|
|
||||||
}
|
|
||||||
let smaller_tseg = core::cmp::min(tseg1.value(), tseg2.value());
|
|
||||||
if sjw.value() > smaller_tseg || sjw > SJW_MAX {
|
|
||||||
return Err(InvalidSjwError(sjw).into());
|
|
||||||
}
|
|
||||||
let sample_point = calculate_sample_point(tseg1, tseg2);
|
|
||||||
if sample_point < MIN_SAMPLE_POINT {
|
|
||||||
return Err(InvalidSamplePointError { sample_point }.into());
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
prescaler,
|
|
||||||
tseg1,
|
|
||||||
tseg2,
|
|
||||||
sjw,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the clock configuration for the given input clock, the target bitrate and for a
|
|
||||||
/// set of timing parameters. The CAN controller uses the APB1 clock.
|
|
||||||
///
|
|
||||||
/// This function basically calculates the necessary prescaler to achieve the given timing
|
|
||||||
/// parameters. It also performs sanity and validity checks for the calculated prescaler:
|
|
||||||
/// The bitrate error for the given prescaler needs to be smaller than 0.5 %.
|
|
||||||
pub fn from_bitrate_and_segments(
|
|
||||||
clocks: &Clocks,
|
|
||||||
bitrate: Hertz,
|
|
||||||
tseg1: u8,
|
|
||||||
tseg2: u8,
|
|
||||||
sjw: u8,
|
|
||||||
) -> Result<ClockConfig, ClockConfigError> {
|
|
||||||
if bitrate.raw() == 0 {
|
|
||||||
return Err(ClockConfigError::BitrateIsZero);
|
|
||||||
}
|
|
||||||
let nominal_bit_time = 1 + tseg1 as u32 + tseg2 as u32;
|
|
||||||
let prescaler =
|
|
||||||
roundf(clocks.apb1().raw() as f32 / (bitrate.raw() as f32 * nominal_bit_time as f32))
|
|
||||||
as u32;
|
|
||||||
if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
|
|
||||||
return Err(ClockConfigError::CanNotFindPrescaler);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual_bitrate = (clocks.apb1().raw() as f32) / (prescaler * nominal_bit_time) as f32;
|
|
||||||
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
|
|
||||||
if bitrate_deviation > MAX_BITRATE_DEVIATION {
|
|
||||||
return Err(ClockConfigError::BitrateErrorTooLarge);
|
|
||||||
}
|
|
||||||
// The subtractions are fine because we made checks to avoid underflows.
|
|
||||||
Self::new(prescaler as u8, tseg1, tseg2, sjw)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn sjw_reg_value(&self) -> u2 {
|
|
||||||
u2::new(self.sjw.value() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn tseg1_reg_value(&self) -> u4 {
|
|
||||||
u4::new(self.tseg1.value() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn tseg2_reg_value(&self) -> u3 {
|
|
||||||
u3::new(self.tseg2.value() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn prescaler_reg_value(&self) -> u7 {
|
|
||||||
u7::new(self.prescaler.value() - 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate all viable clock configurations for the given input clock, the target bitrate and
|
|
||||||
/// for a sample point between 0.5 and 1.0.
|
|
||||||
///
|
|
||||||
/// There are various recommendations for the sample point when using the CAN bus. The value
|
|
||||||
/// depends on different parameters like the bus length and propagation time, as well as
|
|
||||||
/// the information processing time of the nodes. It should always be at least 50 %.
|
|
||||||
/// In doubt, select a value like 0.75.
|
|
||||||
///
|
|
||||||
/// - The [Python CAN library](https://python-can.readthedocs.io/en/stable/bit_timing.html)
|
|
||||||
/// assumes a default value of 69 % as the sample point if none is specified.
|
|
||||||
/// - CiA-301 recommends 87.5 %
|
|
||||||
/// - For simpler setups like laboratory setups, smaller values should work as well.
|
|
||||||
///
|
|
||||||
/// A clock configuration is consideres viable when
|
|
||||||
///
|
|
||||||
/// - The sample point deviation is less than 5 %.
|
|
||||||
/// - The bitrate error is less than +-0.5 %.
|
|
||||||
///
|
|
||||||
/// SJW will be set to either TSEG2 or 4, whichever is smaller.
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub fn calculate_all_viable_clock_configs(
|
|
||||||
apb1_clock: Hertz,
|
|
||||||
bitrate: Hertz,
|
|
||||||
sample_point: f32,
|
|
||||||
) -> Result<alloc::vec::Vec<(ClockConfig, f32)>, InvalidSamplePointError> {
|
|
||||||
if sample_point < 0.5 || sample_point > 1.0 {
|
|
||||||
return Err(InvalidSamplePointError { sample_point });
|
|
||||||
}
|
|
||||||
let mut configs = alloc::vec::Vec::new();
|
|
||||||
for prescaler in PRESCALER_MIN..PRESCALER_MAX {
|
|
||||||
let nom_bit_time = calculate_nominal_bit_time(apb1_clock, bitrate, prescaler);
|
|
||||||
// This is taken from the Python CAN library. NBT should not be too small.
|
|
||||||
if nom_bit_time < 8 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let actual_bitrate = calculate_actual_bitrate(apb1_clock, prescaler, nom_bit_time);
|
|
||||||
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
|
|
||||||
if bitrate_deviation > 0.05 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let tseg1 = roundf(sample_point * nom_bit_time as f32) as u32 - 1;
|
|
||||||
if tseg1 > TSEG1_MAX as u32 || tseg1 < TSEG1_MIN as u32 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// limit tseg1, so tseg2 is at least 1 TQ
|
|
||||||
let tseg1 = core::cmp::min(tseg1, nom_bit_time - 2) as u8;
|
|
||||||
let tseg2 = nom_bit_time - tseg1 as u32 - 1;
|
|
||||||
if tseg2 > TSEG2_MAX as u32 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let tseg2 = tseg2 as u8;
|
|
||||||
let sjw = core::cmp::min(tseg2, 4) as u8;
|
|
||||||
// Use percent to have a higher resolution for the sample point deviation.
|
|
||||||
let sample_point_actual = roundf(calculate_sample_point(tseg1, tseg2) * 100.0) as u32;
|
|
||||||
let sample_point = roundf(sample_point * 100.0) as u32;
|
|
||||||
let deviation = (sample_point_actual as i32 - sample_point as i32).abs();
|
|
||||||
if deviation > 5 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
configs.push((
|
|
||||||
ClockConfig {
|
|
||||||
prescaler,
|
|
||||||
tseg1,
|
|
||||||
tseg2,
|
|
||||||
sjw,
|
|
||||||
},
|
|
||||||
bitrate_deviation,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(configs)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub const fn calculate_nominal_bit_time(
|
|
||||||
apb1_clock: Hertz,
|
|
||||||
target_bitrate: Hertz,
|
|
||||||
prescaler: u8,
|
|
||||||
) -> u32 {
|
|
||||||
apb1_clock.raw() / (target_bitrate.raw() * prescaler as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub const fn calculate_actual_bitrate(apb1_clock: Hertz, prescaler: u8, nom_bit_time: u32) -> f32 {
|
|
||||||
apb1_clock.raw() as f32 / (prescaler as u32 * nom_bit_time) as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub const fn calculate_bitrate_deviation(actual_bitrate: f32, target_bitrate: Hertz) -> f32 {
|
|
||||||
(actual_bitrate - target_bitrate.raw() as f32).abs() / target_bitrate.raw() as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CanMarker {
|
|
||||||
const ID: CanId;
|
|
||||||
const IRQ: va416xx::Interrupt;
|
|
||||||
const PERIPH_SEL: PeripheralSelect;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanMarker for va416xx::Can0 {
|
|
||||||
const ID: CanId = CanId::Can0;
|
|
||||||
const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN0;
|
|
||||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanMarker for va416xx::Can1 {
|
|
||||||
const ID: CanId = CanId::Can1;
|
|
||||||
const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN1;
|
|
||||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[error("invalid buffer index {0}")]
|
|
||||||
pub struct InvalidBufferIndexError(usize);
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[error("sjw must be less than or equal to the smaller tseg value")]
|
|
||||||
pub struct InvalidSjwError(u8);
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("invalid sample point {sample_point}")]
|
|
||||||
pub struct InvalidSamplePointError {
|
|
||||||
/// Sample point, should be larger than 0.5 (50 %) but was not.
|
|
||||||
sample_point: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum ClockConfigError {
|
|
||||||
#[error("invalid sjw: {0}")]
|
|
||||||
InvalidSjw(#[from] InvalidSjwError),
|
|
||||||
#[error("TSEG is zero which is not allowed")]
|
|
||||||
TsegIsZero,
|
|
||||||
#[error("TSEG1 is larger than 16")]
|
|
||||||
InvalidTseg1,
|
|
||||||
#[error("TSEG1 is larger than 8")]
|
|
||||||
InvalidTseg2,
|
|
||||||
#[error("invalid sample point: {0}")]
|
|
||||||
InvalidSamplePoint(#[from] InvalidSamplePointError),
|
|
||||||
#[error("bitrate is zero")]
|
|
||||||
BitrateIsZero,
|
|
||||||
#[error("bitrate error larger than +-0.5 %")]
|
|
||||||
BitrateErrorTooLarge,
|
|
||||||
#[error("maximum or minimum allowed prescaler is not sufficient for target bitrate clock")]
|
|
||||||
CanNotFindPrescaler,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main CAN peripheral driver.
|
|
||||||
pub struct Can {
|
|
||||||
regs: regs::MmioCan<'static>,
|
|
||||||
id: CanId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Can {
|
|
||||||
pub fn new<CanI: CanMarker>(_can: CanI, clk_config: ClockConfig) -> Self {
|
|
||||||
enable_peripheral_clock(CanI::PERIPH_SEL);
|
|
||||||
let id = CanI::ID;
|
|
||||||
let mut regs = if id == CanId::Can0 {
|
|
||||||
unsafe { regs::Can::new_mmio_fixed_0() }
|
|
||||||
} else {
|
|
||||||
unsafe { regs::Can::new_mmio_fixed_1() }
|
|
||||||
};
|
|
||||||
// Disable the CAN bus before configuring it.
|
|
||||||
regs.write_control(Control::new_with_raw_value(0));
|
|
||||||
for i in 0..15 {
|
|
||||||
regs.cmbs(i).unwrap().reset();
|
|
||||||
}
|
|
||||||
regs.write_timing(
|
|
||||||
TimingConfig::builder()
|
|
||||||
.with_tseg2(clk_config.tseg2_reg_value())
|
|
||||||
.with_tseg1(clk_config.tseg1_reg_value())
|
|
||||||
.with_sync_jump_width(clk_config.sjw_reg_value())
|
|
||||||
.with_prescaler(clk_config.prescaler_reg_value())
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
Self { regs, id }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This configures the global mask so that acceptance is only determined by an exact match
|
|
||||||
/// with the ID in the receive message buffers. This is the default reset configuration for
|
|
||||||
/// the global mask as well.
|
|
||||||
pub fn set_global_mask_for_exact_id_match(&mut self) {
|
|
||||||
self.regs
|
|
||||||
.write_gmskx(regs::ExtendedId::new_with_raw_value(0));
|
|
||||||
self.regs.write_gmskb(BaseId::new_with_raw_value(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a resource management singleton for the 15 CAN channels.
|
|
||||||
pub fn take_channels(&self) -> Option<CanChannels> {
|
|
||||||
if CHANNELS_TAKEN[self.id() as usize].swap(true, core::sync::atomic::Ordering::SeqCst) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(CanChannels::new(self.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Similar to [Self::set_global_mask_for_exact_id_match] but masks the XRTR and RTR/SRR bits.
|
|
||||||
///
|
|
||||||
/// This is useful for when transmitting remote frames with the RTR bit set. The hardware
|
|
||||||
/// will automatically go into the [regs::BufferState::RxReady] state after the transmission,
|
|
||||||
/// but the XRTR and RTR/SRR bits need to be masked for the response frame to be accepted
|
|
||||||
/// on that buffer.
|
|
||||||
pub fn set_global_mask_for_exact_id_match_with_rtr_masked(&mut self) {
|
|
||||||
self.regs.write_gmskx(
|
|
||||||
regs::ExtendedId::builder()
|
|
||||||
.with_mask_14_0(u15::new(0))
|
|
||||||
.with_xrtr(true)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
self.regs.write_gmskb(
|
|
||||||
BaseId::builder()
|
|
||||||
.with_mask_28_18(u11::new(0))
|
|
||||||
.with_rtr_or_srr(true)
|
|
||||||
.with_ide(false)
|
|
||||||
.with_mask_17_15(u3::new(0))
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This configures the base mask for buffer 14 so that acceptance is only determined by an
|
|
||||||
/// exact match with the ID in the receive message buffers. This is the default reset
|
|
||||||
/// configuration for the global mask as well.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_base_mask_for_exact_id_match(&mut self) {
|
|
||||||
self.regs
|
|
||||||
.write_bmskx(regs::ExtendedId::new_with_raw_value(0));
|
|
||||||
self.regs.write_bmskb(BaseId::new_with_raw_value(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This configures the base mask so that all CAN frames which are not handled by any other
|
|
||||||
/// buffers are accepted by the base buffer 14.
|
|
||||||
#[inline]
|
|
||||||
pub fn set_base_mask_for_all_match(&mut self) {
|
|
||||||
self.regs
|
|
||||||
.write_bmskx(regs::ExtendedId::new_with_raw_value(0xffff));
|
|
||||||
self.regs.write_bmskb(BaseId::new_with_raw_value(0xffff));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn regs(&mut self) -> &mut MmioCan<'static> {
|
|
||||||
&mut self.regs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all interrupts.
|
|
||||||
#[inline]
|
|
||||||
pub fn clear_interrupts(&mut self) {
|
|
||||||
self.regs
|
|
||||||
.write_iclr(regs::InterruptClear::new_with_raw_value(0xFFFF_FFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function only enable the CAN interrupt vector in the NVIC.
|
|
||||||
///
|
|
||||||
/// The interrupts for the individual channels or errors still need to be enabled
|
|
||||||
/// separately.
|
|
||||||
#[inline]
|
|
||||||
pub fn enable_nvic_interrupt(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
enable_nvic_interrupt(self.id().irq_id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_error_counters(&self) -> regs::ErrorCounter {
|
|
||||||
self.regs.read_error_counter()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_error_diagnostics(&self) -> regs::DiagnosticRegister {
|
|
||||||
self.regs.read_diag()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> CanId {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn write_ctrl_reg(&mut self, ctrl: Control) {
|
|
||||||
self.regs.write_control(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn modify_control<F>(&mut self, f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(Control) -> Control,
|
|
||||||
{
|
|
||||||
self.regs.modify_control(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_bufflock(&mut self, enable: bool) {
|
|
||||||
self.regs.modify_control(|mut ctrl| {
|
|
||||||
ctrl.set_bufflock(enable);
|
|
||||||
ctrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn enable(&mut self) {
|
|
||||||
self.regs.modify_control(|mut ctrl| {
|
|
||||||
ctrl.set_enable(true);
|
|
||||||
ctrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum TxState {
|
|
||||||
Idle,
|
|
||||||
TransmittingDataFrame,
|
|
||||||
TransmittingRemoteFrame,
|
|
||||||
AwaitingRemoteFrameReply,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum InvalidTxState {
|
|
||||||
State(TxState),
|
|
||||||
BufferState(BufferState),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TxState> for InvalidTxState {
|
|
||||||
fn from(state: TxState) -> Self {
|
|
||||||
InvalidTxState::State(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BufferState> for InvalidTxState {
|
|
||||||
fn from(state: BufferState) -> Self {
|
|
||||||
InvalidTxState::BufferState(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("invalid tx state {0:?}")]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct InvalidTxStateError(pub InvalidTxState);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum RxState {
|
|
||||||
Idle,
|
|
||||||
Receiving,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("invalid rx state {0:?}")]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct InvalidRxStateError(pub RxState);
|
|
||||||
|
|
||||||
/// Driver instance to use an individual CAN channel as a transmission channel.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CanTx {
|
|
||||||
ll: CanChannelLowLevel,
|
|
||||||
mode: TxState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanTx {
|
|
||||||
pub fn new(mut ll: CanChannelLowLevel, tx_priority: Option<u4>) -> Self {
|
|
||||||
ll.reset();
|
|
||||||
ll.configure_for_transmission(tx_priority);
|
|
||||||
Self {
|
|
||||||
ll,
|
|
||||||
mode: TxState::Idle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn into_rx_channel(self) -> CanRx {
|
|
||||||
CanRx::new(self.ll)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start transmitting a frame.
|
|
||||||
///
|
|
||||||
/// The frame transmission can be polled/awaited to completion using the [Self::transfer_done]
|
|
||||||
/// method.
|
|
||||||
///
|
|
||||||
/// This function will return a [state error][InvalidTxStateError] if a transmission is already
|
|
||||||
/// active and/or the transmit buffer has an invalid state.
|
|
||||||
pub fn transmit_frame(&mut self, frame: CanFrame) -> Result<(), InvalidTxStateError> {
|
|
||||||
if self.mode == TxState::AwaitingRemoteFrameReply {
|
|
||||||
self.ll.configure_for_transmission(None);
|
|
||||||
self.mode = TxState::Idle;
|
|
||||||
}
|
|
||||||
if self.mode != TxState::Idle {
|
|
||||||
return Err(InvalidTxStateError(self.mode.into()));
|
|
||||||
}
|
|
||||||
if !frame.is_remote_frame() {
|
|
||||||
self.mode = TxState::TransmittingDataFrame;
|
|
||||||
} else {
|
|
||||||
self.mode = TxState::TransmittingRemoteFrame;
|
|
||||||
}
|
|
||||||
if let Ok(state) = self.ll.read_state() {
|
|
||||||
if state != BufferState::TxNotActive {
|
|
||||||
return Err(InvalidTxStateError(state.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.ll.transmit_frame_unchecked(frame);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll whether an active data frame transmission is done.
|
|
||||||
///
|
|
||||||
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
|
|
||||||
pub fn transfer_done(&mut self) -> nb::Result<(), InvalidTxStateError> {
|
|
||||||
if self.mode != TxState::TransmittingDataFrame {
|
|
||||||
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
|
|
||||||
}
|
|
||||||
let status = self.ll.read_state();
|
|
||||||
if status.is_err() {
|
|
||||||
return Err(nb::Error::WouldBlock);
|
|
||||||
}
|
|
||||||
let status = status.unwrap();
|
|
||||||
if status == BufferState::TxNotActive {
|
|
||||||
self.mode = TxState::Idle;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(nb::Error::WouldBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll whether an active remote frame transmission is done.
|
|
||||||
///
|
|
||||||
/// On success, returns the channel re-configured to a [CanRx] channel. This is because the
|
|
||||||
/// default behaviour of the hardware will be to re-configure the channel state to
|
|
||||||
/// [BufferState::RxReady] once the remote frame has been transmitted so that the response
|
|
||||||
/// frame can be awaited.
|
|
||||||
///
|
|
||||||
/// If the channel should instead be re-configured for transmission again,
|
|
||||||
/// [Self::remote_transfer_done_with_tx_reconfig] can be used.
|
|
||||||
///
|
|
||||||
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
|
|
||||||
pub fn remote_transfer_done(&mut self) -> nb::Result<CanRx, InvalidTxStateError> {
|
|
||||||
if self.mode != TxState::TransmittingRemoteFrame {
|
|
||||||
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
|
|
||||||
}
|
|
||||||
let status = self.ll.read_state();
|
|
||||||
if status.is_err() {
|
|
||||||
return Err(nb::Error::WouldBlock);
|
|
||||||
}
|
|
||||||
let status = status.unwrap();
|
|
||||||
if status == BufferState::RxReady {
|
|
||||||
self.mode = TxState::AwaitingRemoteFrameReply;
|
|
||||||
return Ok(CanRx {
|
|
||||||
ll: unsafe { self.ll.clone() },
|
|
||||||
mode: RxState::Receiving,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(nb::Error::WouldBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll whether an active remote frame transmission is done.
|
|
||||||
///
|
|
||||||
/// This function will re-configure the buffer back for transmission once the
|
|
||||||
/// transmission has completed.
|
|
||||||
///
|
|
||||||
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
|
|
||||||
pub fn remote_transfer_done_with_tx_reconfig(&mut self) -> nb::Result<(), InvalidTxStateError> {
|
|
||||||
if self.mode != TxState::TransmittingRemoteFrame {
|
|
||||||
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
|
|
||||||
}
|
|
||||||
let status = self.ll.read_state();
|
|
||||||
if status.is_err() {
|
|
||||||
return Err(nb::Error::WouldBlock);
|
|
||||||
}
|
|
||||||
let status = status.unwrap();
|
|
||||||
if status == BufferState::RxReady {
|
|
||||||
self.ll.write_state(BufferState::TxNotActive);
|
|
||||||
self.mode = TxState::Idle;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(nb::Error::WouldBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.ll.reset();
|
|
||||||
self.mode = TxState::Idle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Driver instance to use an individual CAN channel as a reception channel.
|
|
||||||
pub struct CanRx {
|
|
||||||
ll: CanChannelLowLevel,
|
|
||||||
mode: RxState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanRx {
|
|
||||||
pub fn new(mut ll: CanChannelLowLevel) -> Self {
|
|
||||||
ll.reset();
|
|
||||||
Self {
|
|
||||||
ll,
|
|
||||||
mode: RxState::Idle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn into_tx_channel(self, tx_priority: Option<u4>) -> CanTx {
|
|
||||||
CanTx::new(self.ll, tx_priority)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn enable_interrupt(&mut self, enable_translation: bool) {
|
|
||||||
self.ll.enable_interrupt(enable_translation);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure_for_reception_with_standard_id(
|
|
||||||
&mut self,
|
|
||||||
standard_id: embedded_can::StandardId,
|
|
||||||
set_rtr: bool,
|
|
||||||
) {
|
|
||||||
self.ll.set_standard_id(standard_id, set_rtr);
|
|
||||||
self.configure_for_reception();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure_for_reception_with_extended_id(
|
|
||||||
&mut self,
|
|
||||||
extended_id: embedded_can::ExtendedId,
|
|
||||||
set_rtr: bool,
|
|
||||||
) {
|
|
||||||
self.ll.set_extended_id(extended_id, set_rtr);
|
|
||||||
self.configure_for_reception();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure_for_reception(&mut self) {
|
|
||||||
self.ll.configure_for_reception();
|
|
||||||
self.mode = RxState::Receiving;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn frame_available(&self) -> bool {
|
|
||||||
self.ll
|
|
||||||
.read_state()
|
|
||||||
.is_ok_and(|state| state == BufferState::RxFull || state == BufferState::RxOverrun)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll for frame reception. Returns the frame if one is available.
|
|
||||||
pub fn receive(
|
|
||||||
&mut self,
|
|
||||||
reconfigure_for_reception: bool,
|
|
||||||
) -> nb::Result<CanFrame, InvalidRxStateError> {
|
|
||||||
if self.mode != RxState::Receiving {
|
|
||||||
return Err(nb::Error::Other(InvalidRxStateError(self.mode)));
|
|
||||||
}
|
|
||||||
let status = self.ll.read_state();
|
|
||||||
if status.is_err() {
|
|
||||||
return Err(nb::Error::WouldBlock);
|
|
||||||
}
|
|
||||||
let status = status.unwrap();
|
|
||||||
if status == BufferState::RxFull || status == BufferState::RxOverrun {
|
|
||||||
self.mode = RxState::Idle;
|
|
||||||
if reconfigure_for_reception {
|
|
||||||
self.ll.write_state(BufferState::RxReady);
|
|
||||||
}
|
|
||||||
return Ok(self.ll.read_frame_unchecked());
|
|
||||||
}
|
|
||||||
Err(nb::Error::WouldBlock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CanChannels {
|
|
||||||
id: CanId,
|
|
||||||
channels: [Option<CanChannelLowLevel>; 15],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanChannels {
|
|
||||||
const fn new(id: CanId) -> Self {
|
|
||||||
// Safety: Private function, ownership rules enforced by public API.
|
|
||||||
unsafe {
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
channels: [
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 0)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 1)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 2)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 3)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 4)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 5)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 6)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 7)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 8)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 9)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 10)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 11)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 12)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 13)),
|
|
||||||
Some(CanChannelLowLevel::steal_unchecked(id, 14)),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn can_id(&self) -> CanId {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take the indidivual CAN channel low level driver instance.
|
|
||||||
pub fn take(&mut self, idx: usize) -> Option<CanChannelLowLevel> {
|
|
||||||
if idx > 14 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.channels[idx].take()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn give(&mut self, idx: usize, channel: CanChannelLowLevel) {
|
|
||||||
if idx > 14 {
|
|
||||||
panic!("invalid buffer index for CAN channel");
|
|
||||||
}
|
|
||||||
self.channels[idx] = Some(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use std::println;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
#[test]
|
|
||||||
pub fn test_clock_calculator_example_1() {
|
|
||||||
let configs = super::calculate_all_viable_clock_configs(
|
|
||||||
crate::time::Hertz::from_raw(50_000_000),
|
|
||||||
crate::time::Hertz::from_raw(25_000),
|
|
||||||
0.75,
|
|
||||||
)
|
|
||||||
.expect("clock calculation failed");
|
|
||||||
// Bitrate: 25278.05 Hz. Sample point: 0.7391
|
|
||||||
assert_eq!(configs[0].prescaler, 84);
|
|
||||||
assert_eq!(configs[0].tseg1, 16);
|
|
||||||
assert_eq!(configs[0].tseg2, 6);
|
|
||||||
assert_eq!(configs[0].sjw, 4);
|
|
||||||
// Vorago sample value.
|
|
||||||
let sample_cfg = configs
|
|
||||||
.iter()
|
|
||||||
.find(|c| c.prescaler == 100)
|
|
||||||
.expect("clock config not found");
|
|
||||||
// Slightly different distribution because we use a different sample point, but
|
|
||||||
// the sum of TSEG1 and TSEG2 is the same as the Vorago example 1.
|
|
||||||
assert_eq!(sample_cfg.tseg1, 14);
|
|
||||||
assert_eq!(sample_cfg.tseg2, 5);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,416 +0,0 @@
|
|||||||
//! Custom register definitions for the CAN register block to circumvent PAC API / SVD
|
|
||||||
//! shortcomings.
|
|
||||||
|
|
||||||
use arbitrary_int::{u11, u15, u2, u3, u4, u6, u7, Number};
|
|
||||||
|
|
||||||
pub const CAN_0_BASE: usize = 0x4001_4000;
|
|
||||||
pub const CAN_1_BASE: usize = 0x4001_4400;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u4)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum BufferState {
|
|
||||||
/// Passive channel.
|
|
||||||
RxNotActive = 0b0000,
|
|
||||||
/// This condition indicated that SW wrote RxNotActive to a buffer when a data copy
|
|
||||||
/// process is still active.
|
|
||||||
RxBusy = 0b0001,
|
|
||||||
RxReady = 0b0010,
|
|
||||||
/// Indicated that data is being copied for the first time (RxRead -> RxBusy0).
|
|
||||||
RxBusy0 = 0b0011,
|
|
||||||
RxFull = 0b0100,
|
|
||||||
/// Indicated that data is being copied for the second time (RxFull -> RxBusy2).
|
|
||||||
RxBusy1 = 0b0101,
|
|
||||||
RxOverrun = 0b0110,
|
|
||||||
RxBusy2 = 0b0111,
|
|
||||||
TxNotActive = 0b1000,
|
|
||||||
/// Automatical response to a remote frame.
|
|
||||||
TxRtr = 0b1010,
|
|
||||||
/// Transmit one frame.
|
|
||||||
TxOnce = 0b1100,
|
|
||||||
TxBusy0 = 0b1101,
|
|
||||||
/// Transmit one frame, and changes to TxRtr after that. This can either be written by
|
|
||||||
/// software, or it will be written by the hardware after an auto response of the
|
|
||||||
/// [BufferState::TxRtr] state.
|
|
||||||
TxOnceRtr = 0b1110,
|
|
||||||
TxBusy2 = 0b1111,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Status control register for individual message buffers.
|
|
||||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BufStatusAndControl {
|
|
||||||
/// Data length code.
|
|
||||||
#[bits(12..=15, rw)]
|
|
||||||
dlc: u4,
|
|
||||||
#[bits(4..=7, rw)]
|
|
||||||
priority: u4,
|
|
||||||
#[bits(0..=3, rw)]
|
|
||||||
state: Option<BufferState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Timestamp(arbitrary_int::UInt<u32, 16>);
|
|
||||||
|
|
||||||
impl Timestamp {
|
|
||||||
pub fn new(value: u16) -> Self {
|
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
pub fn value(&self) -> u16 {
|
|
||||||
self.0.value() as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(&mut self, value: u16) {
|
|
||||||
self.0 = value.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TwoBytesData {
|
|
||||||
#[bits(0..=7, rw)]
|
|
||||||
data_lower_byte: u8,
|
|
||||||
#[bits(8..=15, rw)]
|
|
||||||
data_upper_byte: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(derive_mmio::Mmio)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct CanMsgBuf {
|
|
||||||
stat_ctrl: BufStatusAndControl,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
data3: TwoBytesData,
|
|
||||||
data2: TwoBytesData,
|
|
||||||
data1: TwoBytesData,
|
|
||||||
data0: TwoBytesData,
|
|
||||||
id0: ExtendedId,
|
|
||||||
id1: BaseId,
|
|
||||||
}
|
|
||||||
|
|
||||||
static_assertions::const_assert_eq!(core::mem::size_of::<CanMsgBuf>(), 0x20);
|
|
||||||
|
|
||||||
impl MmioCanMsgBuf<'_> {
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.write_stat_ctrl(BufStatusAndControl::new_with_raw_value(0));
|
|
||||||
self.write_timestamp(Timestamp::new(0));
|
|
||||||
self.write_data0(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.write_data1(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.write_data2(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.write_data3(TwoBytesData::new_with_raw_value(0));
|
|
||||||
self.write_id1(BaseId::new_with_raw_value(0));
|
|
||||||
self.write_id0(ExtendedId::new_with_raw_value(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PinLogicLevel {
|
|
||||||
DominantIsZero = 0b0,
|
|
||||||
DominantIsOne = 0b1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ErrorInterruptType {
|
|
||||||
/// EIPND bit is set on every error.
|
|
||||||
EveryError = 0b0,
|
|
||||||
/// EIPND bit is only set if error state changes as a result of a receive or transmit
|
|
||||||
/// error counter increment.
|
|
||||||
ErrorOnRxTxCounterChange = 0b1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum DataDirection {
|
|
||||||
FirstByteAtHighestAddr = 0b0,
|
|
||||||
LastByteAtHighestAddr = 0b1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
pub struct Control {
|
|
||||||
#[bit(11, rw)]
|
|
||||||
error_interrupt_type: ErrorInterruptType,
|
|
||||||
/// Enables special diagnostics features of the CAN like LO, IGNACK, LOOPBACK, INTERNAL.
|
|
||||||
#[bit(10, rw)]
|
|
||||||
diag_enable: bool,
|
|
||||||
/// CANTX and CANRX pins are internally connected to each other.
|
|
||||||
#[bit(9, rw)]
|
|
||||||
internal: bool,
|
|
||||||
/// All messages sent by the CAN controller can also be received by a CAN buffer with a
|
|
||||||
/// matching buffer ID.
|
|
||||||
#[bit(8, rw)]
|
|
||||||
loopback: bool,
|
|
||||||
/// IGNACK feature. The CAN does not expect to receive an ACK bit.
|
|
||||||
#[bit(7, rw)]
|
|
||||||
ignore_ack: bool,
|
|
||||||
/// LO feature. The CAN is only configured as a receiver.
|
|
||||||
#[bit(6, rw)]
|
|
||||||
listen_only: bool,
|
|
||||||
#[bit(5, rw)]
|
|
||||||
data_dir: DataDirection,
|
|
||||||
#[bit(4, rw)]
|
|
||||||
timestamp_enable: bool,
|
|
||||||
#[bit(3, rw)]
|
|
||||||
bufflock: bool,
|
|
||||||
#[bit(2, rw)]
|
|
||||||
tx_logic_level: PinLogicLevel,
|
|
||||||
#[bit(1, rw)]
|
|
||||||
rx_logic_level: PinLogicLevel,
|
|
||||||
#[bit(0, rw)]
|
|
||||||
enable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TimingConfig {
|
|
||||||
#[bits(0..=2, rw)]
|
|
||||||
tseg2: u3,
|
|
||||||
#[bits(3..=6, rw)]
|
|
||||||
tseg1: u4,
|
|
||||||
#[bits(7..=8, rw)]
|
|
||||||
sync_jump_width: u2,
|
|
||||||
#[bits(9..=15, rw)]
|
|
||||||
prescaler: u7,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InterruptEnable {
|
|
||||||
#[bit(15, rw)]
|
|
||||||
error: bool,
|
|
||||||
#[bit(0, rw)]
|
|
||||||
buffer: [bool; 15],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InterruptClear {
|
|
||||||
#[bit(15, w)]
|
|
||||||
error: bool,
|
|
||||||
#[bit(0, w)]
|
|
||||||
buffer: [bool; 15],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InterruptPending {
|
|
||||||
#[bit(15, r)]
|
|
||||||
error: bool,
|
|
||||||
#[bit(0, r)]
|
|
||||||
buffer: [bool; 15],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(usize)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum CanInterruptId {
|
|
||||||
None = 0b00000,
|
|
||||||
Error = 0b10000,
|
|
||||||
Buffer(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct StatusPending {
|
|
||||||
#[bits(5..=7, r)]
|
|
||||||
ns: u3,
|
|
||||||
#[bit(4, r)]
|
|
||||||
irq: bool,
|
|
||||||
#[bits(0..=3, r)]
|
|
||||||
ist: u4,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusPending {
|
|
||||||
pub fn interrupt_id(&self) -> Option<CanInterruptId> {
|
|
||||||
if !self.irq() && self.ist().value() == 0 {
|
|
||||||
return Some(CanInterruptId::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.irq() && self.ist().value() == 0 {
|
|
||||||
return Some(CanInterruptId::Error);
|
|
||||||
}
|
|
||||||
if !self.irq() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(CanInterruptId::Buffer(self.ist().as_usize() - 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ErrorCounter {
|
|
||||||
#[bits(0..=7, r)]
|
|
||||||
transmit: u8,
|
|
||||||
#[bits(8..=15, r)]
|
|
||||||
receive: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This register is unused for standard frames.
|
|
||||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ExtendedId {
|
|
||||||
/// Mask for ID bits \[14:0\] of extended frames.
|
|
||||||
#[bits(1..=15, rw)]
|
|
||||||
mask_14_0: u15,
|
|
||||||
/// CAN XRTR bit.
|
|
||||||
#[bit(0, rw)]
|
|
||||||
xrtr: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BaseId {
|
|
||||||
/// This will contain ID\[10:0\] for standard frames and bits \[28:18\] for extended frames.
|
|
||||||
#[bits(5..=15, rw)]
|
|
||||||
mask_28_18: u11,
|
|
||||||
/// This is the RTR bit for standard frames, and the SRR bit for extended frames.
|
|
||||||
#[bit(4, rw)]
|
|
||||||
rtr_or_srr: bool,
|
|
||||||
/// Identifier extension bit.
|
|
||||||
#[bit(3, rw)]
|
|
||||||
ide: bool,
|
|
||||||
/// Mask for ID bits \[17:15\] of extended frames.
|
|
||||||
#[bits(0..=2, rw)]
|
|
||||||
mask_17_15: u3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitbybit::bitenum(u4, exhaustive = true)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum ErrorFieldId {
|
|
||||||
Error = 0b0000,
|
|
||||||
ErrorDel = 0b0001,
|
|
||||||
ErrorEcho = 0b0010,
|
|
||||||
BusIdle = 0b0011,
|
|
||||||
Ack = 0b0100,
|
|
||||||
Eof = 0b0101,
|
|
||||||
Intermission = 0b0110,
|
|
||||||
SuspendTransmission = 0b0111,
|
|
||||||
Sof = 0b1000,
|
|
||||||
Arbitration = 0b1001,
|
|
||||||
Ide = 0b1010,
|
|
||||||
ExtendedArbitration = 0b1011,
|
|
||||||
R1R0 = 0b1100,
|
|
||||||
Dlc = 0b1101,
|
|
||||||
Data = 0b1110,
|
|
||||||
Crc = 0b1111,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32)]
|
|
||||||
pub struct DiagnosticRegister {
|
|
||||||
/// Shows the output value on the CAN TX pin at the time of the error.
|
|
||||||
#[bit(14, r)]
|
|
||||||
drive: bool,
|
|
||||||
/// Shows the bus value on the CAN RX pin as sampled by the CAN module at the time of the
|
|
||||||
/// error.
|
|
||||||
#[bit(13, r)]
|
|
||||||
mon: bool,
|
|
||||||
/// Indicated whether the CRC is invalid. This bit should only be checked if the EFID field
|
|
||||||
/// is [ErrorFieldId::Ack].
|
|
||||||
#[bit(12, r)]
|
|
||||||
crc: bool,
|
|
||||||
/// Indicated whether the bit stuffing rule was violated at the time the error occured.
|
|
||||||
#[bit(11, r)]
|
|
||||||
stuff: bool,
|
|
||||||
/// Indicated whether the CAN module was an active transmitter at the time the error occured.
|
|
||||||
#[bit(10, r)]
|
|
||||||
txe: bool,
|
|
||||||
#[bits(4..=9, r)]
|
|
||||||
ebid: u6,
|
|
||||||
#[bits(0..=3, r)]
|
|
||||||
efid: ErrorFieldId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Debug for DiagnosticRegister {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.debug_struct("DiagnosticRegister")
|
|
||||||
.field("efid", &self.efid())
|
|
||||||
.field("ebid", &self.ebid())
|
|
||||||
.field("txe", &self.txe())
|
|
||||||
.field("stuff", &self.stuff())
|
|
||||||
.field("crc", &self.crc())
|
|
||||||
.field("mon", &self.mon())
|
|
||||||
.field("drive", &self.drive())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
|
||||||
impl defmt::Format for DiagnosticRegister {
|
|
||||||
fn format(&self, fmt: defmt::Formatter) {
|
|
||||||
defmt::write!(
|
|
||||||
fmt,
|
|
||||||
"DiagnosticRegister {{ efid: {}, ebid: {}, txe: {}, stuff: {}, crc: {}, mon: {}, drive: {} }}",
|
|
||||||
self.efid(),
|
|
||||||
self.ebid(),
|
|
||||||
self.txe(),
|
|
||||||
self.stuff(),
|
|
||||||
self.crc(),
|
|
||||||
self.mon(),
|
|
||||||
self.drive()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(derive_mmio::Mmio)]
|
|
||||||
#[mmio(const_inner)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Can {
|
|
||||||
#[mmio(inner)]
|
|
||||||
cmbs: [CanMsgBuf; 15],
|
|
||||||
/// Hidden CAN message buffer. Only allowed to be used internally by the peripheral.
|
|
||||||
#[mmio(inner)]
|
|
||||||
_hcmb: CanMsgBuf,
|
|
||||||
control: Control,
|
|
||||||
timing: TimingConfig,
|
|
||||||
/// Global mask extension used for buffers 0 to 13.
|
|
||||||
gmskx: ExtendedId,
|
|
||||||
/// Global mask base used for buffers 0 to 13.
|
|
||||||
gmskb: BaseId,
|
|
||||||
/// Basic mask extension used for buffer 14.
|
|
||||||
bmskx: ExtendedId,
|
|
||||||
/// Basic mask base used for buffer 14.
|
|
||||||
bmskb: BaseId,
|
|
||||||
ien: InterruptEnable,
|
|
||||||
#[mmio(PureRead)]
|
|
||||||
ipnd: InterruptPending,
|
|
||||||
#[mmio(Write)]
|
|
||||||
iclr: InterruptClear,
|
|
||||||
/// Interrupt Code Enable Register.
|
|
||||||
icen: InterruptEnable,
|
|
||||||
#[mmio(PureRead)]
|
|
||||||
status_pending: StatusPending,
|
|
||||||
#[mmio(PureRead)]
|
|
||||||
error_counter: ErrorCounter,
|
|
||||||
#[mmio(PureRead)]
|
|
||||||
diag: DiagnosticRegister,
|
|
||||||
#[mmio(PureRead)]
|
|
||||||
timer: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
static_assertions::const_assert_eq!(core::mem::size_of::<Can>(), 0x238);
|
|
||||||
|
|
||||||
impl Can {
|
|
||||||
/// Create a new CAN MMIO instance for peripheral 0.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
|
||||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
|
||||||
/// interfere with each other.
|
|
||||||
pub const unsafe fn new_mmio_fixed_0() -> MmioCan<'static> {
|
|
||||||
Self::new_mmio_at(CAN_0_BASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new CAN MMIO instance for peripheral 1.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
|
||||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
|
||||||
/// interfere with each other.
|
|
||||||
pub const unsafe fn new_mmio_fixed_1() -> MmioCan<'static> {
|
|
||||||
Self::new_mmio_at(CAN_1_BASE)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +1,61 @@
|
|||||||
//! API for using the [crate::pac::Clkgen] peripheral.
|
//! API for using the [crate::pac::Clkgen] peripheral.
|
||||||
//!
|
//!
|
||||||
//! It also includes functionality to enable the peripheral clocks.
|
//! It also includes functionality to enable the peripheral clocks.
|
||||||
//! Calling [ClockConfigurator::new] returns a builder structure which allows
|
//! Calling [ClkgenExt::constrain] on the [crate::pac::Clkgen] peripheral generates the
|
||||||
//! setting up the clock.
|
//! [ClkgenCfgr] structure which can be used to configure and set up the clock.
|
||||||
//!
|
//!
|
||||||
//! Calling [ClockConfigurator::freeze] returns the frozen clock configuration inside the [Clocks]
|
//! Calling [ClkgenCfgr::freeze] returns the frozen clock configuration inside the [Clocks]
|
||||||
//! structure. This structure can also be used to configure other structures provided by this HAL.
|
//! structure. This structure can also be used to configure other structures provided by this HAL.
|
||||||
//!
|
//!
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
//! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
use crate::adc::ADC_MAX_CLK;
|
use crate::adc::ADC_MAX_CLK;
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
|
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
pub use vorago_shared_periphs::clock::{Clocks, HBO_FREQ};
|
|
||||||
use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
|
|
||||||
|
|
||||||
|
pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
|
||||||
pub const XTAL_OSC_TSTART_MS: u32 = 15;
|
pub const XTAL_OSC_TSTART_MS: u32 = 15;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub enum PeripheralSelect {
|
||||||
|
Spi0 = 0,
|
||||||
|
Spi1 = 1,
|
||||||
|
Spi2 = 2,
|
||||||
|
Spi3 = 3,
|
||||||
|
Uart0 = 4,
|
||||||
|
Uart1 = 5,
|
||||||
|
Uart2 = 6,
|
||||||
|
I2c0 = 7,
|
||||||
|
I2c1 = 8,
|
||||||
|
I2c2 = 9,
|
||||||
|
Can0 = 10,
|
||||||
|
Can1 = 11,
|
||||||
|
Rng = 12,
|
||||||
|
Adc = 13,
|
||||||
|
Dac = 14,
|
||||||
|
Dma = 15,
|
||||||
|
Ebi = 16,
|
||||||
|
Eth = 17,
|
||||||
|
Spw = 18,
|
||||||
|
Clkgen = 19,
|
||||||
|
IrqRouter = 20,
|
||||||
|
IoConfig = 21,
|
||||||
|
Utility = 22,
|
||||||
|
Watchdog = 23,
|
||||||
|
PortA = 24,
|
||||||
|
PortB = 25,
|
||||||
|
PortC = 26,
|
||||||
|
PortD = 27,
|
||||||
|
PortE = 28,
|
||||||
|
PortF = 29,
|
||||||
|
PortG = 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PeripheralClock = PeripheralSelect;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum FilterClkSel {
|
pub enum FilterClkSel {
|
||||||
SysClk = 0,
|
SysClk = 0,
|
||||||
Clk1 = 1,
|
Clk1 = 1,
|
||||||
@ -33,6 +67,81 @@ pub enum FilterClkSel {
|
|||||||
Clk7 = 7,
|
Clk7 = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) {
|
||||||
|
syscfg
|
||||||
|
.peripheral_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) {
|
||||||
|
syscfg
|
||||||
|
.peripheral_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn assert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
|
||||||
|
syscfg
|
||||||
|
.peripheral_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
|
||||||
|
syscfg
|
||||||
|
.peripheral_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn assert_periph_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
|
||||||
|
assert_periph_reset(syscfg, periph);
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
deassert_periph_reset(syscfg, periph);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SyscfgExt {
|
||||||
|
fn enable_peripheral_clock(&mut self, clock: PeripheralClock);
|
||||||
|
|
||||||
|
fn disable_peripheral_clock(&mut self, clock: PeripheralClock);
|
||||||
|
|
||||||
|
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
|
||||||
|
|
||||||
|
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
|
||||||
|
|
||||||
|
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyscfgExt for pac::Sysconfig {
|
||||||
|
#[inline(always)]
|
||||||
|
fn enable_peripheral_clock(&mut self, clock: PeripheralClock) {
|
||||||
|
enable_peripheral_clock(self, clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
|
||||||
|
disable_peripheral_clock(self, clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
|
||||||
|
assert_periph_reset(self, clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
|
||||||
|
deassert_periph_reset(self, clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
|
||||||
|
assert_periph_reset_for_two_cycles(self, periph)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
|
/// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
@ -111,12 +220,12 @@ pub fn pll_setup_delay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait ClkgenExt {
|
pub trait ClkgenExt {
|
||||||
fn constrain(self) -> ClockConfigurator;
|
fn constrain(self) -> ClkgenCfgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClkgenExt for pac::Clkgen {
|
impl ClkgenExt for pac::Clkgen {
|
||||||
fn constrain(self) -> ClockConfigurator {
|
fn constrain(self) -> ClkgenCfgr {
|
||||||
ClockConfigurator {
|
ClkgenCfgr {
|
||||||
source_clk: None,
|
source_clk: None,
|
||||||
ref_clk_sel: RefClkSel::None,
|
ref_clk_sel: RefClkSel::None,
|
||||||
clksel_sys: ClkselSys::Hbo,
|
clksel_sys: ClkselSys::Hbo,
|
||||||
@ -129,6 +238,21 @@ impl ClkgenExt for pac::Clkgen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ClkgenCfgr {
|
||||||
|
ref_clk_sel: RefClkSel,
|
||||||
|
clksel_sys: ClkselSys,
|
||||||
|
clk_div_sel: ClkDivSel,
|
||||||
|
/// The source clock frequency which is either an external clock connected to XTAL_N, or a
|
||||||
|
/// crystal connected to the XTAL_OSC input.
|
||||||
|
source_clk: Option<Hertz>,
|
||||||
|
pll_cfg: Option<PllCfg>,
|
||||||
|
clk_lost_detection: bool,
|
||||||
|
/// Feature only works on revision B of the board.
|
||||||
|
#[cfg(feature = "revb")]
|
||||||
|
pll_lock_lost_detection: bool,
|
||||||
|
clkgen: pac::Clkgen,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub struct ClkSourceFreqNotSet;
|
pub struct ClkSourceFreqNotSet;
|
||||||
@ -142,21 +266,6 @@ pub enum ClkCfgError {
|
|||||||
InconsistentCfg,
|
InconsistentCfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClockConfigurator {
|
|
||||||
ref_clk_sel: RefClkSel,
|
|
||||||
clksel_sys: ClkselSys,
|
|
||||||
clk_div_sel: ClkDivSel,
|
|
||||||
/// The source clock frequency which is either an external clock connected to XTAL_N, or a
|
|
||||||
/// crystal connected to the XTAL_OSC input.
|
|
||||||
source_clk: Option<Hertz>,
|
|
||||||
pll_cfg: Option<PllCfg>,
|
|
||||||
clk_lost_detection: bool,
|
|
||||||
/// Feature only works on revision B of the board.
|
|
||||||
#[cfg(feature = "revb")]
|
|
||||||
pll_lock_lost_detection: bool,
|
|
||||||
clkgen: pac::Clkgen,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delays a given amount of milliseconds.
|
/// Delays a given amount of milliseconds.
|
||||||
///
|
///
|
||||||
/// Taken from the HAL implementation. This implementation is probably not precise and it
|
/// Taken from the HAL implementation. This implementation is probably not precise and it
|
||||||
@ -171,30 +280,7 @@ pub fn hbo_clock_delay_ms(ms: u32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockConfigurator {
|
impl ClkgenCfgr {
|
||||||
/// Create a new clock configuration instance.
|
|
||||||
pub fn new(clkgen: pac::Clkgen) -> Self {
|
|
||||||
ClockConfigurator {
|
|
||||||
source_clk: None,
|
|
||||||
ref_clk_sel: RefClkSel::None,
|
|
||||||
clksel_sys: ClkselSys::Hbo,
|
|
||||||
clk_div_sel: ClkDivSel::Div1,
|
|
||||||
clk_lost_detection: false,
|
|
||||||
pll_lock_lost_detection: false,
|
|
||||||
pll_cfg: None,
|
|
||||||
clkgen,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Steals a new [ClockConfigurator] instance.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Circumvents HAL ownership rules.
|
|
||||||
pub unsafe fn steal() -> Self {
|
|
||||||
Self::new(unsafe { pac::Clkgen::steal() })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn source_clk(mut self, src_clk: Hertz) -> Self {
|
pub fn source_clk(mut self, src_clk: Hertz) -> Self {
|
||||||
self.source_clk = Some(src_clk);
|
self.source_clk = Some(src_clk);
|
||||||
@ -224,12 +310,6 @@ impl ClockConfigurator {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn pll_cfg(mut self, pll_cfg: PllCfg) -> Self {
|
|
||||||
self.pll_cfg = Some(pll_cfg);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
|
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
|
||||||
self.ref_clk_sel = ref_clk_sel;
|
self.ref_clk_sel = ref_clk_sel;
|
||||||
@ -237,7 +317,7 @@ impl ClockConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Configures all clocks and return a clock configuration structure containing the final
|
/// Configures all clocks and return a clock configuration structure containing the final
|
||||||
/// frozen clocks.
|
/// frozen clock.
|
||||||
///
|
///
|
||||||
/// Internal implementation details: This implementation is based on the HAL implementation
|
/// Internal implementation details: This implementation is based on the HAL implementation
|
||||||
/// which performs a lot of delays. I do not know if all of those are necessary, but
|
/// which performs a lot of delays. I do not know if all of those are necessary, but
|
||||||
@ -245,7 +325,7 @@ impl ClockConfigurator {
|
|||||||
/// might have had a reason for those, so I am going to keep them. Chances are, this
|
/// might have had a reason for those, so I am going to keep them. Chances are, this
|
||||||
/// process only has to be performed once, and it does not matter if it takes a few
|
/// process only has to be performed once, and it does not matter if it takes a few
|
||||||
/// microseconds or milliseconds longer.
|
/// microseconds or milliseconds longer.
|
||||||
pub fn freeze(self) -> Result<Clocks, ClkCfgError> {
|
pub fn freeze(self, syscfg: &mut pac::Sysconfig) -> Result<Clocks, ClkCfgError> {
|
||||||
// Sanitize configuration.
|
// Sanitize configuration.
|
||||||
if self.source_clk.is_none() {
|
if self.source_clk.is_none() {
|
||||||
return Err(ClkCfgError::ClkSourceFreqNotSet);
|
return Err(ClkCfgError::ClkSourceFreqNotSet);
|
||||||
@ -260,7 +340,7 @@ impl ClockConfigurator {
|
|||||||
return Err(ClkCfgError::PllConfigNotSet);
|
return Err(ClkCfgError::PllConfigNotSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_peripheral_clock(PeripheralSelect::Clkgen);
|
syscfg.enable_peripheral_clock(PeripheralSelect::Clkgen);
|
||||||
let mut final_sysclk = self.source_clk.unwrap();
|
let mut final_sysclk = self.source_clk.unwrap();
|
||||||
// The HAL forces back the HBO clock here with a delay.. Even though this is
|
// The HAL forces back the HBO clock here with a delay.. Even though this is
|
||||||
// not stricly necessary when coming from a fresh start, it could be still become relevant
|
// not stricly necessary when coming from a fresh start, it could be still become relevant
|
||||||
@ -344,9 +424,7 @@ impl ClockConfigurator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit()),
|
||||||
self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.clk_lost_detection {
|
if self.clk_lost_detection {
|
||||||
@ -369,20 +447,11 @@ impl ClockConfigurator {
|
|||||||
.ctrl0()
|
.ctrl0()
|
||||||
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
|
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
|
||||||
|
|
||||||
Ok(Clocks::__new(
|
|
||||||
final_sysclk,
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
self.cfg_adc_clk_div(final_sysclk),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
fn cfg_adc_clk_div(&self, final_sysclk: Hertz) -> Hertz {
|
|
||||||
// I will just do the ADC stuff like Vorago does it.
|
// I will just do the ADC stuff like Vorago does it.
|
||||||
// ADC clock (must be 2-12.5 MHz)
|
// ADC clock (must be 2-12.5 MHz)
|
||||||
// NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
|
// NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
|
||||||
// For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
|
// For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
|
||||||
if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
|
let adc_clk = if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
|
||||||
self.clkgen
|
self.clkgen
|
||||||
.ctrl1()
|
.ctrl1()
|
||||||
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
|
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
|
||||||
@ -392,8 +461,61 @@ impl ClockConfigurator {
|
|||||||
.ctrl1()
|
.ctrl1()
|
||||||
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
|
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
|
||||||
final_sysclk / 8
|
final_sysclk / 8
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Clocks {
|
||||||
|
sysclk: final_sysclk,
|
||||||
|
apb1: final_sysclk / 2,
|
||||||
|
apb2: final_sysclk / 4,
|
||||||
|
adc_clk,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Frozen clock frequencies
|
||||||
|
///
|
||||||
|
/// The existence of this value indicates that the clock configuration can no longer be changed.
|
||||||
|
/// The [self] module documentation gives some more information on how to retrieve an instance
|
||||||
|
/// of this structure.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct Clocks {
|
||||||
|
sysclk: Hertz,
|
||||||
|
apb1: Hertz,
|
||||||
|
apb2: Hertz,
|
||||||
|
adc_clk: Hertz,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clocks {
|
||||||
|
/// Returns the frequency of the HBO clock
|
||||||
|
pub fn hbo(&self) -> Hertz {
|
||||||
|
HBO_FREQ
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the frequency of the APB0 which is equal to the system clock.
|
||||||
|
pub fn apb0(&self) -> Hertz {
|
||||||
|
self.sysclk()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns system clock divied by 2.
|
||||||
|
pub fn apb1(&self) -> Hertz {
|
||||||
|
self.apb1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns system clock divied by 4.
|
||||||
|
pub fn apb2(&self) -> Hertz {
|
||||||
|
self.apb2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the system (core) frequency
|
||||||
|
pub fn sysclk(&self) -> Hertz {
|
||||||
|
self.sysclk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ADC clock frequency which has a separate divider.
|
||||||
|
pub fn adc_clk(&self) -> Hertz {
|
||||||
|
self.adc_clk
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rearm_sysclk_lost() {
|
pub fn rearm_sysclk_lost() {
|
||||||
|
@ -1,28 +1,21 @@
|
|||||||
//! Digital to Analog Converter (DAC) driver.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
|
|
||||||
use vorago_shared_periphs::{
|
use crate::{
|
||||||
disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
|
clock::{Clocks, PeripheralSelect, SyscfgExt},
|
||||||
PeripheralSelect,
|
pac,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{clock::Clocks, pac};
|
|
||||||
|
|
||||||
pub type DacRegisterBlock = pac::dac0::RegisterBlock;
|
pub type DacRegisterBlock = pac::dac0::RegisterBlock;
|
||||||
|
|
||||||
/// Common trait implemented by all PAC peripheral access structures. The register block
|
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||||
/// format is the same for all DAC blocks.
|
/// format is the same for all DAC blocks.
|
||||||
pub trait DacMarker: Deref<Target = DacRegisterBlock> {
|
pub trait Instance: Deref<Target = DacRegisterBlock> {
|
||||||
const IDX: u8;
|
const IDX: u8;
|
||||||
|
|
||||||
fn ptr() -> *const DacRegisterBlock;
|
fn ptr() -> *const DacRegisterBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DacMarker for pac::Dac0 {
|
impl Instance for pac::Dac0 {
|
||||||
const IDX: u8 = 0;
|
const IDX: u8 = 0;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -31,7 +24,7 @@ impl DacMarker for pac::Dac0 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DacMarker for pac::Dac1 {
|
impl Instance for pac::Dac1 {
|
||||||
const IDX: u8 = 1;
|
const IDX: u8 = 1;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -52,37 +45,40 @@ pub enum DacSettling {
|
|||||||
Apb2Times150 = 6,
|
Apb2Times150 = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Dac(*const DacRegisterBlock);
|
pub struct Dac<DacInstance> {
|
||||||
|
dac: DacInstance,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub struct ValueTooLarge;
|
pub struct ValueTooLarge;
|
||||||
|
|
||||||
impl Dac {
|
impl<DacInstance: Instance> Dac<DacInstance> {
|
||||||
/// Create a new [Dac] driver instance.
|
/// Create a new [Dac] driver instance.
|
||||||
///
|
///
|
||||||
/// The [Clocks] structure is expected here as well to ensure the clock was set up properly.
|
/// The [Clocks] structure is expected here as well to ensure the clock was set up properly.
|
||||||
pub fn new<Dac: DacMarker>(dac: Dac, dac_settling: DacSettling, _clocks: &Clocks) -> Self {
|
pub fn new(
|
||||||
enable_peripheral_clock(PeripheralSelect::Dac);
|
syscfg: &mut pac::Sysconfig,
|
||||||
|
dac: DacInstance,
|
||||||
|
dac_settling: DacSettling,
|
||||||
|
_clocks: &Clocks,
|
||||||
|
) -> Self {
|
||||||
|
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
|
||||||
|
|
||||||
dac.ctrl1().write(|w| {
|
dac.ctrl1().write(|w| {
|
||||||
w.dac_en().set_bit();
|
w.dac_en().set_bit();
|
||||||
// SAFETY: Enum values are valid values only.
|
// SAFETY: Enum values are valid values only.
|
||||||
unsafe { w.dac_settling().bits(dac_settling as u8) }
|
unsafe { w.dac_settling().bits(dac_settling as u8) }
|
||||||
});
|
});
|
||||||
let mut dac = Self(Dac::ptr());
|
let dac = Self { dac };
|
||||||
dac.clear_fifo();
|
dac.clear_fifo();
|
||||||
dac.clear_irqs();
|
dac.clear_irqs();
|
||||||
dac
|
dac
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn regs(&self) -> &DacRegisterBlock {
|
|
||||||
unsafe { &*self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_irqs(&mut self) {
|
pub fn clear_irqs(&self) {
|
||||||
self.regs().irq_clr().write(|w| {
|
self.dac.irq_clr().write(|w| {
|
||||||
w.fifo_oflow().set_bit();
|
w.fifo_oflow().set_bit();
|
||||||
w.fifo_uflow().set_bit();
|
w.fifo_uflow().set_bit();
|
||||||
w.dac_done().set_bit();
|
w.dac_done().set_bit();
|
||||||
@ -91,30 +87,31 @@ impl Dac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_fifo(&mut self) {
|
pub fn clear_fifo(&self) {
|
||||||
self.regs().fifo_clr().write(|w| unsafe { w.bits(1) });
|
self.dac.fifo_clr().write(|w| unsafe { w.bits(1) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load next value into the FIFO.
|
/// Load next value into the FIFO.
|
||||||
///
|
///
|
||||||
/// Uses the [nb] API to allow blocking and non-blocking usage.
|
/// Uses the [nb] API to allow blocking and non-blocking usage.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn load_value(&mut self, val: u16) -> nb::Result<(), ValueTooLarge> {
|
pub fn load_value(&self, val: u16) -> nb::Result<(), ValueTooLarge> {
|
||||||
if val > 2_u16.pow(12) - 1 {
|
if val > 2_u16.pow(12) - 1 {
|
||||||
return Err(nb::Error::Other(ValueTooLarge));
|
return Err(nb::Error::Other(ValueTooLarge));
|
||||||
}
|
}
|
||||||
let regs = self.regs();
|
if self.dac.status().read().fifo_entry_cnt().bits() >= 32_u8 {
|
||||||
if regs.status().read().fifo_entry_cnt().bits() >= 32_u8 {
|
|
||||||
return Err(nb::Error::WouldBlock);
|
return Err(nb::Error::WouldBlock);
|
||||||
}
|
}
|
||||||
regs.fifo_data().write(|w| unsafe { w.bits(val.into()) });
|
self.dac
|
||||||
|
.fifo_data()
|
||||||
|
.write(|w| unsafe { w.bits(val.into()) });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This loads and triggers the next value immediately. It also clears the FIFO before
|
/// This loads and triggers the next value immediately. It also clears the FIFO before
|
||||||
/// loading the passed value.
|
/// loading the passed value.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn load_and_trigger_manually(&mut self, val: u16) -> Result<(), ValueTooLarge> {
|
pub fn load_and_trigger_manually(&self, val: u16) -> Result<(), ValueTooLarge> {
|
||||||
if val > 2_u16.pow(12) - 1 {
|
if val > 2_u16.pow(12) - 1 {
|
||||||
return Err(ValueTooLarge);
|
return Err(ValueTooLarge);
|
||||||
}
|
}
|
||||||
@ -130,30 +127,31 @@ impl Dac {
|
|||||||
/// to be processed by the DAC.
|
/// to be processed by the DAC.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn trigger_manually(&self) {
|
pub fn trigger_manually(&self) {
|
||||||
self.regs().ctrl0().write(|w| w.man_trig_en().set_bit());
|
self.dac.ctrl0().write(|w| w.man_trig_en().set_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn enable_external_trigger(&self) {
|
pub fn enable_external_trigger(&self) {
|
||||||
self.regs().ctrl0().write(|w| w.ext_trig_en().set_bit());
|
self.dac.ctrl0().write(|w| w.ext_trig_en().set_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_settled(&self) -> nb::Result<(), ()> {
|
pub fn is_settled(&self) -> nb::Result<(), ()> {
|
||||||
if self.regs().status().read().dac_busy().bit_is_set() {
|
if self.dac.status().read().dac_busy().bit_is_set() {
|
||||||
return Err(nb::Error::WouldBlock);
|
return Err(nb::Error::WouldBlock);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self, syscfg: &mut pac::Sysconfig) {
|
||||||
enable_peripheral_clock(PeripheralSelect::Dac);
|
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
|
||||||
reset_peripheral_for_cycles(PeripheralSelect::Dac, 2);
|
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dac);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the DAC, which disables its peripheral clock.
|
/// Relases the DAC, which also disables its peripheral clock.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn stop(self) {
|
pub fn release(self, syscfg: &mut pac::Sysconfig) -> DacInstance {
|
||||||
disable_peripheral_clock(PeripheralSelect::Dac);
|
syscfg.disable_peripheral_clock(PeripheralSelect::Dac);
|
||||||
|
self.dac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
|
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
|
||||||
use arbitrary_int::{u10, u3};
|
use embedded_dma::WriteBuffer;
|
||||||
use vorago_shared_periphs::{
|
|
||||||
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{enable_nvic_interrupt, pac};
|
use crate::{
|
||||||
|
clock::{PeripheralClock, PeripheralSelect},
|
||||||
|
enable_interrupt, pac,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
|
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
|
||||||
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
|
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
|
||||||
@ -16,10 +17,9 @@ const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
|
|||||||
/// DMA cycle control values.
|
/// DMA cycle control values.
|
||||||
///
|
///
|
||||||
/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details.
|
/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details.
|
||||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum CycleControl {
|
pub enum CycleControl {
|
||||||
/// Indicates that the data structure is invalid.
|
/// Indicates that the data structure is invalid.
|
||||||
Stop = 0b000,
|
Stop = 0b000,
|
||||||
@ -44,8 +44,7 @@ pub enum CycleControl {
|
|||||||
PeriphScatterGatherAlternate = 0b111,
|
PeriphScatterGatherAlternate = 0b111,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum AddrIncrement {
|
pub enum AddrIncrement {
|
||||||
Byte = 0b00,
|
Byte = 0b00,
|
||||||
@ -54,8 +53,7 @@ pub enum AddrIncrement {
|
|||||||
None = 0b11,
|
None = 0b11,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bitbybit::bitenum(u2, exhaustive = false)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum DataSize {
|
pub enum DataSize {
|
||||||
Byte = 0b00,
|
Byte = 0b00,
|
||||||
@ -64,8 +62,7 @@ pub enum DataSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
|
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
|
||||||
#[bitbybit::bitenum(u4, exhaustive = true)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum RPower {
|
pub enum RPower {
|
||||||
EachTransfer = 0b0000,
|
EachTransfer = 0b0000,
|
||||||
@ -78,24 +75,16 @@ pub enum RPower {
|
|||||||
Every128 = 0b0111,
|
Every128 = 0b0111,
|
||||||
Every256 = 0b1000,
|
Every256 = 0b1000,
|
||||||
Every512 = 0b1001,
|
Every512 = 0b1001,
|
||||||
Every1024 = 0b1010,
|
Every1024Min = 0b1010,
|
||||||
Every1024Alt0 = 0b1011,
|
Every1024 = 0b1111,
|
||||||
Every1024Alt1 = 0b1100,
|
|
||||||
Every1024Alt2 = 0b1101,
|
|
||||||
Every1024Alt3 = 0b1110,
|
|
||||||
Every1024Alt4 = 0b1111,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
#[error("Invalid DMA control block address")]
|
pub struct InvalidCtrlBlockAddr;
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct InvalidCtrlBlockAddrError;
|
|
||||||
|
|
||||||
/*
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct ChannelConfig(u32);
|
pub struct ChannelConfig(u32);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u32;
|
u32;
|
||||||
@ -121,37 +110,9 @@ bitfield::bitfield! {
|
|||||||
u8;
|
u8;
|
||||||
pub cycle_ctrl, set_cycle_ctr: 2, 0;
|
pub cycle_ctrl, set_cycle_ctr: 2, 0;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct ChannelConfig {
|
|
||||||
#[bits(30..=31, rw)]
|
|
||||||
dst_inc: AddrIncrement,
|
|
||||||
#[bits(28..=29, rw)]
|
|
||||||
dst_size: Option<DataSize>,
|
|
||||||
#[bits(26..=27, rw)]
|
|
||||||
src_inc: AddrIncrement,
|
|
||||||
#[bits(24..=25, rw)]
|
|
||||||
src_size: Option<DataSize>,
|
|
||||||
#[bits(21..=23, rw)]
|
|
||||||
dest_prot_ctrl: u3,
|
|
||||||
#[bits(18..=20, rw)]
|
|
||||||
src_prot_ctrl: u3,
|
|
||||||
#[bits(14..=17, rw)]
|
|
||||||
r_power: RPower,
|
|
||||||
#[bits(4..=13, rw)]
|
|
||||||
n_minus_1: u10,
|
|
||||||
#[bit(3, rw)]
|
|
||||||
next_useburst: bool,
|
|
||||||
#[bits(0..=2, rw)]
|
|
||||||
cycle_ctrl: CycleControl,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct DmaChannelControl {
|
pub struct DmaChannelControl {
|
||||||
pub src_end_ptr: u32,
|
pub src_end_ptr: u32,
|
||||||
pub dest_end_ptr: u32,
|
pub dest_end_ptr: u32,
|
||||||
@ -164,7 +125,7 @@ impl DmaChannelControl {
|
|||||||
Self {
|
Self {
|
||||||
src_end_ptr: 0,
|
src_end_ptr: 0,
|
||||||
dest_end_ptr: 0,
|
dest_end_ptr: 0,
|
||||||
cfg: ChannelConfig::new_with_raw_value(0),
|
cfg: ChannelConfig(0),
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,9 +162,9 @@ impl DmaCtrlBlock {
|
|||||||
/// The passed address must be 128-byte aligned. The user must also take care of specifying
|
/// The passed address must be 128-byte aligned. The user must also take care of specifying
|
||||||
/// a valid memory address for the DMA control block which is accessible by the system as well.
|
/// a valid memory address for the DMA control block which is accessible by the system as well.
|
||||||
/// For example, the control block can be placed in the SRAM1.
|
/// For example, the control block can be placed in the SRAM1.
|
||||||
pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddrError> {
|
pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddr> {
|
||||||
if addr & BASE_PTR_ADDR_MASK > 0 {
|
if addr & BASE_PTR_ADDR_MASK > 0 {
|
||||||
return Err(InvalidCtrlBlockAddrError);
|
return Err(InvalidCtrlBlockAddr);
|
||||||
}
|
}
|
||||||
let ctrl_block_ptr = addr as *mut DmaCtrlBlock;
|
let ctrl_block_ptr = addr as *mut DmaCtrlBlock;
|
||||||
unsafe { core::ptr::write(ctrl_block_ptr, DmaCtrlBlock::default()) }
|
unsafe { core::ptr::write(ctrl_block_ptr, DmaCtrlBlock::default()) }
|
||||||
@ -216,21 +177,19 @@ pub struct Dma {
|
|||||||
ctrl_block: *mut DmaCtrlBlock,
|
ctrl_block: *mut DmaCtrlBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum DmaTransferInitError {
|
pub enum DmaTransferInitError {
|
||||||
#[error("source and destination buffer length mismatch: {src_len} != {dest_len}")]
|
SourceDestLenMissmatch {
|
||||||
SourceDestLenMissmatch { src_len: usize, dest_len: usize },
|
src_len: usize,
|
||||||
|
dest_len: usize,
|
||||||
|
},
|
||||||
/// Overflow when calculating the source or destination end address.
|
/// Overflow when calculating the source or destination end address.
|
||||||
#[error("address overflow")]
|
|
||||||
AddrOverflow,
|
AddrOverflow,
|
||||||
/// Transfer size larger than 1024 units.
|
/// Transfer size larger than 1024 units.
|
||||||
#[error("transfer size too large: {0}, 1024 is the allowed maximum")]
|
|
||||||
TransferSizeTooLarge(usize),
|
TransferSizeTooLarge(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct DmaCfg {
|
pub struct DmaCfg {
|
||||||
pub bufferable: bool,
|
pub bufferable: bool,
|
||||||
pub cacheable: bool,
|
pub cacheable: bool,
|
||||||
@ -246,6 +205,28 @@ pub struct DmaChannel {
|
|||||||
pub ch_ctrl_alt: &'static mut DmaChannelControl,
|
pub ch_ctrl_alt: &'static mut DmaChannelControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This transfer structure takes ownership of the mutable destination slice.
|
||||||
|
///
|
||||||
|
/// This avoids accidental violation of the ownership rules because the DMA now has mutable
|
||||||
|
/// access to that slice as well. The mutable slice can be retrieved after DMA transfer completion
|
||||||
|
/// by using the [Self::release] method.
|
||||||
|
pub struct DmaTransfer<W> {
|
||||||
|
buf: W,
|
||||||
|
//ch: DmaChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: WriteBuffer> DmaTransfer<W> {
|
||||||
|
/// Retrieve the mutable destination slice once the DMA transfer has completed.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// - The user MUST ensure that the DMA transfer has completed, for example by polling a
|
||||||
|
/// completion flag set by the DMA_DONE ISR.
|
||||||
|
pub unsafe fn release(self) -> W {
|
||||||
|
self.buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DmaChannel {
|
impl DmaChannel {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn channel(&self) -> u8 {
|
pub fn channel(&self) -> u8 {
|
||||||
@ -303,7 +284,7 @@ impl DmaChannel {
|
|||||||
///
|
///
|
||||||
/// This function is `unsafe` because it can break mask-based critical sections.
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
pub unsafe fn enable_done_interrupt(&mut self) {
|
pub unsafe fn enable_done_interrupt(&mut self) {
|
||||||
enable_nvic_interrupt(self.done_interrupt);
|
enable_interrupt(self.done_interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables the DMA_ACTIVE interrupt for the DMA channel.
|
/// Enables the DMA_ACTIVE interrupt for the DMA channel.
|
||||||
@ -312,7 +293,7 @@ impl DmaChannel {
|
|||||||
///
|
///
|
||||||
/// This function is `unsafe` because it can break mask-based critical sections.
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
pub unsafe fn enable_active_interrupt(&mut self) {
|
pub unsafe fn enable_active_interrupt(&mut self) {
|
||||||
enable_nvic_interrupt(self.active_interrupt);
|
enable_interrupt(self.active_interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares a 8-bit DMA transfer from memory to memory.
|
/// Prepares a 8-bit DMA transfer from memory to memory.
|
||||||
@ -324,35 +305,25 @@ impl DmaChannel {
|
|||||||
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
/// start the DMA transfer.
|
/// start the DMA transfer.
|
||||||
///
|
pub fn prepare_mem_to_mem_transfer_8_bit<W: WriteBuffer<Word = u8>>(
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
|
||||||
/// is safe for DMA reads. The specific requirements can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
pub unsafe fn prepare_mem_to_mem_transfer_8_bit(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &[u8],
|
source: &[u8],
|
||||||
dest: &mut [u8],
|
mut dest: W,
|
||||||
) -> Result<(), DmaTransferInitError> {
|
) -> Result<DmaTransfer<W>, DmaTransferInitError> {
|
||||||
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
let (write_ptr, len) = unsafe { dest.write_buffer() };
|
||||||
|
let len = Self::common_mem_transfer_checks(source.len(), len)?;
|
||||||
self.generic_mem_to_mem_transfer_init(
|
self.generic_mem_to_mem_transfer_init(
|
||||||
len,
|
len,
|
||||||
(source.as_ptr() as u32)
|
(source.as_ptr() as u32)
|
||||||
.checked_add(len as u32)
|
.checked_add(len as u32)
|
||||||
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
(dest.as_ptr() as u32)
|
(write_ptr as u32)
|
||||||
.checked_add(len as u32)
|
.checked_add(len as u32)
|
||||||
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
DataSize::Byte,
|
DataSize::Byte,
|
||||||
AddrIncrement::Byte,
|
AddrIncrement::Byte,
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(DmaTransfer { buf: dest })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares a 16-bit DMA transfer from memory to memory.
|
/// Prepares a 16-bit DMA transfer from memory to memory.
|
||||||
@ -364,21 +335,10 @@ impl DmaChannel {
|
|||||||
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
/// start the DMA transfer.
|
/// start the DMA transfer.
|
||||||
///
|
pub fn prepare_mem_to_mem_transfer_16_bit<'dest>(
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
|
||||||
/// is safe for DMA reads. The specific requirements can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
pub unsafe fn prepare_mem_to_mem_transfer_16_bit(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &[u16],
|
source: &[u16],
|
||||||
dest: &mut [u16],
|
dest: &'dest mut [u16],
|
||||||
) -> Result<(), DmaTransferInitError> {
|
) -> Result<(), DmaTransferInitError> {
|
||||||
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
self.generic_mem_to_mem_transfer_init(
|
self.generic_mem_to_mem_transfer_init(
|
||||||
@ -404,21 +364,10 @@ impl DmaChannel {
|
|||||||
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
/// start the DMA transfer.
|
/// start the DMA transfer.
|
||||||
///
|
pub fn prepare_mem_to_mem_transfer_32_bit<'dest>(
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
|
||||||
/// is safe for DMA reads. The specific requirements can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
pub unsafe fn prepare_mem_to_mem_transfer_32_bit(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &[u32],
|
source: &[u32],
|
||||||
dest: &mut [u32],
|
dest: &'dest mut [u32],
|
||||||
) -> Result<(), DmaTransferInitError> {
|
) -> Result<(), DmaTransferInitError> {
|
||||||
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
self.generic_mem_to_mem_transfer_init(
|
self.generic_mem_to_mem_transfer_init(
|
||||||
@ -435,52 +384,6 @@ impl DmaChannel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares a 8-bit DMA transfer from memory to a peripheral.
|
|
||||||
///
|
|
||||||
/// It is assumed that a peripheral with a 16-byte FIFO is used here and that the
|
|
||||||
/// transfer is activated by an IRQ trigger when the half-full interrupt of the peripheral
|
|
||||||
/// is fired. Therefore, this function configured the DMA in [CycleControl::Basic] mode with
|
|
||||||
/// rearbitration happening every 8 DMA cycles. It also configures the primary channel control
|
|
||||||
/// structure to perform the transfer.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure that the source buffer is safe for DMA reads. The specific requirements
|
|
||||||
/// can be read here:
|
|
||||||
///
|
|
||||||
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
|
||||||
///
|
|
||||||
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
|
||||||
/// active or until the DMA is stopped.
|
|
||||||
///
|
|
||||||
/// The destination address must be the pointer address of a peripheral FIFO register address.
|
|
||||||
/// You must also ensure that the regular synchronous transfer API of the peripheral is NOT
|
|
||||||
/// used to perform transfers.
|
|
||||||
pub unsafe fn prepare_mem_to_periph_transfer_8_bit(
|
|
||||||
&mut self,
|
|
||||||
source: &[u8],
|
|
||||||
dest: *mut u32,
|
|
||||||
) -> Result<(), DmaTransferInitError> {
|
|
||||||
if source.len() > MAX_DMA_TRANSFERS_PER_CYCLE {
|
|
||||||
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
|
|
||||||
}
|
|
||||||
let len = source.len() - 1;
|
|
||||||
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
|
|
||||||
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
|
|
||||||
.checked_add(len as u32)
|
|
||||||
.ok_or(DmaTransferInitError::AddrOverflow)?;
|
|
||||||
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
|
|
||||||
self.ch_ctrl_pri.cfg.set_cycle_ctrl(CycleControl::Basic);
|
|
||||||
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte);
|
|
||||||
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte);
|
|
||||||
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte);
|
|
||||||
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None);
|
|
||||||
self.ch_ctrl_pri.cfg.set_n_minus_1(u10::new(len as u16));
|
|
||||||
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8);
|
|
||||||
self.select_primary_structure();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function performs common checks and returns the source length minus one which is
|
// This function performs common checks and returns the source length minus one which is
|
||||||
// relevant for further configuration of the DMA. This is because the DMA API expects N minus
|
// relevant for further configuration of the DMA. This is because the DMA API expects N minus
|
||||||
// 1 and the source and end pointer need to point to the last transfer address.
|
// 1 and the source and end pointer need to point to the last transfer address.
|
||||||
@ -505,18 +408,16 @@ impl DmaChannel {
|
|||||||
data_size: DataSize,
|
data_size: DataSize,
|
||||||
addr_incr: AddrIncrement,
|
addr_incr: AddrIncrement,
|
||||||
) {
|
) {
|
||||||
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
|
self.ch_ctrl_pri.cfg.set_raw(0);
|
||||||
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
|
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
|
||||||
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
|
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
|
||||||
self.ch_ctrl_pri.cfg.set_cycle_ctrl(CycleControl::Auto);
|
self.ch_ctrl_pri.cfg.set_cycle_ctr(CycleControl::Auto as u8);
|
||||||
self.ch_ctrl_pri.cfg.set_src_size(data_size);
|
self.ch_ctrl_pri.cfg.set_src_size(data_size as u8);
|
||||||
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr);
|
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8);
|
||||||
self.ch_ctrl_pri.cfg.set_dst_size(data_size);
|
self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8);
|
||||||
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr);
|
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8);
|
||||||
self.ch_ctrl_pri
|
self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16);
|
||||||
.cfg
|
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8);
|
||||||
.set_n_minus_1(u10::new(n_minus_one as u16));
|
|
||||||
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4);
|
|
||||||
self.select_primary_structure();
|
self.select_primary_structure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,17 +433,18 @@ impl Dma {
|
|||||||
/// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
|
/// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
|
||||||
/// control block at a specific address.
|
/// control block at a specific address.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
syscfg: &mut pac::Sysconfig,
|
||||||
dma: pac::Dma,
|
dma: pac::Dma,
|
||||||
cfg: DmaCfg,
|
cfg: DmaCfg,
|
||||||
ctrl_block: *mut DmaCtrlBlock,
|
ctrl_block: *mut DmaCtrlBlock,
|
||||||
) -> Result<Self, InvalidCtrlBlockAddrError> {
|
) -> Result<Self, InvalidCtrlBlockAddr> {
|
||||||
// The conversion to u32 is safe here because we are on a 32-bit system.
|
// The conversion to u32 is safe here because we are on a 32-bit system.
|
||||||
let raw_addr = ctrl_block as u32;
|
let raw_addr = ctrl_block as u32;
|
||||||
if raw_addr & BASE_PTR_ADDR_MASK > 0 {
|
if raw_addr & BASE_PTR_ADDR_MASK > 0 {
|
||||||
return Err(InvalidCtrlBlockAddrError);
|
return Err(InvalidCtrlBlockAddr);
|
||||||
}
|
}
|
||||||
enable_peripheral_clock(PeripheralSelect::Dma);
|
syscfg.enable_peripheral_clock(PeripheralClock::Dma);
|
||||||
reset_peripheral_for_cycles(PeripheralSelect::Dma, 2);
|
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
|
||||||
let dma = Dma { dma, ctrl_block };
|
let dma = Dma { dma, ctrl_block };
|
||||||
dma.dma
|
dma.dma
|
||||||
.ctrl_base_ptr()
|
.ctrl_base_ptr()
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
use crate::{enable_nvic_interrupt, pac};
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_rom_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
|
|
||||||
syscfg
|
|
||||||
.rom_scrub()
|
|
||||||
.write(|w| unsafe { w.bits(counter_reset as u32) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_ram0_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
|
|
||||||
syscfg
|
|
||||||
.ram0_scrub()
|
|
||||||
.write(|w| unsafe { w.bits(counter_reset as u32) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_ram1_scrub(syscfg: &mut pac::Sysconfig, counter_reset: u16) {
|
|
||||||
syscfg
|
|
||||||
.ram1_scrub()
|
|
||||||
.write(|w| unsafe { w.bits(counter_reset as u32) });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function enables the SBE related interrupts. The user should also provide a
|
|
||||||
/// `EDAC_SBE` ISR and use [clear_sbe_irq] inside that ISR at the very least.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_sbe_irq() {
|
|
||||||
unsafe {
|
|
||||||
enable_nvic_interrupt(pac::Interrupt::EDAC_SBE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function enables the SBE related interrupts. The user should also provide a
|
|
||||||
/// `EDAC_MBE` ISR and use [clear_mbe_irq] inside that ISR at the very least.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn enable_mbe_irq() {
|
|
||||||
unsafe {
|
|
||||||
enable_nvic_interrupt(pac::Interrupt::EDAC_MBE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function should be called in the user provided `EDAC_SBE` interrupt-service routine
|
|
||||||
/// to clear the SBE related interrupts.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn clear_sbe_irq() {
|
|
||||||
// Safety: This function only clears SBE related IRQs
|
|
||||||
let syscfg = unsafe { pac::Sysconfig::steal() };
|
|
||||||
syscfg.irq_clr().write(|w| {
|
|
||||||
w.romsbe().set_bit();
|
|
||||||
w.ram0sbe().set_bit();
|
|
||||||
w.ram1sbe().set_bit()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function should be called in the user provided `EDAC_MBE` interrupt-service routine
|
|
||||||
/// to clear the MBE related interrupts.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn clear_mbe_irq() {
|
|
||||||
// Safety: This function only clears SBE related IRQs
|
|
||||||
let syscfg = unsafe { pac::Sysconfig::steal() };
|
|
||||||
syscfg.irq_clr().write(|w| {
|
|
||||||
w.rommbe().set_bit();
|
|
||||||
w.ram0mbe().set_bit();
|
|
||||||
w.ram1mbe().set_bit()
|
|
||||||
});
|
|
||||||
}
|
|
453
va416xx-hal/src/gpio/dynpin.rs
Normal file
453
va416xx-hal/src/gpio/dynpin.rs
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
use embedded_hal::digital::{ErrorKind, ErrorType, InputPin, OutputPin, StatefulOutputPin};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
reg::RegisterInterface, FilterClkSel, FilterType, InterruptEdge, InterruptLevel, Pin, PinId,
|
||||||
|
PinMode, PinState,
|
||||||
|
};
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// DynPinMode configurations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Value-level `enum` for disabled configurations
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum DynDisabled {
|
||||||
|
Floating,
|
||||||
|
PullDown,
|
||||||
|
PullUp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value-level `enum` for input configurations
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum DynInput {
|
||||||
|
Floating,
|
||||||
|
PullDown,
|
||||||
|
PullUp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value-level `enum` for output configurations
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum DynOutput {
|
||||||
|
PushPull,
|
||||||
|
OpenDrain,
|
||||||
|
ReadablePushPull,
|
||||||
|
ReadableOpenDrain,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DynAlternate = crate::FunSel;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// DynPinMode
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Value-level `enum` representing pin modes
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum DynPinMode {
|
||||||
|
Input(DynInput),
|
||||||
|
Output(DynOutput),
|
||||||
|
Alternate(DynAlternate),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value-level variant of [`DynPinMode`] for floating input mode
|
||||||
|
pub const DYN_FLOATING_INPUT: DynPinMode = DynPinMode::Input(DynInput::Floating);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for pull-down input mode
|
||||||
|
pub const DYN_PULL_DOWN_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullDown);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for pull-up input mode
|
||||||
|
pub const DYN_PULL_UP_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullUp);
|
||||||
|
|
||||||
|
/// Value-level variant of [`DynPinMode`] for push-pull output mode
|
||||||
|
pub const DYN_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::PushPull);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for open-drain output mode
|
||||||
|
pub const DYN_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::OpenDrain);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for readable push-pull output mode
|
||||||
|
pub const DYN_RD_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadablePushPull);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for readable opendrain output mode
|
||||||
|
pub const DYN_RD_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadableOpenDrain);
|
||||||
|
|
||||||
|
/// Value-level variant of [`DynPinMode`] for function select 1
|
||||||
|
pub const DYN_ALT_FUNC_1: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel1);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for function select 2
|
||||||
|
pub const DYN_ALT_FUNC_2: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel2);
|
||||||
|
/// Value-level variant of [`DynPinMode`] for function select 3
|
||||||
|
pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel3);
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// DynGroup & DynPinId
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Value-level `enum` for pin groups
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum DynGroup {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
E,
|
||||||
|
F,
|
||||||
|
G,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value-level `struct` representing pin IDs
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub struct DynPinId {
|
||||||
|
pub group: DynGroup,
|
||||||
|
pub num: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// DynRegisters
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/// Provide a safe register interface for [`DynPin`]s
|
||||||
|
///
|
||||||
|
/// This `struct` takes ownership of a [`DynPinId`] and provides an API to
|
||||||
|
/// access the corresponding regsiters.
|
||||||
|
struct DynRegisters {
|
||||||
|
id: DynPinId,
|
||||||
|
}
|
||||||
|
|
||||||
|
// [`DynRegisters`] takes ownership of the [`DynPinId`], and [`DynPin`]
|
||||||
|
// guarantees that each pin is a singleton, so this implementation is safe.
|
||||||
|
unsafe impl RegisterInterface for DynRegisters {
|
||||||
|
#[inline]
|
||||||
|
fn id(&self) -> DynPinId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynRegisters {
|
||||||
|
/// Create a new instance of [`DynRegisters`]
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Users must never create two simultaneous instances of this `struct` with
|
||||||
|
/// the same [`DynPinId`]
|
||||||
|
#[inline]
|
||||||
|
unsafe fn new(id: DynPinId) -> Self {
|
||||||
|
DynRegisters { id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Error
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/// GPIO error type
|
||||||
|
///
|
||||||
|
/// [`DynPin`]s are not tracked and verified at compile-time, so run-time
|
||||||
|
/// operations are fallible. This `enum` represents the corresponding errors.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct InvalidPinTypeError(pub(crate) ());
|
||||||
|
|
||||||
|
impl embedded_hal::digital::Error for InvalidPinTypeError {
|
||||||
|
fn kind(&self) -> embedded_hal::digital::ErrorKind {
|
||||||
|
ErrorKind::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// DynPin
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// A value-level pin, parameterized by [`DynPinId`] and [`DynPinMode`]
|
||||||
|
///
|
||||||
|
/// This type acts as a type-erased version of [`Pin`]. Every pin is represented
|
||||||
|
/// by the same type, and pins are tracked and distinguished at run-time.
|
||||||
|
pub struct DynPin {
|
||||||
|
regs: DynRegisters,
|
||||||
|
mode: DynPinMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynPin {
|
||||||
|
/// Create a new [`DynPin`]
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Each [`DynPin`] must be a singleton. For a given [`DynPinId`], there
|
||||||
|
/// must be at most one corresponding [`DynPin`] in existence at any given
|
||||||
|
/// time. Violating this requirement is `unsafe`.
|
||||||
|
#[inline]
|
||||||
|
unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self {
|
||||||
|
DynPin {
|
||||||
|
regs: DynRegisters::new(id),
|
||||||
|
mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a copy of the pin ID
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> DynPinId {
|
||||||
|
self.regs.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a copy of the pin mode
|
||||||
|
#[inline]
|
||||||
|
pub fn mode(&self) -> DynPinMode {
|
||||||
|
self.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the pin to the requested [`DynPinMode`]
|
||||||
|
#[inline]
|
||||||
|
pub fn into_mode(&mut self, mode: DynPinMode) {
|
||||||
|
// Only modify registers if we are actually changing pin mode
|
||||||
|
if mode != self.mode {
|
||||||
|
self.regs.change_mode(mode);
|
||||||
|
self.mode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_funsel_1(&mut self) {
|
||||||
|
self.into_mode(DYN_ALT_FUNC_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_funsel_2(&mut self) {
|
||||||
|
self.into_mode(DYN_ALT_FUNC_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_funsel_3(&mut self) {
|
||||||
|
self.into_mode(DYN_ALT_FUNC_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a floating input
|
||||||
|
#[inline]
|
||||||
|
pub fn into_floating_input(&mut self) {
|
||||||
|
self.into_mode(DYN_FLOATING_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a pulled down input
|
||||||
|
#[inline]
|
||||||
|
pub fn into_pull_down_input(&mut self) {
|
||||||
|
self.into_mode(DYN_PULL_DOWN_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a pulled up input
|
||||||
|
#[inline]
|
||||||
|
pub fn into_pull_up_input(&mut self) {
|
||||||
|
self.into_mode(DYN_PULL_UP_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a push-pull output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_push_pull_output(&mut self) {
|
||||||
|
self.into_mode(DYN_PUSH_PULL_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a push-pull output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_open_drain_output(&mut self) {
|
||||||
|
self.into_mode(DYN_OPEN_DRAIN_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a push-pull output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_readable_push_pull_output(&mut self) {
|
||||||
|
self.into_mode(DYN_RD_PUSH_PULL_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a push-pull output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_readable_open_drain_output(&mut self) {
|
||||||
|
self.into_mode(DYN_RD_OPEN_DRAIN_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
common_reg_if_functions!();
|
||||||
|
|
||||||
|
/// See p.53 of the programmers guide for more information.
|
||||||
|
/// Possible delays in clock cycles:
|
||||||
|
/// - Delay 1: 1
|
||||||
|
/// - Delay 2: 2
|
||||||
|
/// - Delay 1 + Delay 2: 3
|
||||||
|
#[inline]
|
||||||
|
pub fn delay(self, delay_1: bool, delay_2: bool) -> Result<Self, InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Output(_) => {
|
||||||
|
self.regs.delay(delay_1, delay_2);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See p.52 of the programmers guide for more information.
|
||||||
|
/// When configured for pulse mode, a given pin will set the non-default state for exactly
|
||||||
|
/// one clock cycle before returning to the configured default state
|
||||||
|
pub fn pulse_mode(
|
||||||
|
self,
|
||||||
|
enable: bool,
|
||||||
|
default_state: PinState,
|
||||||
|
) -> Result<Self, InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Output(_) => {
|
||||||
|
self.regs.pulse_mode(enable, default_state);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See p.37 and p.38 of the programmers guide for more information.
|
||||||
|
#[inline]
|
||||||
|
pub fn filter_type(
|
||||||
|
self,
|
||||||
|
filter: FilterType,
|
||||||
|
clksel: FilterClkSel,
|
||||||
|
) -> Result<Self, InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Input(_) => {
|
||||||
|
self.regs.filter_type(filter, clksel);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_edge(mut self, edge_type: InterruptEdge) -> Result<Self, InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Input(_) | DynPinMode::Output(_) => {
|
||||||
|
self.regs.interrupt_edge(edge_type);
|
||||||
|
self.irq_enb();
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_level(
|
||||||
|
mut self,
|
||||||
|
level_type: InterruptLevel,
|
||||||
|
) -> Result<Self, InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Input(_) | DynPinMode::Output(_) => {
|
||||||
|
self.regs.interrupt_level(level_type);
|
||||||
|
self.irq_enb();
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn _read(&self) -> Result<bool, InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => {
|
||||||
|
Ok(self.regs.read_pin())
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn _write(&mut self, bit: bool) -> Result<(), InvalidPinTypeError> {
|
||||||
|
match self.mode {
|
||||||
|
DynPinMode::Output(_) => {
|
||||||
|
self.regs.write_pin(bit);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(InvalidPinTypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn _is_low(&self) -> Result<bool, InvalidPinTypeError> {
|
||||||
|
self._read().map(|v| !v)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn _is_high(&self) -> Result<bool, InvalidPinTypeError> {
|
||||||
|
self._read()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn _set_low(&mut self) -> Result<(), InvalidPinTypeError> {
|
||||||
|
self._write(false)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn _set_high(&mut self) -> Result<(), InvalidPinTypeError> {
|
||||||
|
self._write(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Convert between Pin and DynPin
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
impl<I, M> From<Pin<I, M>> for DynPin
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
M: PinMode,
|
||||||
|
{
|
||||||
|
/// Erase the type-level information in a [`Pin`] and return a value-level
|
||||||
|
/// [`DynPin`]
|
||||||
|
#[inline]
|
||||||
|
fn from(_pin: Pin<I, M>) -> Self {
|
||||||
|
// The `Pin` is consumed, so it is safe to replace it with the
|
||||||
|
// corresponding `DynPin`
|
||||||
|
unsafe { DynPin::new(I::DYN, M::DYN) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, M> TryFrom<DynPin> for Pin<I, M>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
M: PinMode,
|
||||||
|
{
|
||||||
|
type Error = InvalidPinTypeError;
|
||||||
|
|
||||||
|
/// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
|
||||||
|
///
|
||||||
|
/// There is no way for the compiler to know if the conversion will be
|
||||||
|
/// successful at compile-time. We must verify the conversion at run-time
|
||||||
|
/// or refuse to perform it.
|
||||||
|
#[inline]
|
||||||
|
fn try_from(pin: DynPin) -> Result<Self, Self::Error> {
|
||||||
|
if pin.regs.id == I::DYN && pin.mode == M::DYN {
|
||||||
|
// The `DynPin` is consumed, so it is safe to replace it with the
|
||||||
|
// corresponding `Pin`
|
||||||
|
Ok(unsafe { Self::new() })
|
||||||
|
} else {
|
||||||
|
Err(InvalidPinTypeError(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Embedded HAL v1 traits
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
impl ErrorType for DynPin {
|
||||||
|
type Error = InvalidPinTypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputPin for DynPin {
|
||||||
|
#[inline]
|
||||||
|
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self._set_high()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self._set_low()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputPin for DynPin {
|
||||||
|
#[inline]
|
||||||
|
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
self._is_high()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
self._is_low()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatefulOutputPin for DynPin {
|
||||||
|
#[inline]
|
||||||
|
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
self._is_high()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
self._is_low()
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,82 @@
|
|||||||
//! # GPIO support module.
|
//! # API for the GPIO peripheral
|
||||||
//!
|
//!
|
||||||
//! Contains abstractions to use the pins provided by the [crate::pins] module as GPIO or
|
//! The implementation of this GPIO module is heavily based on the
|
||||||
//! IO peripheral pins.
|
//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/index.html).
|
||||||
//!
|
//!
|
||||||
//! The core data structures provided for this are the
|
//! This API provides two different submodules, [pin] and [dynpin],
|
||||||
|
//! representing two different ways to handle GPIO pins. The default, [pin],
|
||||||
|
//! is a type-level API that tracks the state of each pin at compile-time. The
|
||||||
|
//! alternative, [dynpin] is a type-erased, value-level API that tracks the
|
||||||
|
//! state of each pin at run-time.
|
||||||
//!
|
//!
|
||||||
//! - [Output] for push-pull output pins.
|
//! The type-level API is strongly preferred. By representing the state of each
|
||||||
//! - [Input] for input pins.
|
//! pin within the type system, the compiler can detect logic errors at
|
||||||
//! - [Flex] for pins with flexible configuration requirements.
|
//! compile-time. Furthermore, the type-level API has absolutely zero run-time
|
||||||
//! - [IoPeriphPin] for IO peripheral pins.
|
//! cost.
|
||||||
//!
|
//!
|
||||||
//! The [crate::pins] module exposes singletons to access the [Pin]s required by this module
|
//! If needed, [dynpin] can be used to erase the type-level differences
|
||||||
//! in a type-safe way.
|
//! between pins. However, by doing so, pins must now be tracked at run-time,
|
||||||
|
//! and each pin has a non-zero memory footprint.
|
||||||
//!
|
//!
|
||||||
//! # Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
|
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
|
||||||
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
|
|
||||||
pub use vorago_shared_periphs::gpio::*;
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct IsMaskedError;
|
||||||
|
|
||||||
|
macro_rules! common_reg_if_functions {
|
||||||
|
() => {
|
||||||
|
paste::paste!(
|
||||||
|
#[inline]
|
||||||
|
pub fn datamask(&self) -> bool {
|
||||||
|
self.regs.datamask()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_datamask(self) -> Self {
|
||||||
|
self.regs.clear_datamask();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_datamask(self) -> Self {
|
||||||
|
self.regs.set_datamask();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
|
||||||
|
self.regs.read_pin_masked()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
|
||||||
|
self.regs.read_pin_masked().map(|v| !v)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
|
||||||
|
self.regs.write_pin_masked(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
|
||||||
|
self.regs.write_pin_masked(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn irq_enb(&mut self) {
|
||||||
|
self.regs.enable_irq();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod pin;
|
||||||
|
pub use pin::*;
|
||||||
|
|
||||||
|
pub mod dynpin;
|
||||||
|
pub use dynpin::*;
|
||||||
|
|
||||||
|
mod reg;
|
||||||
|
911
va416xx-hal/src/gpio/pin.rs
Normal file
911
va416xx-hal/src/gpio/pin.rs
Normal file
@ -0,0 +1,911 @@
|
|||||||
|
//! # Type-level module for GPIO pins
|
||||||
|
//!
|
||||||
|
//! This documentation is strongly based on the
|
||||||
|
//! [atsamd documentation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/pin/index.html).
|
||||||
|
//!
|
||||||
|
//! This module provides a type-level API for GPIO pins. It uses the type system
|
||||||
|
//! to track the state of pins at compile-time. Representing GPIO pins in this
|
||||||
|
//! manner incurs no run-time overhead. Each [`Pin`] struct is zero-sized, so
|
||||||
|
//! there is no data to copy around. Instead, real code is generated as a side
|
||||||
|
//! effect of type transformations, and the resulting assembly is nearly
|
||||||
|
//! identical to the equivalent, hand-written C.
|
||||||
|
//!
|
||||||
|
//! To track the state of pins at compile-time, this module uses traits to
|
||||||
|
//! represent [type classes] and types as instances of those type classes. For
|
||||||
|
//! example, the trait [`InputConfig`] acts as a [type-level enum] of the
|
||||||
|
//! available input configurations, and the types [`Floating`], [`PullDown`] and
|
||||||
|
//! [`PullUp`] are its type-level variants.
|
||||||
|
//!
|
||||||
|
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
|
||||||
|
//! [`PinMode`].
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! pub struct Pin<I, M>
|
||||||
|
//! where
|
||||||
|
//! I: PinId,
|
||||||
|
//! M: PinMode,
|
||||||
|
//! {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! A `PinId` identifies a pin by it's group (A to G) and pin number. Each
|
||||||
|
//! `PinId` instance is named according to its datasheet identifier, e.g.
|
||||||
|
//! [PA2].
|
||||||
|
//!
|
||||||
|
//! A `PinMode` represents the various pin modes. The available `PinMode`
|
||||||
|
//! variants are [`Input`], [`Output`] and [`Alternate`], each with its own
|
||||||
|
//! corresponding configurations.
|
||||||
|
//!
|
||||||
|
//! It is not possible for users to create new instances of a [`Pin`]. Singleton
|
||||||
|
//! instances of each pin are made available to users through the PinsX
|
||||||
|
//! struct.
|
||||||
|
//!
|
||||||
|
//! Example for the pins of PORT A:
|
||||||
|
//!
|
||||||
|
//! To create the [PinsA] struct, users must supply the PAC
|
||||||
|
//! [Port](crate::pac::Porta) peripheral. The [PinsA] struct takes
|
||||||
|
//! ownership of the [Porta] and provides the corresponding pins. Each [`Pin`]
|
||||||
|
//! within the [PinsA] struct can be moved out and used individually.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! let mut peripherals = Peripherals::take().unwrap();
|
||||||
|
//! let pinsa = PinsA::new(peripherals.porta);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Pins can be converted between modes using several different methods.
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! // Use one of the literal function names
|
||||||
|
//! let pa0 = pinsa.pa0.into_floating_input();
|
||||||
|
//! // Use a generic method and one of the `PinMode` variant types
|
||||||
|
//! let pa0 = pinsa.pa0.into_mode::<FloatingInput>();
|
||||||
|
//! // Specify the target type and use `From`/`Into`
|
||||||
|
//! let pa0: Pin<PA0, FloatingInput> = pinsa.pa27.into();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Embedded HAL traits
|
||||||
|
//!
|
||||||
|
//! This module implements all of the embedded HAL GPIO traits for each [`Pin`]
|
||||||
|
//! in the corresponding [`PinMode`]s, namely: [`InputPin`], [`OutputPin`],
|
||||||
|
//! and [`StatefulOutputPin`].
|
||||||
|
use core::{convert::Infallible, marker::PhantomData, mem::transmute};
|
||||||
|
|
||||||
|
pub use crate::clock::FilterClkSel;
|
||||||
|
use crate::typelevel::Sealed;
|
||||||
|
use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin};
|
||||||
|
use va416xx::{Porta, Portb, Portc, Portd, Porte, Portf, Portg};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
reg::RegisterInterface, DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Errors and Definitions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum InterruptEdge {
|
||||||
|
HighToLow,
|
||||||
|
LowToHigh,
|
||||||
|
BothEdges,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum InterruptLevel {
|
||||||
|
Low = 0,
|
||||||
|
High = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum PinState {
|
||||||
|
Low = 0,
|
||||||
|
High = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Input configuration
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Type-level enum for input configurations
|
||||||
|
///
|
||||||
|
/// The valid options are [Floating], [PullDown] and [PullUp].
|
||||||
|
pub trait InputConfig: Sealed {
|
||||||
|
/// Corresponding [DynInput]
|
||||||
|
const DYN: DynInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Floating {}
|
||||||
|
pub enum PullDown {}
|
||||||
|
pub enum PullUp {}
|
||||||
|
|
||||||
|
impl InputConfig for Floating {
|
||||||
|
const DYN: DynInput = DynInput::Floating;
|
||||||
|
}
|
||||||
|
impl InputConfig for PullDown {
|
||||||
|
const DYN: DynInput = DynInput::PullDown;
|
||||||
|
}
|
||||||
|
impl InputConfig for PullUp {
|
||||||
|
const DYN: DynInput = DynInput::PullUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for Floating {}
|
||||||
|
impl Sealed for PullDown {}
|
||||||
|
impl Sealed for PullUp {}
|
||||||
|
|
||||||
|
/// Type-level variant of [PinMode] for floating input mode
|
||||||
|
pub type InputFloating = Input<Floating>;
|
||||||
|
/// Type-level variant of [PinMode] for pull-down input mode
|
||||||
|
pub type InputPullDown = Input<PullDown>;
|
||||||
|
/// Type-level variant of [PinMode] for pull-up input mode
|
||||||
|
pub type InputPullUp = Input<PullUp>;
|
||||||
|
|
||||||
|
/// Type-level variant of [PinMode] for input modes
|
||||||
|
///
|
||||||
|
/// Type `C` is one of three input configurations: [Floating], [PullDown] or
|
||||||
|
/// [PullUp]
|
||||||
|
pub struct Input<C: InputConfig> {
|
||||||
|
cfg: PhantomData<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: InputConfig> Sealed for Input<C> {}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum FilterType {
|
||||||
|
SystemClock = 0,
|
||||||
|
DirectInputWithSynchronization = 1,
|
||||||
|
FilterOneClockCycle = 2,
|
||||||
|
FilterTwoClockCycles = 3,
|
||||||
|
FilterThreeClockCycles = 4,
|
||||||
|
FilterFourClockCycles = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Output configuration
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait OutputConfig: Sealed {
|
||||||
|
const DYN: DynOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ReadableOutput: Sealed {}
|
||||||
|
|
||||||
|
/// Type-level variant of [`OutputConfig`] for a push-pull configuration
|
||||||
|
pub enum PushPull {}
|
||||||
|
/// Type-level variant of [`OutputConfig`] for an open drain configuration
|
||||||
|
pub enum OpenDrain {}
|
||||||
|
|
||||||
|
/// Type-level variant of [`OutputConfig`] for a readable push-pull configuration
|
||||||
|
pub enum ReadablePushPull {}
|
||||||
|
/// Type-level variant of [`OutputConfig`] for a readable open-drain configuration
|
||||||
|
pub enum ReadableOpenDrain {}
|
||||||
|
|
||||||
|
impl Sealed for PushPull {}
|
||||||
|
impl Sealed for OpenDrain {}
|
||||||
|
impl Sealed for ReadableOpenDrain {}
|
||||||
|
impl Sealed for ReadablePushPull {}
|
||||||
|
impl ReadableOutput for ReadableOpenDrain {}
|
||||||
|
impl ReadableOutput for ReadablePushPull {}
|
||||||
|
|
||||||
|
impl OutputConfig for PushPull {
|
||||||
|
const DYN: DynOutput = DynOutput::PushPull;
|
||||||
|
}
|
||||||
|
impl OutputConfig for OpenDrain {
|
||||||
|
const DYN: DynOutput = DynOutput::OpenDrain;
|
||||||
|
}
|
||||||
|
impl OutputConfig for ReadablePushPull {
|
||||||
|
const DYN: DynOutput = DynOutput::ReadablePushPull;
|
||||||
|
}
|
||||||
|
impl OutputConfig for ReadableOpenDrain {
|
||||||
|
const DYN: DynOutput = DynOutput::ReadableOpenDrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type-level variant of [`PinMode`] for output modes
|
||||||
|
///
|
||||||
|
/// Type `C` is one of four output configurations: [`PushPull`], [`OpenDrain`] or
|
||||||
|
/// their respective readable versions
|
||||||
|
pub struct Output<C: OutputConfig> {
|
||||||
|
cfg: PhantomData<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: OutputConfig> Sealed for Output<C> {}
|
||||||
|
|
||||||
|
/// Type-level variant of [`PinMode`] for push-pull output mode
|
||||||
|
pub type PushPullOutput = Output<PushPull>;
|
||||||
|
/// Type-level variant of [`PinMode`] for open drain output mode
|
||||||
|
pub type OutputOpenDrain = Output<OpenDrain>;
|
||||||
|
|
||||||
|
pub type OutputReadablePushPull = Output<ReadablePushPull>;
|
||||||
|
pub type OutputReadableOpenDrain = Output<ReadableOpenDrain>;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Alternate configurations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Type-level enum for alternate peripheral function configurations
|
||||||
|
pub trait AlternateConfig: Sealed {
|
||||||
|
const DYN: DynAlternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Funsel1 {}
|
||||||
|
pub enum Funsel2 {}
|
||||||
|
pub enum Funsel3 {}
|
||||||
|
|
||||||
|
impl AlternateConfig for Funsel1 {
|
||||||
|
const DYN: DynAlternate = DynAlternate::Sel1;
|
||||||
|
}
|
||||||
|
impl AlternateConfig for Funsel2 {
|
||||||
|
const DYN: DynAlternate = DynAlternate::Sel2;
|
||||||
|
}
|
||||||
|
impl AlternateConfig for Funsel3 {
|
||||||
|
const DYN: DynAlternate = DynAlternate::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for Funsel1 {}
|
||||||
|
impl Sealed for Funsel2 {}
|
||||||
|
impl Sealed for Funsel3 {}
|
||||||
|
|
||||||
|
/// Type-level variant of [`PinMode`] for alternate peripheral functions
|
||||||
|
///
|
||||||
|
/// Type `C` is an [`AlternateConfig`]
|
||||||
|
pub struct Alternate<C: AlternateConfig> {
|
||||||
|
cfg: PhantomData<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: AlternateConfig> Sealed for Alternate<C> {}
|
||||||
|
|
||||||
|
pub type AltFunc1 = Alternate<Funsel1>;
|
||||||
|
pub type AltFunc2 = Alternate<Funsel2>;
|
||||||
|
pub type AltFunc3 = Alternate<Funsel3>;
|
||||||
|
|
||||||
|
/// Type alias for the [`PinMode`] at reset
|
||||||
|
pub type Reset = InputFloating;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Pin modes
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Type-level enum representing pin modes
|
||||||
|
///
|
||||||
|
/// The valid options are [Input], [Output] and [Alternate].
|
||||||
|
pub trait PinMode: Sealed {
|
||||||
|
/// Corresponding [DynPinMode]
|
||||||
|
const DYN: DynPinMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: InputConfig> PinMode for Input<C> {
|
||||||
|
const DYN: DynPinMode = DynPinMode::Input(C::DYN);
|
||||||
|
}
|
||||||
|
impl<C: OutputConfig> PinMode for Output<C> {
|
||||||
|
const DYN: DynPinMode = DynPinMode::Output(C::DYN);
|
||||||
|
}
|
||||||
|
impl<C: AlternateConfig> PinMode for Alternate<C> {
|
||||||
|
const DYN: DynPinMode = DynPinMode::Alternate(C::DYN);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Pin IDs
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Type-level enum for pin IDs
|
||||||
|
pub trait PinId: Sealed {
|
||||||
|
/// Corresponding [DynPinId]
|
||||||
|
const DYN: DynPinId;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pin_id {
|
||||||
|
($Group:ident, $Id:ident, $NUM:literal) => {
|
||||||
|
// Need paste macro to use ident in doc attribute
|
||||||
|
paste::paste! {
|
||||||
|
#[doc = "Pin ID representing pin " $Id]
|
||||||
|
pub enum $Id {}
|
||||||
|
impl Sealed for $Id {}
|
||||||
|
impl PinId for $Id {
|
||||||
|
const DYN: DynPinId = DynPinId {
|
||||||
|
group: DynGroup::$Group,
|
||||||
|
num: $NUM,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Pin
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// A type-level GPIO pin, parameterized by [`PinId`] and [`PinMode`] types
|
||||||
|
|
||||||
|
pub struct Pin<I: PinId, M: PinMode> {
|
||||||
|
pub(in crate::gpio) regs: Registers<I>,
|
||||||
|
mode: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinId, M: PinMode> Pin<I, M> {
|
||||||
|
/// Create a new [`Pin`]
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Each [`Pin`] must be a singleton. For a given [`PinId`], there must be
|
||||||
|
/// at most one corresponding [`Pin`] in existence at any given time.
|
||||||
|
/// Violating this requirement is `unsafe`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn new() -> Pin<I, M> {
|
||||||
|
Pin {
|
||||||
|
regs: Registers::new(),
|
||||||
|
mode: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the pin to the requested [`PinMode`]
|
||||||
|
#[inline]
|
||||||
|
pub fn into_mode<N: PinMode>(mut self) -> Pin<I, N> {
|
||||||
|
// Only modify registers if we are actually changing pin mode
|
||||||
|
// This check should compile away
|
||||||
|
if N::DYN != M::DYN {
|
||||||
|
self.regs.change_mode::<N>();
|
||||||
|
}
|
||||||
|
// Safe because we drop the existing Pin
|
||||||
|
unsafe { Pin::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin for function select 1. See Programmer Guide p.40 for the function table
|
||||||
|
#[inline]
|
||||||
|
pub fn into_funsel_1(self) -> Pin<I, AltFunc1> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin for function select 2. See Programmer Guide p.40 for the function table
|
||||||
|
#[inline]
|
||||||
|
pub fn into_funsel_2(self) -> Pin<I, AltFunc2> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin for function select 3. See Programmer Guide p.40 for the function table
|
||||||
|
#[inline]
|
||||||
|
pub fn into_funsel_3(self) -> Pin<I, AltFunc3> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a floating input
|
||||||
|
#[inline]
|
||||||
|
pub fn into_floating_input(self) -> Pin<I, InputFloating> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a pulled down input
|
||||||
|
#[inline]
|
||||||
|
pub fn into_pull_down_input(self) -> Pin<I, InputPullDown> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a pulled up input
|
||||||
|
#[inline]
|
||||||
|
pub fn into_pull_up_input(self) -> Pin<I, InputPullUp> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a push-pull output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_push_pull_output(self) -> Pin<I, PushPullOutput> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a readable push-pull output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_readable_push_pull_output(self) -> Pin<I, OutputReadablePushPull> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pin to operate as a readable open-drain output
|
||||||
|
#[inline]
|
||||||
|
pub fn into_readable_open_drain_output(self) -> Pin<I, OutputReadableOpenDrain> {
|
||||||
|
self.into_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
common_reg_if_functions!();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn _set_high(&mut self) {
|
||||||
|
self.regs.write_pin(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn _set_low(&mut self) {
|
||||||
|
self.regs.write_pin(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn _is_low(&self) -> bool {
|
||||||
|
!self.regs.read_pin()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn _is_high(&self) -> bool {
|
||||||
|
self.regs.read_pin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// AnyPin
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/// Type class for [`Pin`] types
|
||||||
|
///
|
||||||
|
/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for
|
||||||
|
/// [`Pin`] types. See the `AnyKind` documentation for more details on the
|
||||||
|
/// pattern.
|
||||||
|
///
|
||||||
|
/// ## `v1` Compatibility
|
||||||
|
///
|
||||||
|
/// Normally, this trait would use `Is<Type = SpecificPin<Self>>` as a super
|
||||||
|
/// trait. But doing so would restrict implementations to only the `v2` `Pin`
|
||||||
|
/// type in this module. To aid in backwards compatibility, we want to implement
|
||||||
|
/// `AnyPin` for the `v1` `Pin` type as well. This is possible for a few
|
||||||
|
/// reasons. First, both structs are zero-sized, so there is no meaningful
|
||||||
|
/// memory layout to begin with. And even if there were, the `v1` `Pin` type is
|
||||||
|
/// a newtype wrapper around a `v2` `Pin`, and single-field structs are
|
||||||
|
/// guaranteed to have the same layout as the field, even for `repr(Rust)`.
|
||||||
|
///
|
||||||
|
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
|
||||||
|
/// [type class]: crate::typelevel#type-classes
|
||||||
|
pub trait AnyPin
|
||||||
|
where
|
||||||
|
Self: Sealed,
|
||||||
|
Self: From<SpecificPin<Self>>,
|
||||||
|
Self: Into<SpecificPin<Self>>,
|
||||||
|
Self: AsRef<SpecificPin<Self>>,
|
||||||
|
Self: AsMut<SpecificPin<Self>>,
|
||||||
|
{
|
||||||
|
/// [`PinId`] of the corresponding [`Pin`]
|
||||||
|
type Id: PinId;
|
||||||
|
/// [`PinMode`] of the corresponding [`Pin`]
|
||||||
|
type Mode: PinMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, M> Sealed for Pin<I, M>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
M: PinMode,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, M> AnyPin for Pin<I, M>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
M: PinMode,
|
||||||
|
{
|
||||||
|
type Id = I;
|
||||||
|
type Mode = M;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type alias to recover the specific [`Pin`] type from an implementation of
|
||||||
|
/// [`AnyPin`]
|
||||||
|
///
|
||||||
|
/// See the [`AnyKind`] documentation for more details on the pattern.
|
||||||
|
///
|
||||||
|
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
|
||||||
|
pub type SpecificPin<P> = Pin<<P as AnyPin>::Id, <P as AnyPin>::Mode>;
|
||||||
|
|
||||||
|
impl<P: AnyPin> AsRef<P> for SpecificPin<P> {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &P {
|
||||||
|
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
|
||||||
|
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
|
||||||
|
// both are zero-sized, and single-field, newtype structs are guaranteed
|
||||||
|
// to have the same layout as the field anyway, even for repr(Rust).
|
||||||
|
unsafe { transmute(self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: AnyPin> AsMut<P> for SpecificPin<P> {
|
||||||
|
#[inline]
|
||||||
|
fn as_mut(&mut self) -> &mut P {
|
||||||
|
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
|
||||||
|
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
|
||||||
|
// both are zero-sized, and single-field, newtype structs are guaranteed
|
||||||
|
// to have the same layout as the field anyway, even for repr(Rust).
|
||||||
|
unsafe { transmute(self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Additional functionality
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
|
||||||
|
pub fn interrupt_edge(mut self, edge_type: InterruptEdge) -> Self {
|
||||||
|
self.regs.interrupt_edge(edge_type);
|
||||||
|
self.irq_enb();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_level(mut self, level_type: InterruptLevel) -> Self {
|
||||||
|
self.regs.interrupt_level(level_type);
|
||||||
|
self.irq_enb();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
|
||||||
|
/// See p.53 of the programmers guide for more information.
|
||||||
|
/// Possible delays in clock cycles:
|
||||||
|
/// - Delay 1: 1
|
||||||
|
/// - Delay 2: 2
|
||||||
|
/// - Delay 1 + Delay 2: 3
|
||||||
|
#[inline]
|
||||||
|
pub fn delay(self, delay_1: bool, delay_2: bool) -> Self {
|
||||||
|
self.regs.delay(delay_1, delay_2);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See p.52 of the programmers guide for more information.
|
||||||
|
/// When configured for pulse mode, a given pin will set the non-default state for exactly
|
||||||
|
/// one clock cycle before returning to the configured default state
|
||||||
|
pub fn pulse_mode(self, enable: bool, default_state: PinState) -> Self {
|
||||||
|
self.regs.pulse_mode(enable, default_state);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_edge(mut self, edge_type: InterruptEdge) -> Self {
|
||||||
|
self.regs.interrupt_edge(edge_type);
|
||||||
|
self.irq_enb();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_level(mut self, level_type: InterruptLevel) -> Self {
|
||||||
|
self.regs.interrupt_level(level_type);
|
||||||
|
self.irq_enb();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
|
||||||
|
/// See p.37 and p.38 of the programmers guide for more information.
|
||||||
|
#[inline]
|
||||||
|
pub fn filter_type(self, filter: FilterType, clksel: FilterClkSel) -> Self {
|
||||||
|
self.regs.filter_type(filter, clksel);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Embedded HAL traits
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
impl<I, M> ErrorType for Pin<I, M>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
M: PinMode,
|
||||||
|
{
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinId, C: OutputConfig> OutputPin for Pin<I, Output<C>> {
|
||||||
|
#[inline]
|
||||||
|
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self._set_high();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self._set_low();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, C> InputPin for Pin<I, Input<C>>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
C: InputConfig,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self._is_high())
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self._is_low())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, C> StatefulOutputPin for Pin<I, Output<C>>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
C: OutputConfig + ReadableOutput,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self._is_high())
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self._is_low())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, C> InputPin for Pin<I, Output<C>>
|
||||||
|
where
|
||||||
|
I: PinId,
|
||||||
|
C: OutputConfig + ReadableOutput,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self._is_high())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self._is_low())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Registers
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Provide a safe register interface for [`Pin`]s
|
||||||
|
///
|
||||||
|
/// This `struct` takes ownership of a [`PinId`] and provides an API to
|
||||||
|
/// access the corresponding registers.
|
||||||
|
pub(in crate::gpio) struct Registers<I: PinId> {
|
||||||
|
id: PhantomData<I>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// [`Registers`] takes ownership of the [`PinId`], and [`Pin`] guarantees that
|
||||||
|
// each pin is a singleton, so this implementation is safe.
|
||||||
|
unsafe impl<I: PinId> RegisterInterface for Registers<I> {
|
||||||
|
#[inline]
|
||||||
|
fn id(&self) -> DynPinId {
|
||||||
|
I::DYN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinId> Registers<I> {
|
||||||
|
/// Create a new instance of [`Registers`]
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Users must never create two simultaneous instances of this `struct` with
|
||||||
|
/// the same [`PinId`]
|
||||||
|
#[inline]
|
||||||
|
unsafe fn new() -> Self {
|
||||||
|
Registers { id: PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a type-level equivalent for the
|
||||||
|
/// [`RegisterInterface::change_mode`] method.
|
||||||
|
#[inline]
|
||||||
|
pub(in crate::gpio) fn change_mode<M: PinMode>(&mut self) {
|
||||||
|
RegisterInterface::change_mode(self, M::DYN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Pin definitions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
macro_rules! pins {
|
||||||
|
(
|
||||||
|
$Port:ident, $PinsName:ident, $($Id:ident,)+,
|
||||||
|
) => {
|
||||||
|
paste::paste!(
|
||||||
|
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
|
||||||
|
pub struct $PinsName {
|
||||||
|
port: $Port,
|
||||||
|
$(
|
||||||
|
#[doc = "Pin " $Id]
|
||||||
|
pub [<$Id:lower>]: Pin<$Id, Reset>,
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $PinsName {
|
||||||
|
/// Create a new struct containing all the Pins. Passing the IOCONFIG peripheral
|
||||||
|
/// is optional because it might be required to create pin definitions for both
|
||||||
|
/// ports.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(
|
||||||
|
syscfg: &mut va416xx::Sysconfig,
|
||||||
|
port: $Port
|
||||||
|
) -> $PinsName {
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.[<$Port:lower>]().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
$PinsName {
|
||||||
|
port,
|
||||||
|
// Safe because we only create one `Pin` per `PinId`
|
||||||
|
$(
|
||||||
|
[<$Id:lower>]: unsafe { Pin::new() },
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the peripheral ID
|
||||||
|
/// Safety: Read-only register
|
||||||
|
pub fn get_perid() -> u32 {
|
||||||
|
let port = unsafe { &(*$Port::ptr()) };
|
||||||
|
port.perid().read().bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the Pins struct and returns the port definitions
|
||||||
|
pub fn release(self) -> $Port {
|
||||||
|
self.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! declare_pins {
|
||||||
|
(
|
||||||
|
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+]
|
||||||
|
) => {
|
||||||
|
pins!($Port, $PinsName, $($Id,)+,);
|
||||||
|
$(
|
||||||
|
pin_id!($Group, $Id, $NUM);
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
A,
|
||||||
|
PinsA,
|
||||||
|
Porta,
|
||||||
|
[
|
||||||
|
(PA0, 0),
|
||||||
|
(PA1, 1),
|
||||||
|
(PA2, 2),
|
||||||
|
(PA3, 3),
|
||||||
|
(PA4, 4),
|
||||||
|
(PA5, 5),
|
||||||
|
(PA6, 6),
|
||||||
|
(PA7, 7),
|
||||||
|
(PA8, 8),
|
||||||
|
(PA9, 9),
|
||||||
|
(PA10, 10),
|
||||||
|
(PA11, 11),
|
||||||
|
(PA12, 12),
|
||||||
|
(PA13, 13),
|
||||||
|
(PA14, 14),
|
||||||
|
(PA15, 15),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
B,
|
||||||
|
PinsB,
|
||||||
|
Portb,
|
||||||
|
[
|
||||||
|
(PB0, 0),
|
||||||
|
(PB1, 1),
|
||||||
|
(PB2, 2),
|
||||||
|
(PB3, 3),
|
||||||
|
(PB4, 4),
|
||||||
|
(PB5, 5),
|
||||||
|
(PB6, 6),
|
||||||
|
(PB7, 7),
|
||||||
|
(PB8, 8),
|
||||||
|
(PB9, 9),
|
||||||
|
(PB10, 10),
|
||||||
|
(PB11, 11),
|
||||||
|
(PB12, 12),
|
||||||
|
(PB13, 13),
|
||||||
|
(PB14, 14),
|
||||||
|
(PB15, 15),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
C,
|
||||||
|
PinsC,
|
||||||
|
Portc,
|
||||||
|
[
|
||||||
|
(PC0, 0),
|
||||||
|
(PC1, 1),
|
||||||
|
(PC2, 2),
|
||||||
|
(PC3, 3),
|
||||||
|
(PC4, 4),
|
||||||
|
(PC5, 5),
|
||||||
|
(PC6, 6),
|
||||||
|
(PC7, 7),
|
||||||
|
(PC8, 8),
|
||||||
|
(PC9, 9),
|
||||||
|
(PC10, 10),
|
||||||
|
(PC11, 11),
|
||||||
|
(PC12, 12),
|
||||||
|
(PC13, 13),
|
||||||
|
(PC14, 14),
|
||||||
|
(PC15, 15),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
D,
|
||||||
|
PinsD,
|
||||||
|
Portd,
|
||||||
|
[
|
||||||
|
(PD0, 0),
|
||||||
|
(PD1, 1),
|
||||||
|
(PD2, 2),
|
||||||
|
(PD3, 3),
|
||||||
|
(PD4, 4),
|
||||||
|
(PD5, 5),
|
||||||
|
(PD6, 6),
|
||||||
|
(PD7, 7),
|
||||||
|
(PD8, 8),
|
||||||
|
(PD9, 9),
|
||||||
|
(PD10, 10),
|
||||||
|
(PD11, 11),
|
||||||
|
(PD12, 12),
|
||||||
|
(PD13, 13),
|
||||||
|
(PD14, 14),
|
||||||
|
(PD15, 15),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
E,
|
||||||
|
PinsE,
|
||||||
|
Porte,
|
||||||
|
[
|
||||||
|
(PE0, 0),
|
||||||
|
(PE1, 1),
|
||||||
|
(PE2, 2),
|
||||||
|
(PE3, 3),
|
||||||
|
(PE4, 4),
|
||||||
|
(PE5, 5),
|
||||||
|
(PE6, 6),
|
||||||
|
(PE7, 7),
|
||||||
|
(PE8, 8),
|
||||||
|
(PE9, 9),
|
||||||
|
(PE10, 10),
|
||||||
|
(PE11, 11),
|
||||||
|
(PE12, 12),
|
||||||
|
(PE13, 13),
|
||||||
|
(PE14, 14),
|
||||||
|
(PE15, 15),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
F,
|
||||||
|
PinsF,
|
||||||
|
Portf,
|
||||||
|
[
|
||||||
|
(PF0, 0),
|
||||||
|
(PF1, 1),
|
||||||
|
(PF2, 2),
|
||||||
|
(PF3, 3),
|
||||||
|
(PF4, 4),
|
||||||
|
(PF5, 5),
|
||||||
|
(PF6, 6),
|
||||||
|
(PF7, 7),
|
||||||
|
(PF8, 8),
|
||||||
|
(PF9, 9),
|
||||||
|
(PF10, 10),
|
||||||
|
(PF11, 11),
|
||||||
|
(PF12, 12),
|
||||||
|
(PF13, 13),
|
||||||
|
(PF14, 14),
|
||||||
|
(PF15, 15),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
declare_pins!(
|
||||||
|
G,
|
||||||
|
PinsG,
|
||||||
|
Portg,
|
||||||
|
[
|
||||||
|
(PG0, 0),
|
||||||
|
(PG1, 1),
|
||||||
|
(PG2, 2),
|
||||||
|
(PG3, 3),
|
||||||
|
(PG4, 4),
|
||||||
|
(PG5, 5),
|
||||||
|
(PG6, 6),
|
||||||
|
(PG7, 7),
|
||||||
|
]
|
||||||
|
);
|
387
va416xx-hal/src/gpio/reg.rs
Normal file
387
va416xx-hal/src/gpio/reg.rs
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
use crate::FunSel;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
dynpin::{self, DynGroup, DynPinId},
|
||||||
|
DynPinMode, FilterClkSel, FilterType, InterruptEdge, InterruptLevel, IsMaskedError, PinState,
|
||||||
|
};
|
||||||
|
use va416xx::{ioconfig, porta, Ioconfig, Porta, Portb, Portc, Portd, Porte, Portf, Portg};
|
||||||
|
|
||||||
|
/// Type definition to avoid confusion: These register blocks are identical
|
||||||
|
type PortRegisterBlock = porta::RegisterBlock;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// ModeFields
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Collect all fields needed to set the [`PinMode`](super::PinMode)
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ModeFields {
|
||||||
|
dir: bool,
|
||||||
|
opendrn: bool,
|
||||||
|
pull_en: bool,
|
||||||
|
/// true for pullup, false for pulldown
|
||||||
|
pull_dir: bool,
|
||||||
|
funsel: u8,
|
||||||
|
enb_input: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DynPinMode> for ModeFields {
|
||||||
|
#[inline]
|
||||||
|
fn from(mode: DynPinMode) -> Self {
|
||||||
|
let mut fields = Self::default();
|
||||||
|
use DynPinMode::*;
|
||||||
|
match mode {
|
||||||
|
Input(config) => {
|
||||||
|
use dynpin::DynInput::*;
|
||||||
|
fields.dir = false;
|
||||||
|
fields.funsel = FunSel::Sel0 as u8;
|
||||||
|
match config {
|
||||||
|
Floating => (),
|
||||||
|
PullUp => {
|
||||||
|
fields.pull_en = true;
|
||||||
|
fields.pull_dir = true;
|
||||||
|
}
|
||||||
|
PullDown => {
|
||||||
|
fields.pull_en = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Output(config) => {
|
||||||
|
use dynpin::DynOutput::*;
|
||||||
|
fields.dir = true;
|
||||||
|
fields.funsel = FunSel::Sel0 as u8;
|
||||||
|
match config {
|
||||||
|
PushPull => (),
|
||||||
|
OpenDrain => {
|
||||||
|
fields.opendrn = true;
|
||||||
|
}
|
||||||
|
ReadableOpenDrain => {
|
||||||
|
fields.enb_input = true;
|
||||||
|
fields.opendrn = true;
|
||||||
|
}
|
||||||
|
ReadablePushPull => {
|
||||||
|
fields.enb_input = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Alternate(config) => {
|
||||||
|
fields.funsel = config as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// RegisterInterface
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
pub type PortReg = ioconfig::Porta;
|
||||||
|
|
||||||
|
/// Provide a safe register interface for pin objects
|
||||||
|
///
|
||||||
|
/// [`PORT`], like every PAC `struct`, is [`Send`] but not [`Sync`], because it
|
||||||
|
/// points to a `RegisterBlock` of `VolatileCell`s. Unfortunately, such an
|
||||||
|
/// interface is quite restrictive. Instead, it would be ideal if we could split
|
||||||
|
/// the [`PORT`] into independent pins that are both [`Send`] and [`Sync`].
|
||||||
|
///
|
||||||
|
/// [`PORT`] is a single, zero-sized marker `struct` that provides access to
|
||||||
|
/// every [`PORT`] register. Instead, we would like to create zero-sized marker
|
||||||
|
/// `struct`s for every pin, where each pin is only allowed to control its own
|
||||||
|
/// registers. Furthermore, each pin `struct` should be a singleton, so that
|
||||||
|
/// exclusive access to the `struct` also guarantees exclusive access to the
|
||||||
|
/// corresponding registers. Finally, the pin `struct`s should not have any
|
||||||
|
/// interior mutability. Together, these requirements would allow the pin
|
||||||
|
/// `struct`s to be both [`Send`] and [`Sync`].
|
||||||
|
///
|
||||||
|
/// This trait creates a safe API for accomplishing these goals. Implementers
|
||||||
|
/// supply a pin ID through the [`id`] function. The remaining functions provide
|
||||||
|
/// a safe API for accessing the registers associated with that pin ID. Any
|
||||||
|
/// modification of the registers requires `&mut self`, which destroys interior
|
||||||
|
/// mutability.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Users should only implement the [`id`] function. No default function
|
||||||
|
/// implementations should be overridden. The implementing type must also have
|
||||||
|
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
|
||||||
|
/// pin ID is a singleton.
|
||||||
|
///
|
||||||
|
/// [`id`]: Self::id
|
||||||
|
pub(super) unsafe trait RegisterInterface {
|
||||||
|
/// Provide a [`DynPinId`] identifying the set of registers controlled by
|
||||||
|
/// this type.
|
||||||
|
fn id(&self) -> DynPinId;
|
||||||
|
|
||||||
|
const PORTA: *const PortRegisterBlock = Porta::ptr();
|
||||||
|
const PORTB: *const PortRegisterBlock = Portb::ptr();
|
||||||
|
const PORTC: *const PortRegisterBlock = Portc::ptr();
|
||||||
|
const PORTD: *const PortRegisterBlock = Portd::ptr();
|
||||||
|
const PORTE: *const PortRegisterBlock = Porte::ptr();
|
||||||
|
const PORTF: *const PortRegisterBlock = Portf::ptr();
|
||||||
|
const PORTG: *const PortRegisterBlock = Portg::ptr();
|
||||||
|
|
||||||
|
/// Change the pin mode
|
||||||
|
#[inline]
|
||||||
|
fn change_mode(&mut self, mode: DynPinMode) {
|
||||||
|
let ModeFields {
|
||||||
|
dir,
|
||||||
|
funsel,
|
||||||
|
opendrn,
|
||||||
|
pull_dir,
|
||||||
|
pull_en,
|
||||||
|
enb_input,
|
||||||
|
} = mode.into();
|
||||||
|
let (portreg, iocfg) = (self.port_reg(), self.iocfg_port());
|
||||||
|
iocfg.write(|w| {
|
||||||
|
w.opendrn().bit(opendrn);
|
||||||
|
w.pen().bit(pull_en);
|
||||||
|
w.plevel().bit(pull_dir);
|
||||||
|
w.iewo().bit(enb_input);
|
||||||
|
unsafe { w.funsel().bits(funsel) }
|
||||||
|
});
|
||||||
|
let mask = self.mask_32();
|
||||||
|
unsafe {
|
||||||
|
if dir {
|
||||||
|
portreg.dir().modify(|r, w| w.bits(r.bits() | mask));
|
||||||
|
// Clear output
|
||||||
|
portreg.clrout().write(|w| w.bits(mask));
|
||||||
|
} else {
|
||||||
|
portreg.dir().modify(|r, w| w.bits(r.bits() & !mask));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn port_reg(&self) -> &PortRegisterBlock {
|
||||||
|
match self.id().group {
|
||||||
|
DynGroup::A => unsafe { &(*Self::PORTA) },
|
||||||
|
DynGroup::B => unsafe { &(*Self::PORTB) },
|
||||||
|
DynGroup::C => unsafe { &(*Self::PORTC) },
|
||||||
|
DynGroup::D => unsafe { &(*Self::PORTD) },
|
||||||
|
DynGroup::E => unsafe { &(*Self::PORTE) },
|
||||||
|
DynGroup::F => unsafe { &(*Self::PORTF) },
|
||||||
|
DynGroup::G => unsafe { &(*Self::PORTG) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iocfg_port(&self) -> &PortReg {
|
||||||
|
let ioconfig = unsafe { Ioconfig::ptr().as_ref().unwrap() };
|
||||||
|
match self.id().group {
|
||||||
|
DynGroup::A => ioconfig.porta(self.id().num as usize),
|
||||||
|
DynGroup::B => ioconfig.portb0(self.id().num as usize),
|
||||||
|
DynGroup::C => ioconfig.portc0(self.id().num as usize),
|
||||||
|
DynGroup::D => ioconfig.portd0(self.id().num as usize),
|
||||||
|
DynGroup::E => ioconfig.porte0(self.id().num as usize),
|
||||||
|
DynGroup::F => ioconfig.portf0(self.id().num as usize),
|
||||||
|
DynGroup::G => ioconfig.portg0(self.id().num as usize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mask_32(&self) -> u32 {
|
||||||
|
1 << self.id().num
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable_irq(&self) {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_enb()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Read the logic level of an output pin
|
||||||
|
fn read_pin(&self) -> bool {
|
||||||
|
let portreg = self.port_reg();
|
||||||
|
((portreg.datainraw().read().bits() >> self.id().num) & 0x01) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get DATAMASK bit for this particular pin
|
||||||
|
#[inline(always)]
|
||||||
|
fn datamask(&self) -> bool {
|
||||||
|
let portreg = self.port_reg();
|
||||||
|
(portreg.datamask().read().bits() >> self.id().num) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a pin but use the masked version but check whether the datamask for the pin is
|
||||||
|
/// cleared as well
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_pin_masked(&self) -> Result<bool, IsMaskedError> {
|
||||||
|
if !self.datamask() {
|
||||||
|
Err(IsMaskedError)
|
||||||
|
} else {
|
||||||
|
Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the logic level of an output pin
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_pin(&mut self, bit: bool) {
|
||||||
|
// Safety: SETOUT is a "mask" register, and we only write the bit for
|
||||||
|
// this pin ID
|
||||||
|
unsafe {
|
||||||
|
if bit {
|
||||||
|
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
|
||||||
|
} else {
|
||||||
|
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the logic level of an output pin but check whether the datamask for the pin is
|
||||||
|
/// cleared as well
|
||||||
|
#[inline]
|
||||||
|
fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> {
|
||||||
|
if !self.datamask() {
|
||||||
|
Err(IsMaskedError)
|
||||||
|
} else {
|
||||||
|
// Safety: SETOUT is a "mask" register, and we only write the bit for
|
||||||
|
// this pin ID
|
||||||
|
unsafe {
|
||||||
|
if bit {
|
||||||
|
self.port_reg().setout().write(|w| w.bits(self.mask_32()));
|
||||||
|
} else {
|
||||||
|
self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure
|
||||||
|
/// When using edge mode, it is possible to generate interrupts on both edges as well
|
||||||
|
#[inline]
|
||||||
|
fn interrupt_edge(&mut self, edge_type: InterruptEdge) {
|
||||||
|
unsafe {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_sen()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
match edge_type {
|
||||||
|
InterruptEdge::HighToLow => {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_evt()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
}
|
||||||
|
InterruptEdge::LowToHigh => {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_evt()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
}
|
||||||
|
InterruptEdge::BothEdges => {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_edge()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure which edge or level type triggers an interrupt
|
||||||
|
#[inline]
|
||||||
|
fn interrupt_level(&mut self, level: InterruptLevel) {
|
||||||
|
unsafe {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_sen()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
if level == InterruptLevel::Low {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_evt()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
} else {
|
||||||
|
self.port_reg()
|
||||||
|
.irq_evt()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for input pins
|
||||||
|
#[inline]
|
||||||
|
fn filter_type(&self, filter: FilterType, clksel: FilterClkSel) {
|
||||||
|
self.iocfg_port().modify(|_, w| {
|
||||||
|
// Safety: Only write to register for this Pin ID
|
||||||
|
unsafe {
|
||||||
|
w.flttype().bits(filter as u8);
|
||||||
|
w.fltclk().bits(clksel as u8)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set DATAMASK bit for this particular pin. 1 is the default
|
||||||
|
/// state of the bit and allows access of the corresponding bit
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_datamask(&self) {
|
||||||
|
let portreg = self.port_reg();
|
||||||
|
unsafe {
|
||||||
|
portreg
|
||||||
|
.datamask()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear DATAMASK bit for this particular pin. This prevents access
|
||||||
|
/// of the corresponding bit for output and input operations
|
||||||
|
#[inline(always)]
|
||||||
|
fn clear_datamask(&self) {
|
||||||
|
let portreg = self.port_reg();
|
||||||
|
unsafe {
|
||||||
|
portreg
|
||||||
|
.datamask()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for output pins
|
||||||
|
/// See p.52 of the programmers guide for more information.
|
||||||
|
/// When configured for pulse mode, a given pin will set the non-default state for exactly
|
||||||
|
/// one clock cycle before returning to the configured default state
|
||||||
|
fn pulse_mode(&self, enable: bool, default_state: PinState) {
|
||||||
|
let portreg = self.port_reg();
|
||||||
|
unsafe {
|
||||||
|
if enable {
|
||||||
|
portreg
|
||||||
|
.pulse()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
} else {
|
||||||
|
portreg
|
||||||
|
.pulse()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
}
|
||||||
|
if default_state == PinState::Low {
|
||||||
|
portreg
|
||||||
|
.pulsebase()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
} else {
|
||||||
|
portreg
|
||||||
|
.pulsebase()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for output pins
|
||||||
|
fn delay(&self, delay_1: bool, delay_2: bool) {
|
||||||
|
let portreg = self.port_reg();
|
||||||
|
unsafe {
|
||||||
|
if delay_1 {
|
||||||
|
portreg
|
||||||
|
.delay1()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
} else {
|
||||||
|
portreg
|
||||||
|
.delay1()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
}
|
||||||
|
if delay_2 {
|
||||||
|
portreg
|
||||||
|
.delay2()
|
||||||
|
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||||
|
} else {
|
||||||
|
portreg
|
||||||
|
.delay2()
|
||||||
|
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,4 +3,899 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
|
//! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
|
||||||
pub use vorago_shared_periphs::i2c::*;
|
use crate::{
|
||||||
|
clock::{Clocks, PeripheralSelect},
|
||||||
|
pac,
|
||||||
|
prelude::SyscfgExt,
|
||||||
|
time::Hertz,
|
||||||
|
typelevel::Sealed,
|
||||||
|
};
|
||||||
|
use core::{marker::PhantomData, ops::Deref};
|
||||||
|
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Defintions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
const CLK_100K: Hertz = Hertz::from_raw(100_000);
|
||||||
|
const CLK_400K: Hertz = Hertz::from_raw(400_000);
|
||||||
|
const MIN_CLK_400K: Hertz = Hertz::from_raw(10_000_000);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum FifoEmptyMode {
|
||||||
|
Stall = 0,
|
||||||
|
EndTransaction = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct ClockTooSlowForFastI2c;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Error {
|
||||||
|
InvalidTimingParams,
|
||||||
|
ArbitrationLost,
|
||||||
|
NackAddr,
|
||||||
|
/// Data not acknowledged in write operation
|
||||||
|
NackData,
|
||||||
|
/// Not enough data received in read operation
|
||||||
|
InsufficientDataReceived,
|
||||||
|
/// Number of bytes in transfer too large (larger than 0x7fe)
|
||||||
|
DataTooLarge,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum InitError {
|
||||||
|
/// Wrong address used in constructor
|
||||||
|
WrongAddrMode,
|
||||||
|
/// APB1 clock is too slow for fast I2C mode.
|
||||||
|
ClkTooSlow(ClockTooSlowForFastI2c),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ClockTooSlowForFastI2c> for InitError {
|
||||||
|
fn from(value: ClockTooSlowForFastI2c) -> Self {
|
||||||
|
Self::ClkTooSlow(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::i2c::Error for Error {
|
||||||
|
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
|
||||||
|
match self {
|
||||||
|
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
|
||||||
|
Error::NackAddr => {
|
||||||
|
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
|
||||||
|
}
|
||||||
|
Error::NackData => {
|
||||||
|
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
|
||||||
|
}
|
||||||
|
Error::DataTooLarge | Error::InsufficientDataReceived | Error::InvalidTimingParams => {
|
||||||
|
embedded_hal::i2c::ErrorKind::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
enum I2cCmd {
|
||||||
|
Start = 0b00,
|
||||||
|
Stop = 0b10,
|
||||||
|
StartWithStop = 0b11,
|
||||||
|
Cancel = 0b100,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum I2cSpeed {
|
||||||
|
Regular100khz = 0,
|
||||||
|
Fast400khz = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum I2cDirection {
|
||||||
|
Send = 0,
|
||||||
|
Read = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum I2cAddress {
|
||||||
|
Regular(u8),
|
||||||
|
TenBit(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type I2cRegBlock = pac::i2c0::RegisterBlock;
|
||||||
|
|
||||||
|
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||||
|
/// format is the same for all SPI blocks.
|
||||||
|
pub trait Instance: Deref<Target = I2cRegBlock> {
|
||||||
|
const IDX: u8;
|
||||||
|
const PERIPH_SEL: PeripheralSelect;
|
||||||
|
|
||||||
|
fn ptr() -> *const I2cRegBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for pac::I2c0 {
|
||||||
|
const IDX: u8 = 0;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const I2cRegBlock {
|
||||||
|
Self::ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for pac::I2c1 {
|
||||||
|
const IDX: u8 = 1;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const I2cRegBlock {
|
||||||
|
Self::ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for pac::I2c2 {
|
||||||
|
const IDX: u8 = 2;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const I2cRegBlock {
|
||||||
|
Self::ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Config
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct TrTfThighTlow(u8, u8, u8, u8);
|
||||||
|
pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
|
||||||
|
|
||||||
|
pub struct TimingCfg {
|
||||||
|
// 4 bit max width
|
||||||
|
tr: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
tf: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
thigh: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
tlow: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
tsu_sto: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
tsu_sta: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
thd_sta: u8,
|
||||||
|
// 4 bit max width
|
||||||
|
tbuf: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimingCfg {
|
||||||
|
pub fn new(
|
||||||
|
first_16_bits: TrTfThighTlow,
|
||||||
|
second_16_bits: TsuStoTsuStaThdStaTBuf,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
if first_16_bits.0 > 0xf
|
||||||
|
|| first_16_bits.1 > 0xf
|
||||||
|
|| first_16_bits.2 > 0xf
|
||||||
|
|| first_16_bits.3 > 0xf
|
||||||
|
|| second_16_bits.0 > 0xf
|
||||||
|
|| second_16_bits.1 > 0xf
|
||||||
|
|| second_16_bits.2 > 0xf
|
||||||
|
|| second_16_bits.3 > 0xf
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidTimingParams);
|
||||||
|
}
|
||||||
|
Ok(TimingCfg {
|
||||||
|
tr: first_16_bits.0,
|
||||||
|
tf: first_16_bits.1,
|
||||||
|
thigh: first_16_bits.2,
|
||||||
|
tlow: first_16_bits.3,
|
||||||
|
tsu_sto: second_16_bits.0,
|
||||||
|
tsu_sta: second_16_bits.1,
|
||||||
|
thd_sta: second_16_bits.2,
|
||||||
|
tbuf: second_16_bits.3,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reg(&self) -> u32 {
|
||||||
|
(self.tbuf as u32) << 28
|
||||||
|
| (self.thd_sta as u32) << 24
|
||||||
|
| (self.tsu_sta as u32) << 20
|
||||||
|
| (self.tsu_sto as u32) << 16
|
||||||
|
| (self.tlow as u32) << 12
|
||||||
|
| (self.thigh as u32) << 8
|
||||||
|
| (self.tf as u32) << 4
|
||||||
|
| (self.tr as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TimingCfg {
|
||||||
|
fn default() -> Self {
|
||||||
|
TimingCfg {
|
||||||
|
tr: 0x02,
|
||||||
|
tf: 0x01,
|
||||||
|
thigh: 0x08,
|
||||||
|
tlow: 0x09,
|
||||||
|
tsu_sto: 0x8,
|
||||||
|
tsu_sta: 0x0a,
|
||||||
|
thd_sta: 0x8,
|
||||||
|
tbuf: 0xa,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MasterConfig {
|
||||||
|
pub tx_fe_mode: FifoEmptyMode,
|
||||||
|
pub rx_fe_mode: FifoEmptyMode,
|
||||||
|
/// Enable the analog delay glitch filter
|
||||||
|
pub alg_filt: bool,
|
||||||
|
/// Enable the digital glitch filter
|
||||||
|
pub dlg_filt: bool,
|
||||||
|
pub tm_cfg: Option<TimingCfg>,
|
||||||
|
// Loopback mode
|
||||||
|
// lbm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MasterConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
MasterConfig {
|
||||||
|
tx_fe_mode: FifoEmptyMode::Stall,
|
||||||
|
rx_fe_mode: FifoEmptyMode::Stall,
|
||||||
|
alg_filt: false,
|
||||||
|
dlg_filt: false,
|
||||||
|
tm_cfg: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for MasterConfig {}
|
||||||
|
|
||||||
|
pub struct SlaveConfig {
|
||||||
|
pub tx_fe_mode: FifoEmptyMode,
|
||||||
|
pub rx_fe_mode: FifoEmptyMode,
|
||||||
|
/// Maximum number of words before issuing a negative acknowledge.
|
||||||
|
/// Range should be 0 to 0x7fe. Setting the value to 0x7ff has the same effect as not setting
|
||||||
|
/// the enable bit since RXCOUNT stops counting at 0x7fe.
|
||||||
|
pub max_words: Option<usize>,
|
||||||
|
/// A received address is compared to the ADDRESS register (addr) using the address mask
|
||||||
|
/// (addr_mask). Those bits with a 1 in the address mask must match for there to be an address
|
||||||
|
/// match
|
||||||
|
pub addr: I2cAddress,
|
||||||
|
/// The default address mask will be 0x3ff to only allow full matches
|
||||||
|
pub addr_mask: Option<u16>,
|
||||||
|
/// Optionally specify a second I2C address the slave interface responds to
|
||||||
|
pub addr_b: Option<I2cAddress>,
|
||||||
|
pub addr_b_mask: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlaveConfig {
|
||||||
|
/// Build a default slave config given a specified slave address to respond to
|
||||||
|
pub fn new(addr: I2cAddress) -> Self {
|
||||||
|
SlaveConfig {
|
||||||
|
tx_fe_mode: FifoEmptyMode::Stall,
|
||||||
|
rx_fe_mode: FifoEmptyMode::Stall,
|
||||||
|
max_words: None,
|
||||||
|
addr,
|
||||||
|
addr_mask: None,
|
||||||
|
addr_b: None,
|
||||||
|
addr_b_mask: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for SlaveConfig {}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// I2C Base
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct I2cBase<I2c> {
|
||||||
|
i2c: I2c,
|
||||||
|
clock: Hertz,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2C> I2cBase<I2C> {
|
||||||
|
#[inline]
|
||||||
|
fn unwrap_addr(addr: I2cAddress) -> (u16, u32) {
|
||||||
|
match addr {
|
||||||
|
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
|
||||||
|
I2cAddress::TenBit(addr) => (addr, 1 << 15),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance> I2cBase<I2c> {
|
||||||
|
pub fn new(
|
||||||
|
i2c: I2c,
|
||||||
|
syscfg: &mut pac::Sysconfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
ms_cfg: Option<&MasterConfig>,
|
||||||
|
sl_cfg: Option<&SlaveConfig>,
|
||||||
|
) -> Result<Self, ClockTooSlowForFastI2c> {
|
||||||
|
syscfg.enable_peripheral_clock(I2c::PERIPH_SEL);
|
||||||
|
|
||||||
|
let mut i2c_base = I2cBase {
|
||||||
|
i2c,
|
||||||
|
clock: clocks.apb1(),
|
||||||
|
};
|
||||||
|
if let Some(ms_cfg) = ms_cfg {
|
||||||
|
i2c_base.cfg_master(ms_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sl_cfg) = sl_cfg {
|
||||||
|
i2c_base.cfg_slave(sl_cfg);
|
||||||
|
}
|
||||||
|
i2c_base.cfg_clk_scale(speed_mode)?;
|
||||||
|
Ok(i2c_base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cfg_master(&mut self, ms_cfg: &MasterConfig) {
|
||||||
|
let (txfemd, rxfemd) = match (ms_cfg.tx_fe_mode, ms_cfg.rx_fe_mode) {
|
||||||
|
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
|
||||||
|
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
|
||||||
|
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
|
||||||
|
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
|
||||||
|
};
|
||||||
|
self.i2c.ctrl().modify(|_, w| {
|
||||||
|
w.txfemd().bit(txfemd);
|
||||||
|
w.rxffmd().bit(rxfemd);
|
||||||
|
w.dlgfilter().bit(ms_cfg.dlg_filt);
|
||||||
|
w.algfilter().bit(ms_cfg.alg_filt)
|
||||||
|
});
|
||||||
|
if let Some(ref tm_cfg) = ms_cfg.tm_cfg {
|
||||||
|
self.i2c
|
||||||
|
.tmconfig()
|
||||||
|
.write(|w| unsafe { w.bits(tm_cfg.reg()) });
|
||||||
|
}
|
||||||
|
self.i2c.fifo_clr().write(|w| {
|
||||||
|
w.rxfifo().set_bit();
|
||||||
|
w.txfifo().set_bit()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cfg_slave(&mut self, sl_cfg: &SlaveConfig) {
|
||||||
|
let (txfemd, rxfemd) = match (sl_cfg.tx_fe_mode, sl_cfg.rx_fe_mode) {
|
||||||
|
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
|
||||||
|
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
|
||||||
|
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
|
||||||
|
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
|
||||||
|
};
|
||||||
|
self.i2c.s0_ctrl().modify(|_, w| {
|
||||||
|
w.txfemd().bit(txfemd);
|
||||||
|
w.rxffmd().bit(rxfemd)
|
||||||
|
});
|
||||||
|
self.i2c.s0_fifo_clr().write(|w| {
|
||||||
|
w.rxfifo().set_bit();
|
||||||
|
w.txfifo().set_bit()
|
||||||
|
});
|
||||||
|
let max_words = sl_cfg.max_words;
|
||||||
|
if let Some(max_words) = max_words {
|
||||||
|
self.i2c
|
||||||
|
.s0_maxwords()
|
||||||
|
.write(|w| unsafe { w.bits(1 << 31 | max_words as u32) });
|
||||||
|
}
|
||||||
|
let (addr, addr_mode_mask) = Self::unwrap_addr(sl_cfg.addr);
|
||||||
|
// The first bit is the read/write value. Normally, both read and write are matched
|
||||||
|
// using the RWMASK bit of the address mask register
|
||||||
|
self.i2c
|
||||||
|
.s0_address()
|
||||||
|
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
|
||||||
|
if let Some(addr_mask) = sl_cfg.addr_mask {
|
||||||
|
self.i2c
|
||||||
|
.s0_addressmask()
|
||||||
|
.write(|w| unsafe { w.bits((addr_mask << 1) as u32) });
|
||||||
|
}
|
||||||
|
if let Some(addr_b) = sl_cfg.addr_b {
|
||||||
|
let (addr, addr_mode_mask) = Self::unwrap_addr(addr_b);
|
||||||
|
self.i2c
|
||||||
|
.s0_addressb()
|
||||||
|
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) })
|
||||||
|
}
|
||||||
|
if let Some(addr_b_mask) = sl_cfg.addr_b_mask {
|
||||||
|
self.i2c
|
||||||
|
.s0_addressmaskb()
|
||||||
|
.write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn filters(&mut self, digital_filt: bool, analog_filt: bool) {
|
||||||
|
self.i2c.ctrl().modify(|_, w| {
|
||||||
|
w.dlgfilter().bit(digital_filt);
|
||||||
|
w.algfilter().bit(analog_filt)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fifo_empty_mode(&mut self, rx: FifoEmptyMode, tx: FifoEmptyMode) {
|
||||||
|
self.i2c.ctrl().modify(|_, w| {
|
||||||
|
w.txfemd().bit(tx as u8 != 0);
|
||||||
|
w.rxffmd().bit(rx as u8 != 0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2c> {
|
||||||
|
if speed_mode == I2cSpeed::Regular100khz {
|
||||||
|
Ok(((self.clock.raw() / CLK_100K.raw() / 20) - 1) as u8)
|
||||||
|
} else {
|
||||||
|
if self.clock.raw() < MIN_CLK_400K.raw() {
|
||||||
|
return Err(ClockTooSlowForFastI2c);
|
||||||
|
}
|
||||||
|
Ok(((self.clock.raw() / CLK_400K.raw() / 25) - 1) as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures the clock scale for a given speed mode setting
|
||||||
|
pub fn cfg_clk_scale(&mut self, speed_mode: I2cSpeed) -> Result<(), ClockTooSlowForFastI2c> {
|
||||||
|
let clk_div = self.calc_clk_div(speed_mode)?;
|
||||||
|
self.i2c
|
||||||
|
.clkscale()
|
||||||
|
.write(|w| unsafe { w.bits((speed_mode as u32) << 31 | clk_div as u32) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_address(&mut self, addr: u16) {
|
||||||
|
// Load address
|
||||||
|
self.i2c
|
||||||
|
.address()
|
||||||
|
.write(|w| unsafe { w.bits((addr << 1) as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn stop_cmd(&mut self) {
|
||||||
|
self.i2c
|
||||||
|
.cmd()
|
||||||
|
.write(|w| unsafe { w.bits(I2cCmd::Stop as u32) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// I2C Master
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
|
||||||
|
i2c_base: I2cBase<I2c>,
|
||||||
|
addr: PhantomData<Addr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance, Addr> I2cMaster<I2c, Addr> {
|
||||||
|
pub fn new(
|
||||||
|
i2c: I2c,
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
cfg: MasterConfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<Self, ClockTooSlowForFastI2c> {
|
||||||
|
Ok(I2cMaster {
|
||||||
|
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, Some(&cfg), None)?,
|
||||||
|
addr: PhantomData,
|
||||||
|
}
|
||||||
|
.enable_master())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cancel_transfer(&self) {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.cmd()
|
||||||
|
.write(|w| unsafe { w.bits(I2cCmd::Cancel as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_tx_fifo(&self) {
|
||||||
|
self.i2c_base.i2c.fifo_clr().write(|w| w.txfifo().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_rx_fifo(&self) {
|
||||||
|
self.i2c_base.i2c.fifo_clr().write(|w| w.rxfifo().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable_master(self) -> Self {
|
||||||
|
self.i2c_base.i2c.ctrl().modify(|_, w| w.enable().set_bit());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_master(self) -> Self {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| w.enable().clear_bit());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn load_fifo(&self, word: u8) {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.data()
|
||||||
|
.write(|w| unsafe { w.bits(word as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_fifo(&self) -> u8 {
|
||||||
|
self.i2c_base.i2c.data().read().bits() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_handler_write(&mut self, init_cmd: &I2cCmd) {
|
||||||
|
self.clear_tx_fifo();
|
||||||
|
if *init_cmd == I2cCmd::Start {
|
||||||
|
self.i2c_base.stop_cmd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_base(
|
||||||
|
&mut self,
|
||||||
|
addr: I2cAddress,
|
||||||
|
init_cmd: I2cCmd,
|
||||||
|
bytes: impl IntoIterator<Item = u8>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut iter = bytes.into_iter();
|
||||||
|
// Load address
|
||||||
|
let (addr, addr_mode_bit) = I2cBase::<I2c>::unwrap_addr(addr);
|
||||||
|
self.i2c_base.i2c.address().write(|w| unsafe {
|
||||||
|
w.bits(I2cDirection::Send as u32 | (addr << 1) as u32 | addr_mode_bit)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.cmd()
|
||||||
|
.write(|w| unsafe { w.bits(init_cmd as u32) });
|
||||||
|
let mut load_if_next_available = || {
|
||||||
|
if let Some(next_byte) = iter.next() {
|
||||||
|
self.load_fifo(next_byte);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
let status_reader = self.i2c_base.i2c.status().read();
|
||||||
|
if status_reader.arblost().bit_is_set() {
|
||||||
|
self.error_handler_write(&init_cmd);
|
||||||
|
return Err(Error::ArbitrationLost);
|
||||||
|
} else if status_reader.nackaddr().bit_is_set() {
|
||||||
|
self.error_handler_write(&init_cmd);
|
||||||
|
return Err(Error::NackAddr);
|
||||||
|
} else if status_reader.nackdata().bit_is_set() {
|
||||||
|
self.error_handler_write(&init_cmd);
|
||||||
|
return Err(Error::NackData);
|
||||||
|
} else if status_reader.idle().bit_is_set() {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
while !status_reader.txnfull().bit_is_set() {
|
||||||
|
load_if_next_available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_from_buffer(
|
||||||
|
&mut self,
|
||||||
|
init_cmd: I2cCmd,
|
||||||
|
addr: I2cAddress,
|
||||||
|
output: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let len = output.len();
|
||||||
|
// It should theoretically possible to transfer larger data sizes by tracking
|
||||||
|
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||||
|
// bytes are remaining. However, large transfer like this are not common. This
|
||||||
|
// feature will therefore not be supported for now.
|
||||||
|
if len > 0x7fe {
|
||||||
|
return Err(Error::DataTooLarge);
|
||||||
|
}
|
||||||
|
// Load number of words
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.words()
|
||||||
|
.write(|w| unsafe { w.bits(len as u32) });
|
||||||
|
let mut bytes = output.iter();
|
||||||
|
// FIFO has a depth of 16. We load slightly above the trigger level
|
||||||
|
// but not all of it because the transaction might fail immediately
|
||||||
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
|
// load the FIFO
|
||||||
|
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
|
||||||
|
self.load_fifo(*bytes.next().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_base(addr, init_cmd, output.iter().cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_internal(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
|
let len = buffer.len();
|
||||||
|
// It should theoretically possible to transfer larger data sizes by tracking
|
||||||
|
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||||
|
// bytes are remaining. However, large transfer like this are not common. This
|
||||||
|
// feature will therefore not be supported for now.
|
||||||
|
if len > 0x7fe {
|
||||||
|
return Err(Error::DataTooLarge);
|
||||||
|
}
|
||||||
|
// Clear the receive FIFO
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
|
||||||
|
// Load number of words
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.words()
|
||||||
|
.write(|w| unsafe { w.bits(len as u32) });
|
||||||
|
let (addr, addr_mode_bit) = match addr {
|
||||||
|
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
|
||||||
|
I2cAddress::TenBit(addr) => (addr, 1 << 15),
|
||||||
|
};
|
||||||
|
// Load address
|
||||||
|
self.i2c_base.i2c.address().write(|w| unsafe {
|
||||||
|
w.bits(I2cDirection::Read as u32 | (addr << 1) as u32 | addr_mode_bit)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut buf_iter = buffer.iter_mut();
|
||||||
|
let mut read_bytes = 0;
|
||||||
|
// Start receive transfer
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.cmd()
|
||||||
|
.write(|w| unsafe { w.bits(I2cCmd::StartWithStop as u32) });
|
||||||
|
let mut read_if_next_available = || {
|
||||||
|
if let Some(next_byte) = buf_iter.next() {
|
||||||
|
*next_byte = self.read_fifo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
let status_reader = self.i2c_base.i2c.status().read();
|
||||||
|
if status_reader.arblost().bit_is_set() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
return Err(Error::ArbitrationLost);
|
||||||
|
} else if status_reader.nackaddr().bit_is_set() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
return Err(Error::NackAddr);
|
||||||
|
} else if status_reader.idle().bit_is_set() {
|
||||||
|
if read_bytes != len {
|
||||||
|
return Err(Error::InsufficientDataReceived);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
} else if status_reader.rxnempty().bit_is_set() {
|
||||||
|
read_if_next_available();
|
||||||
|
read_bytes += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//======================================================================================
|
||||||
|
// Embedded HAL I2C implementations
|
||||||
|
//======================================================================================
|
||||||
|
|
||||||
|
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, SevenBitAddress> {
|
||||||
|
type Error = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance> embedded_hal::i2c::I2c for I2cMaster<I2c, SevenBitAddress> {
|
||||||
|
fn transaction(
|
||||||
|
&mut self,
|
||||||
|
address: SevenBitAddress,
|
||||||
|
operations: &mut [Operation<'_>],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
for operation in operations {
|
||||||
|
match operation {
|
||||||
|
Operation::Read(buf) => self.read_internal(I2cAddress::Regular(address), buf)?,
|
||||||
|
Operation::Write(buf) => self.write_from_buffer(
|
||||||
|
I2cCmd::StartWithStop,
|
||||||
|
I2cAddress::Regular(address),
|
||||||
|
buf,
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, TenBitAddress> {
|
||||||
|
type Error = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance> embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<I2c, TenBitAddress> {
|
||||||
|
fn transaction(
|
||||||
|
&mut self,
|
||||||
|
address: TenBitAddress,
|
||||||
|
operations: &mut [Operation<'_>],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
for operation in operations {
|
||||||
|
match operation {
|
||||||
|
Operation::Read(buf) => self.read_internal(I2cAddress::TenBit(address), buf)?,
|
||||||
|
Operation::Write(buf) => {
|
||||||
|
self.write_from_buffer(I2cCmd::StartWithStop, I2cAddress::TenBit(address), buf)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// I2C Slave
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
|
||||||
|
i2c_base: I2cBase<I2c>,
|
||||||
|
addr: PhantomData<Addr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance, Addr> I2cSlave<I2c, Addr> {
|
||||||
|
fn new_generic(
|
||||||
|
i2c: I2c,
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
cfg: SlaveConfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<Self, ClockTooSlowForFastI2c> {
|
||||||
|
Ok(I2cSlave {
|
||||||
|
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, None, Some(&cfg))?,
|
||||||
|
addr: PhantomData,
|
||||||
|
}
|
||||||
|
.enable_slave())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable_slave(self) -> Self {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.s0_ctrl()
|
||||||
|
.modify(|_, w| w.enable().set_bit());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_slave(self) -> Self {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.s0_ctrl()
|
||||||
|
.modify(|_, w| w.enable().clear_bit());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn load_fifo(&self, word: u8) {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.s0_data()
|
||||||
|
.write(|w| unsafe { w.bits(word as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_fifo(&self) -> u8 {
|
||||||
|
self.i2c_base.i2c.s0_data().read().bits() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn clear_tx_fifo(&self) {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.s0_fifo_clr()
|
||||||
|
.write(|w| w.txfifo().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn clear_rx_fifo(&self) {
|
||||||
|
self.i2c_base
|
||||||
|
.i2c
|
||||||
|
.s0_fifo_clr()
|
||||||
|
.write(|w| w.rxfifo().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the last address that was matched by the slave control and the corresponding
|
||||||
|
/// master direction
|
||||||
|
pub fn last_address(&self) -> (I2cDirection, u32) {
|
||||||
|
let bits = self.i2c_base.i2c.s0_lastaddress().read().bits();
|
||||||
|
match bits & 0x01 {
|
||||||
|
0 => (I2cDirection::Send, bits >> 1),
|
||||||
|
1 => (I2cDirection::Read, bits >> 1),
|
||||||
|
_ => (I2cDirection::Send, bits >> 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, output: &[u8]) -> Result<(), Error> {
|
||||||
|
let len = output.len();
|
||||||
|
// It should theoretically possible to transfer larger data sizes by tracking
|
||||||
|
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||||
|
// bytes are remaining. However, large transfer like this are not common. This
|
||||||
|
// feature will therefore not be supported for now.
|
||||||
|
if len > 0x7fe {
|
||||||
|
return Err(Error::DataTooLarge);
|
||||||
|
}
|
||||||
|
let mut bytes = output.iter();
|
||||||
|
// FIFO has a depth of 16. We load slightly above the trigger level
|
||||||
|
// but not all of it because the transaction might fail immediately
|
||||||
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
|
// load the FIFO
|
||||||
|
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
|
||||||
|
self.load_fifo(*bytes.next().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let status_reader = self.i2c_base.i2c.s0_status().read();
|
||||||
|
let mut load_if_next_available = || {
|
||||||
|
if let Some(next_byte) = bytes.next() {
|
||||||
|
self.load_fifo(*next_byte);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
if status_reader.nackdata().bit_is_set() {
|
||||||
|
self.clear_tx_fifo();
|
||||||
|
return Err(Error::NackData);
|
||||||
|
} else if status_reader.idle().bit_is_set() {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
while !status_reader.txnfull().bit_is_set() {
|
||||||
|
load_if_next_available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
|
let len = buffer.len();
|
||||||
|
// It should theoretically possible to transfer larger data sizes by tracking
|
||||||
|
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||||
|
// bytes are remaining. However, large transfer like this are not common. This
|
||||||
|
// feature will therefore not be supported for now.
|
||||||
|
if len > 0x7fe {
|
||||||
|
return Err(Error::DataTooLarge);
|
||||||
|
}
|
||||||
|
// Clear the receive FIFO
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
|
||||||
|
let mut buf_iter = buffer.iter_mut();
|
||||||
|
let mut read_bytes = 0;
|
||||||
|
let mut read_if_next_available = || {
|
||||||
|
if let Some(next_byte) = buf_iter.next() {
|
||||||
|
*next_byte = self.read_fifo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
let status_reader = self.i2c_base.i2c.s0_status().read();
|
||||||
|
if status_reader.idle().bit_is_set() {
|
||||||
|
if read_bytes != len {
|
||||||
|
return Err(Error::InsufficientDataReceived);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
} else if status_reader.rxnempty().bit_is_set() {
|
||||||
|
read_bytes += 1;
|
||||||
|
read_if_next_available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
|
||||||
|
/// Create a new I2C slave for seven bit addresses
|
||||||
|
pub fn new(
|
||||||
|
i2c: I2c,
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
cfg: SlaveConfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<Self, InitError> {
|
||||||
|
if let I2cAddress::TenBit(_) = cfg.addr {
|
||||||
|
return Err(InitError::WrongAddrMode);
|
||||||
|
}
|
||||||
|
Ok(Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I2c: Instance> I2cSlave<I2c, TenBitAddress> {
|
||||||
|
pub fn new_ten_bit_addr(
|
||||||
|
i2c: I2c,
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
cfg: SlaveConfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<Self, ClockTooSlowForFastI2c> {
|
||||||
|
Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
//! IRQ Router peripheral support.
|
|
||||||
use vorago_shared_periphs::{
|
|
||||||
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::pac;
|
|
||||||
|
|
||||||
/// This enables and initiates the peripheral.
|
|
||||||
///
|
|
||||||
/// Please note that this method also writes 0 to the registers which do not have 0 as the default
|
|
||||||
/// reset value. The programmers guide v1.2 and the actual values inspected using a SVD viewer
|
|
||||||
/// are inconsistent here, and the registers being non-zero can actually lead to weird bugs
|
|
||||||
/// when working with interrupts. Registers DMASELx and ADCSEL/DMASELx will reset to 0x7f and 0x1f
|
|
||||||
/// respectively instead of 0x00.
|
|
||||||
pub fn enable_and_init_irq_router() {
|
|
||||||
let irq_router = unsafe { pac::IrqRouter::steal() };
|
|
||||||
enable_peripheral_clock(PeripheralSelect::IrqRouter);
|
|
||||||
reset_peripheral_for_cycles(PeripheralSelect::IrqRouter, 2);
|
|
||||||
unsafe {
|
|
||||||
irq_router.dmasel0().write_with_zero(|w| w);
|
|
||||||
irq_router.dmasel1().write_with_zero(|w| w);
|
|
||||||
irq_router.dmasel2().write_with_zero(|w| w);
|
|
||||||
irq_router.dmasel3().write_with_zero(|w| w);
|
|
||||||
irq_router.adcsel().write_with_zero(|w| w);
|
|
||||||
irq_router.dacsel0().write_with_zero(|w| w);
|
|
||||||
irq_router.dacsel1().write_with_zero(|w| w);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +1,49 @@
|
|||||||
//! This is the **H**ardware **A**bstraction **L**ayer (HAL) for the VA416xx MCU family.
|
|
||||||
//!
|
|
||||||
//! It is an additional hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx).
|
|
||||||
//!
|
|
||||||
//! It is the result of reading the datasheet for the device and encoding a type-safe layer over the
|
|
||||||
//! raw PAC. This crate also implements traits specified by the
|
|
||||||
//! [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
|
||||||
//! various drivers in the embedded rust ecosystem.
|
|
||||||
//!
|
|
||||||
//! It is generally advised to enable ONE of the following device features to use this crate
|
|
||||||
//! depending on which chip you are using:
|
|
||||||
//!
|
|
||||||
//! - `va41630`
|
|
||||||
//! - `va41629`
|
|
||||||
//! - `va41628`
|
|
||||||
//! - `va41620`
|
|
||||||
//!
|
|
||||||
//! If no option is specified, only access to APIs which are common for all families or
|
|
||||||
//! which are not disabled for specific families is granted.
|
|
||||||
//!
|
|
||||||
//! When using this HAL and writing applications for the VA416xx family in general, it is strongly
|
|
||||||
//! recommended that you set up the clock properly, because the default internal HBO clock
|
|
||||||
//! is not very accurate. You can use the [crate::clock] module for this. If you are working
|
|
||||||
//! with interrupts, it is strongly recommended to set up the IRQ router with the
|
|
||||||
//! [crate::irq_router] module at the very least because that peripheral has confusing and/or
|
|
||||||
//! faulty register reset values which might lead to weird bugs and glitches.
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
extern crate alloc;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
use gpio::Port;
|
|
||||||
pub use va416xx as device;
|
pub use va416xx as device;
|
||||||
pub use va416xx as pac;
|
pub use va416xx as pac;
|
||||||
|
|
||||||
pub mod can;
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod adc;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
|
pub mod dac;
|
||||||
pub mod dma;
|
pub mod dma;
|
||||||
pub mod edac;
|
|
||||||
pub mod gpio;
|
pub mod gpio;
|
||||||
pub mod i2c;
|
pub mod i2c;
|
||||||
pub mod irq_router;
|
|
||||||
pub mod pins;
|
|
||||||
pub mod prelude;
|
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
pub mod spi;
|
pub mod spi;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
|
pub mod typelevel;
|
||||||
pub mod uart;
|
pub mod uart;
|
||||||
pub mod wdt;
|
pub mod wdt;
|
||||||
|
|
||||||
#[cfg(feature = "va41630")]
|
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
|
||||||
pub mod nvm;
|
pub enum FunSel {
|
||||||
|
Sel0 = 0b00,
|
||||||
|
Sel1 = 0b01,
|
||||||
|
Sel2 = 0b10,
|
||||||
|
Sel3 = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
/// Enable a specific interrupt using the NVIC peripheral.
|
||||||
pub mod adc;
|
|
||||||
#[cfg(not(feature = "va41628"))]
|
|
||||||
pub mod dac;
|
|
||||||
|
|
||||||
pub use vorago_shared_periphs::{
|
|
||||||
assert_peripheral_reset, deassert_peripheral_reset, disable_nvic_interrupt,
|
|
||||||
disable_peripheral_clock, enable_nvic_interrupt, enable_peripheral_clock,
|
|
||||||
reset_peripheral_for_cycles, FunSel, PeripheralSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[error("invalid pin with number {0}")]
|
|
||||||
pub struct InvalidPinError(u8);
|
|
||||||
|
|
||||||
/// Can be used to manually manipulate the function select of port pins.
|
|
||||||
///
|
///
|
||||||
/// The function selection table can be found on p.286 of the programmers guide. Please note
|
/// # Safety
|
||||||
/// that most of the structures and APIs in this library will automatically correctly configure
|
///
|
||||||
/// the pin or statically expect the correct pin type.
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn port_function_select(
|
pub unsafe fn enable_interrupt(irq: pac::Interrupt) {
|
||||||
ioconfig: &mut pac::Ioconfig,
|
unsafe {
|
||||||
port: Port,
|
cortex_m::peripheral::NVIC::unmask(irq);
|
||||||
pin: u8,
|
|
||||||
funsel: FunSel,
|
|
||||||
) -> Result<(), InvalidPinError> {
|
|
||||||
if (port == Port::G && pin >= 8) || pin >= 16 {
|
|
||||||
return Err(InvalidPinError(pin));
|
|
||||||
}
|
|
||||||
let reg_block = match port {
|
|
||||||
Port::A => ioconfig.porta(pin as usize),
|
|
||||||
Port::B => ioconfig.portb0(pin as usize),
|
|
||||||
Port::C => ioconfig.portc0(pin as usize),
|
|
||||||
Port::D => ioconfig.portd0(pin as usize),
|
|
||||||
Port::E => ioconfig.porte0(pin as usize),
|
|
||||||
Port::F => ioconfig.portf0(pin as usize),
|
|
||||||
Port::G => ioconfig.portg0(pin as usize),
|
|
||||||
};
|
|
||||||
|
|
||||||
reg_block.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SyscfgExt {
|
|
||||||
fn enable_peripheral_clock(&mut self, clock: PeripheralSelect);
|
|
||||||
|
|
||||||
fn disable_peripheral_clock(&mut self, clock: PeripheralSelect);
|
|
||||||
|
|
||||||
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
|
|
||||||
|
|
||||||
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
|
|
||||||
|
|
||||||
fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SyscfgExt for pac::Sysconfig {
|
|
||||||
#[inline(always)]
|
|
||||||
fn enable_peripheral_clock(&mut self, clock: PeripheralSelect) {
|
|
||||||
enable_peripheral_clock(clock)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn disable_peripheral_clock(&mut self, clock: PeripheralSelect) {
|
|
||||||
disable_peripheral_clock(clock)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
|
|
||||||
assert_peripheral_reset(clock)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
|
|
||||||
deassert_peripheral_reset(clock)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize) {
|
|
||||||
reset_peripheral_for_cycles(periph, cycles)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable a specific interrupt using the NVIC peripheral.
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_interrupt(irq: pac::Interrupt) {
|
||||||
|
cortex_m::peripheral::NVIC::mask(irq);
|
||||||
|
}
|
||||||
|
@ -1,277 +0,0 @@
|
|||||||
//! Non-volatile memory (NVM) driver.
|
|
||||||
//!
|
|
||||||
//! Provides a basic API to work with the internal NVM of the VA41630 MCU.
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//!
|
|
||||||
//! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
use embedded_hal::spi::MODE_0;
|
|
||||||
use vorago_shared_periphs::{
|
|
||||||
disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::clock::Clocks;
|
|
||||||
use crate::pac;
|
|
||||||
use crate::spi::{
|
|
||||||
mode_to_cpo_cph_bit, spi_clk_config_from_div, SpiMarker, WordProvider, BMSTART_BMSTOP_MASK,
|
|
||||||
};
|
|
||||||
|
|
||||||
const NVM_CLOCK_DIV: u16 = 2;
|
|
||||||
|
|
||||||
// Commands. The internal FRAM is based on the Cypress FM25V20A device.
|
|
||||||
|
|
||||||
/// Write enable register.
|
|
||||||
pub const FRAM_WREN: u8 = 0x06;
|
|
||||||
pub const FRAM_WRDI: u8 = 0x04;
|
|
||||||
pub const FRAM_RDSR: u8 = 0x05;
|
|
||||||
/// Write single status register
|
|
||||||
pub const FRAM_WRSR: u8 = 0x01;
|
|
||||||
pub const FRAM_READ: u8 = 0x03;
|
|
||||||
pub const FRAM_WRITE: u8 = 0x02;
|
|
||||||
pub const FRAM_RDID: u8 = 0x9F;
|
|
||||||
pub const FRAM_SLEEP: u8 = 0xB9;
|
|
||||||
|
|
||||||
/* Address Masks */
|
|
||||||
const ADDR_MSB_MASK: u32 = 0xFF0000;
|
|
||||||
const ADDR_MID_MASK: u32 = 0x00FF00;
|
|
||||||
const ADDR_LSB_MASK: u32 = 0x0000FF;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
const fn msb_addr_byte(addr: u32) -> u8 {
|
|
||||||
((addr & ADDR_MSB_MASK) >> 16) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
const fn mid_addr_byte(addr: u32) -> u8 {
|
|
||||||
((addr & ADDR_MID_MASK) >> 8) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
const fn lsb_addr_byte(addr: u32) -> u8 {
|
|
||||||
(addr & ADDR_LSB_MASK) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const WPEN_ENABLE_MASK: u8 = 1 << 7;
|
|
||||||
pub const BP_0_ENABLE_MASK: u8 = 1 << 2;
|
|
||||||
pub const BP_1_ENABLE_MASK: u8 = 1 << 3;
|
|
||||||
|
|
||||||
pub struct Nvm {
|
|
||||||
spi: Option<pac::Spi3>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct VerifyError {
|
|
||||||
addr: u32,
|
|
||||||
found: u8,
|
|
||||||
expected: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Nvm {
|
|
||||||
pub fn new(spi: pac::Spi3, _clocks: &Clocks) -> Self {
|
|
||||||
enable_peripheral_clock(pac::Spi3::PERIPH_SEL);
|
|
||||||
// This is done in the C HAL.
|
|
||||||
reset_peripheral_for_cycles(pac::Spi3::PERIPH_SEL, 2);
|
|
||||||
|
|
||||||
let spi_clk_cfg = spi_clk_config_from_div(NVM_CLOCK_DIV).unwrap();
|
|
||||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
|
|
||||||
spi.ctrl0().write(|w| {
|
|
||||||
unsafe {
|
|
||||||
w.size().bits(u8::word_reg());
|
|
||||||
w.scrdv().bits(spi_clk_cfg.scrdv());
|
|
||||||
// Clear clock phase and polarity. Will be set to correct value for each
|
|
||||||
// transfer
|
|
||||||
w.spo().bit(cpo_bit);
|
|
||||||
w.sph().bit(cph_bit)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
spi.ctrl1().write(|w| {
|
|
||||||
w.blockmode().set_bit();
|
|
||||||
unsafe { w.ss().bits(0) };
|
|
||||||
w.bmstart().set_bit();
|
|
||||||
w.bmstall().set_bit()
|
|
||||||
});
|
|
||||||
spi.clkprescale()
|
|
||||||
.write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val() as u32) });
|
|
||||||
|
|
||||||
spi.fifo_clr().write(|w| {
|
|
||||||
w.rxfifo().set_bit();
|
|
||||||
w.txfifo().set_bit()
|
|
||||||
});
|
|
||||||
// Enable the peripheral as the last step as recommended in the
|
|
||||||
// programmers guide
|
|
||||||
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
|
||||||
|
|
||||||
let mut nvm = Self { spi: Some(spi) };
|
|
||||||
nvm.disable_write_prot();
|
|
||||||
nvm
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_write_prot(&mut self) {
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_with_bmstop(FRAM_WREN);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_single(FRAM_WRSR);
|
|
||||||
self.write_with_bmstop(0x00);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_rdsr(&self) -> u8 {
|
|
||||||
self.write_single(FRAM_RDSR);
|
|
||||||
self.write_with_bmstop(0x00);
|
|
||||||
self.wait_for_rx_available();
|
|
||||||
self.read_single_word();
|
|
||||||
self.wait_for_rx_available();
|
|
||||||
(self.read_single_word() & 0xff) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_write_prot(&mut self) {
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_with_bmstop(FRAM_WREN);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_single(FRAM_WRSR);
|
|
||||||
self.write_with_bmstop(0x00);
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn spi(&self) -> &pac::Spi3 {
|
|
||||||
self.spi.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn write_single(&self, word: u8) {
|
|
||||||
self.spi().data().write(|w| unsafe { w.bits(word as u32) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn write_with_bmstop(&self, word: u8) {
|
|
||||||
self.spi()
|
|
||||||
.data()
|
|
||||||
.write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn wait_for_tx_idle(&self) {
|
|
||||||
while self.spi().status().read().tfe().bit_is_clear() {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
while self.spi().status().read().busy().bit_is_set() {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
self.clear_fifos()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn clear_fifos(&self) {
|
|
||||||
self.spi().fifo_clr().write(|w| {
|
|
||||||
w.rxfifo().set_bit();
|
|
||||||
w.txfifo().set_bit()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn wait_for_rx_available(&self) {
|
|
||||||
while !self.spi().status().read().rne().bit_is_set() {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_single_word(&self) -> u32 {
|
|
||||||
self.spi().data().read().bits()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_data(&self, addr: u32, data: &[u8]) {
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_with_bmstop(FRAM_WREN);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_single(FRAM_WRITE);
|
|
||||||
self.write_single(msb_addr_byte(addr));
|
|
||||||
self.write_single(mid_addr_byte(addr));
|
|
||||||
self.write_single(lsb_addr_byte(addr));
|
|
||||||
for byte in data.iter().take(data.len() - 1) {
|
|
||||||
while self.spi().status().read().tnf().bit_is_clear() {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
self.write_single(*byte);
|
|
||||||
self.read_single_word();
|
|
||||||
}
|
|
||||||
while self.spi().status().read().tnf().bit_is_clear() {
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
self.write_with_bmstop(*data.last().unwrap());
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_data(&self, addr: u32, buf: &mut [u8]) {
|
|
||||||
self.common_read_start(addr);
|
|
||||||
for byte in buf {
|
|
||||||
// Pump the SPI.
|
|
||||||
self.write_single(0);
|
|
||||||
self.wait_for_rx_available();
|
|
||||||
*byte = self.read_single_word() as u8;
|
|
||||||
}
|
|
||||||
self.write_with_bmstop(0);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_data(&self, addr: u32, comp_buf: &[u8]) -> Result<(), VerifyError> {
|
|
||||||
self.common_read_start(addr);
|
|
||||||
for (idx, byte) in comp_buf.iter().enumerate() {
|
|
||||||
// Pump the SPI.
|
|
||||||
self.write_single(0);
|
|
||||||
self.wait_for_rx_available();
|
|
||||||
let next_word = self.read_single_word() as u8;
|
|
||||||
if next_word != *byte {
|
|
||||||
self.write_with_bmstop(0);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
return Err(VerifyError {
|
|
||||||
addr: addr + idx as u32,
|
|
||||||
found: next_word,
|
|
||||||
expected: *byte,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.write_with_bmstop(0);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable write-protection and disables the peripheral clock.
|
|
||||||
pub fn shutdown(&mut self) {
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_with_bmstop(FRAM_WREN);
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK);
|
|
||||||
disable_peripheral_clock(pac::Spi3::PERIPH_SEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function calls [Self::shutdown] and gives back the peripheral structure.
|
|
||||||
pub fn release(mut self) -> pac::Spi3 {
|
|
||||||
self.shutdown();
|
|
||||||
self.spi.take().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn common_read_start(&self, addr: u32) {
|
|
||||||
self.wait_for_tx_idle();
|
|
||||||
self.write_single(FRAM_READ);
|
|
||||||
self.write_single(msb_addr_byte(addr));
|
|
||||||
self.write_single(mid_addr_byte(addr));
|
|
||||||
self.write_single(lsb_addr_byte(addr));
|
|
||||||
for _ in 0..4 {
|
|
||||||
// Pump the SPI.
|
|
||||||
self.write_single(0);
|
|
||||||
self.wait_for_rx_available();
|
|
||||||
// The first 4 data bytes received need to be ignored.
|
|
||||||
self.read_single_word();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call [Self::shutdown] on drop.
|
|
||||||
impl Drop for Nvm {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.spi.is_some() {
|
|
||||||
self.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
//! Pin resource management singletons.
|
|
||||||
//!
|
|
||||||
//! This module contains the pin singletons. It allows creating those singletons
|
|
||||||
//! to access the [Pin] structures of individual ports in a safe way with checked ownership
|
|
||||||
//! rules.
|
|
||||||
pub use vorago_shared_periphs::pins::*;
|
|
@ -1,4 +1,4 @@
|
|||||||
//! Prelude
|
//! Prelude
|
||||||
pub use crate::clock::ClkgenExt;
|
pub use crate::clock::{ClkgenExt, SyscfgExt};
|
||||||
pub use fugit::ExtU32 as _;
|
pub use fugit::ExtU32 as _;
|
||||||
pub use fugit::RateExtU32 as _;
|
pub use fugit::RateExtU32 as _;
|
||||||
|
@ -1,8 +1,388 @@
|
|||||||
//! API for Pulse-Width Modulation (PWM)
|
//! API for Pulse-Width Modulation (PWM)
|
||||||
//!
|
//!
|
||||||
//! The Vorago devices use the TIM peripherals to perform PWM related tasks
|
//! The Vorago VA416xx devices use the TIM peripherals to perform PWM related tasks.
|
||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
|
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
|
||||||
pub use vorago_shared_periphs::pwm::*;
|
use core::convert::Infallible;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::pac;
|
||||||
|
use crate::{clock::Clocks, gpio::DynPinId};
|
||||||
|
pub use crate::{gpio::PinId, time::Hertz, timer::*};
|
||||||
|
|
||||||
|
const DUTY_MAX: u16 = u16::MAX;
|
||||||
|
|
||||||
|
pub struct PwmBase {
|
||||||
|
clock: Hertz,
|
||||||
|
/// For PWMB, this is the upper limit
|
||||||
|
current_duty: u16,
|
||||||
|
/// For PWMA, this value will not be used
|
||||||
|
current_lower_limit: u16,
|
||||||
|
current_period: Hertz,
|
||||||
|
current_rst_val: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StatusSelPwm {
|
||||||
|
PwmA = 3,
|
||||||
|
PwmB = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PwmA {}
|
||||||
|
pub struct PwmB {}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Common
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
macro_rules! pwm_common_func {
|
||||||
|
() => {
|
||||||
|
#[inline]
|
||||||
|
fn enable_pwm_a(&mut self) {
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable_pwm_b(&mut self) {
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_period(&self) -> Hertz {
|
||||||
|
self.pwm_base.current_period
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_period(&mut self, period: impl Into<Hertz>) {
|
||||||
|
self.pwm_base.current_period = period.into();
|
||||||
|
// Avoid division by 0
|
||||||
|
if self.pwm_base.current_period.raw() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.pwm_base.current_rst_val =
|
||||||
|
self.pwm_base.clock.raw() / self.pwm_base.current_period.raw();
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.rst_value()
|
||||||
|
.write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.reg.reg().ctrl().modify(|_, w| w.enable().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.reg.reg().ctrl().modify(|_, w| w.enable().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn period(&self) -> Hertz {
|
||||||
|
self.pwm_base.current_period
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn duty(&self) -> u16 {
|
||||||
|
self.pwm_base.current_duty
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pwmb_func {
|
||||||
|
() => {
|
||||||
|
pub fn pwmb_lower_limit(&self) -> u16 {
|
||||||
|
self.pwm_base.current_lower_limit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pwmb_upper_limit(&self) -> u16 {
|
||||||
|
self.pwm_base.current_duty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the lower limit for PWMB
|
||||||
|
///
|
||||||
|
/// The PWM signal will be 1 as long as the current RST counter is larger than
|
||||||
|
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||||
|
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||||
|
/// state
|
||||||
|
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
|
||||||
|
self.pwm_base.current_lower_limit = duty;
|
||||||
|
let pwmb_val: u64 = (self.pwm_base.current_rst_val as u64
|
||||||
|
* self.pwm_base.current_lower_limit as u64)
|
||||||
|
/ DUTY_MAX as u64;
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.pwmb_value()
|
||||||
|
.write(|w| unsafe { w.bits(pwmb_val as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the higher limit for PWMB
|
||||||
|
///
|
||||||
|
/// The PWM signal will be 1 as long as the current RST counter is smaller than
|
||||||
|
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||||
|
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||||
|
/// state
|
||||||
|
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
|
||||||
|
self.pwm_base.current_duty = duty;
|
||||||
|
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
|
||||||
|
* self.pwm_base.current_duty as u64)
|
||||||
|
/ DUTY_MAX as u64;
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.pwma_value()
|
||||||
|
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Strongly typed PWM pin
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct PwmPin<Pin: TimPin, Tim: ValidTim, Mode = PwmA> {
|
||||||
|
reg: TimAndPinRegister<Pin, Tim>,
|
||||||
|
pwm_base: PwmBase,
|
||||||
|
mode: PhantomData<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim, Mode> PwmPin<Pin, Tim, Mode>
|
||||||
|
where
|
||||||
|
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
|
||||||
|
{
|
||||||
|
/// Create a new stronlgy typed PWM pin
|
||||||
|
pub fn new(
|
||||||
|
pin_and_tim: (Pin, Tim),
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
initial_period: impl Into<Hertz> + Copy,
|
||||||
|
) -> Self {
|
||||||
|
let mut pin = PwmPin {
|
||||||
|
pwm_base: PwmBase {
|
||||||
|
current_duty: 0,
|
||||||
|
current_lower_limit: 0,
|
||||||
|
current_period: initial_period.into(),
|
||||||
|
current_rst_val: 0,
|
||||||
|
clock: Tim::clock(clocks),
|
||||||
|
},
|
||||||
|
reg: unsafe { TimAndPinRegister::new(pin_and_tim.0, pin_and_tim.1) },
|
||||||
|
mode: PhantomData,
|
||||||
|
};
|
||||||
|
sys_cfg
|
||||||
|
.tim_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | pin.reg.mask_32()) });
|
||||||
|
pin.enable_pwm_a();
|
||||||
|
pin.set_period(initial_period);
|
||||||
|
pin
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self) -> (Pin, Tim) {
|
||||||
|
self.reg.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
pwm_common_func!();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for PwmPin<Pin, Tim, PwmB>
|
||||||
|
where
|
||||||
|
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
|
||||||
|
{
|
||||||
|
fn from(other: PwmPin<Pin, Tim, PwmA>) -> Self {
|
||||||
|
let mut pwmb = Self {
|
||||||
|
reg: other.reg,
|
||||||
|
pwm_base: other.pwm_base,
|
||||||
|
mode: PhantomData,
|
||||||
|
};
|
||||||
|
pwmb.enable_pwm_b();
|
||||||
|
pwmb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PwmB>> for PwmPin<PIN, TIM, PwmA>
|
||||||
|
where
|
||||||
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
||||||
|
{
|
||||||
|
fn from(other: PwmPin<PIN, TIM, PwmB>) -> Self {
|
||||||
|
let mut pwmb = Self {
|
||||||
|
reg: other.reg,
|
||||||
|
pwm_base: other.pwm_base,
|
||||||
|
mode: PhantomData,
|
||||||
|
};
|
||||||
|
pwmb.enable_pwm_a();
|
||||||
|
pwmb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmA>
|
||||||
|
where
|
||||||
|
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
|
||||||
|
{
|
||||||
|
pub fn pwma(
|
||||||
|
tim_and_pin: (Pin, Tim),
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
initial_period: impl Into<Hertz> + Copy,
|
||||||
|
) -> Self {
|
||||||
|
let mut pin: PwmPin<Pin, Tim, PwmA> =
|
||||||
|
Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
|
||||||
|
pin.enable_pwm_a();
|
||||||
|
pin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
|
||||||
|
where
|
||||||
|
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
|
||||||
|
{
|
||||||
|
pub fn pwmb(
|
||||||
|
tim_and_pin: (Pin, Tim),
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
clocks: &Clocks,
|
||||||
|
initial_period: impl Into<Hertz> + Copy,
|
||||||
|
) -> Self {
|
||||||
|
let mut pin: PwmPin<Pin, Tim, PwmB> =
|
||||||
|
Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
|
||||||
|
pin.enable_pwm_b();
|
||||||
|
pin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Reduced PWM pin
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Reduced version where type information is deleted
|
||||||
|
pub struct ReducedPwmPin<Mode = PwmA> {
|
||||||
|
reg: TimDynRegister,
|
||||||
|
pwm_base: PwmBase,
|
||||||
|
pin_id: DynPinId,
|
||||||
|
mode: PhantomData<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM>> for ReducedPwmPin<PwmA> {
|
||||||
|
fn from(pwm_pin: PwmPin<PIN, TIM>) -> Self {
|
||||||
|
ReducedPwmPin {
|
||||||
|
reg: TimDynRegister::from(pwm_pin.reg),
|
||||||
|
pwm_base: pwm_pin.pwm_base,
|
||||||
|
pin_id: PIN::DYN,
|
||||||
|
mode: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MODE> ReducedPwmPin<MODE> {
|
||||||
|
pwm_common_func!();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<PwmB> {
|
||||||
|
fn from(other: ReducedPwmPin<PwmA>) -> Self {
|
||||||
|
let mut pwmb = Self {
|
||||||
|
reg: other.reg,
|
||||||
|
pwm_base: other.pwm_base,
|
||||||
|
pin_id: other.pin_id,
|
||||||
|
mode: PhantomData,
|
||||||
|
};
|
||||||
|
pwmb.enable_pwm_b();
|
||||||
|
pwmb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReducedPwmPin<PwmB>> for ReducedPwmPin<PwmA> {
|
||||||
|
fn from(other: ReducedPwmPin<PwmB>) -> Self {
|
||||||
|
let mut pwmb = Self {
|
||||||
|
reg: other.reg,
|
||||||
|
pwm_base: other.pwm_base,
|
||||||
|
pin_id: other.pin_id,
|
||||||
|
mode: PhantomData,
|
||||||
|
};
|
||||||
|
pwmb.enable_pwm_a();
|
||||||
|
pwmb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// PWMB implementations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
impl<PIN: TimPin, TIM: ValidTim> PwmPin<PIN, TIM, PwmB>
|
||||||
|
where
|
||||||
|
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
|
||||||
|
{
|
||||||
|
pwmb_func!();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReducedPwmPin<PwmB> {
|
||||||
|
pwmb_func!();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Embedded HAL implementation: PWMA only
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::ErrorType for PwmPin<Pin, Tim> {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::pwm::ErrorType for ReducedPwmPin {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
|
||||||
|
#[inline]
|
||||||
|
fn max_duty_cycle(&self) -> u16 {
|
||||||
|
DUTY_MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||||
|
self.pwm_base.current_duty = duty;
|
||||||
|
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
|
||||||
|
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
|
||||||
|
/ DUTY_MAX as u64;
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.pwma_value()
|
||||||
|
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin, Tim> {
|
||||||
|
#[inline]
|
||||||
|
fn max_duty_cycle(&self) -> u16 {
|
||||||
|
DUTY_MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||||
|
self.pwm_base.current_duty = duty;
|
||||||
|
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
|
||||||
|
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
|
||||||
|
/ DUTY_MAX as u64;
|
||||||
|
self.reg
|
||||||
|
.reg()
|
||||||
|
.pwma_value()
|
||||||
|
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
|
||||||
|
///
|
||||||
|
/// Please note that this might load a lot of floating point code because this processor does not
|
||||||
|
/// have a FPU
|
||||||
|
pub fn get_duty_from_percent(percent: f32) -> u16 {
|
||||||
|
if percent > 1.0 {
|
||||||
|
DUTY_MAX
|
||||||
|
} else if percent <= 0.0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(percent * DUTY_MAX as f32) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,11 +1,891 @@
|
|||||||
//! API for the SPI peripheral.
|
//! API for the SPI peripheral
|
||||||
//!
|
|
||||||
//! The main abstraction provided by this module is the [Spi] an structure.
|
|
||||||
//! It provides the [SpiBus trait](https://docs.rs/embedded-hal/latest/embedded_hal/spi/trait.SpiBus.html),
|
|
||||||
//! but also offer a low level interface via the [SpiLowLevel] trait.
|
|
||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs)
|
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs)
|
||||||
//! - [NVM library][crate::nvm]
|
use core::{convert::Infallible, marker::PhantomData, ops::Deref};
|
||||||
pub use vorago_shared_periphs::spi::*;
|
|
||||||
|
use embedded_hal::spi::Mode;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
clock::{PeripheralSelect, SyscfgExt},
|
||||||
|
gpio::{
|
||||||
|
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
|
||||||
|
PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1,
|
||||||
|
PC10, PC11, PC7, PC8, PC9, PE10, PE11, PE12, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9,
|
||||||
|
PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PG2, PG3, PG4,
|
||||||
|
},
|
||||||
|
pac,
|
||||||
|
time::Hertz,
|
||||||
|
typelevel::{NoneT, Sealed},
|
||||||
|
};
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Defintions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
// FIFO has a depth of 16.
|
||||||
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum HwChipSelectId {
|
||||||
|
Id0 = 0,
|
||||||
|
Id1 = 1,
|
||||||
|
Id2 = 2,
|
||||||
|
Id3 = 3,
|
||||||
|
Id4 = 4,
|
||||||
|
Id5 = 5,
|
||||||
|
Id6 = 6,
|
||||||
|
Id7 = 7,
|
||||||
|
Invalid = 0xff,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SpiId {
|
||||||
|
Spi0,
|
||||||
|
Spi1,
|
||||||
|
Spi2,
|
||||||
|
Spi3,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum WordSize {
|
||||||
|
OneBit = 0x00,
|
||||||
|
FourBits = 0x03,
|
||||||
|
EightBits = 0x07,
|
||||||
|
SixteenBits = 0x0f,
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Pin type definitions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait PinSck<SPI>: Sealed {}
|
||||||
|
pub trait PinMosi<SPI>: Sealed {}
|
||||||
|
pub trait PinMiso<SPI>: Sealed {}
|
||||||
|
|
||||||
|
pub trait HwCsProvider: Sealed {
|
||||||
|
const CS_ID: HwChipSelectId;
|
||||||
|
const SPI_ID: SpiId;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OptionalHwCs<Spi>: HwCsProvider + Sealed {}
|
||||||
|
|
||||||
|
macro_rules! hw_cs_pins {
|
||||||
|
($SPIx:path, $portId: path:
|
||||||
|
$(
|
||||||
|
($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident),
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
impl HwCsProvider for Pin<$PXx, $AFx> {
|
||||||
|
const CS_ID: HwChipSelectId = $HwCsIdent;
|
||||||
|
const SPI_ID: SpiId = $portId;
|
||||||
|
}
|
||||||
|
impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
|
||||||
|
pub type $typedef = Pin<$PXx, $AFx>;
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HwCsProvider for NoneT {
|
||||||
|
const CS_ID: HwChipSelectId = HwChipSelectId::Invalid;
|
||||||
|
const SPI_ID: SpiId = SpiId::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionalHwCs<pac::Spi0> for NoneT {}
|
||||||
|
impl OptionalHwCs<pac::Spi1> for NoneT {}
|
||||||
|
impl OptionalHwCs<pac::Spi2> for NoneT {}
|
||||||
|
impl OptionalHwCs<pac::Spi3> for NoneT {}
|
||||||
|
|
||||||
|
// SPI 0
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
|
||||||
|
impl PinMosi<pac::Spi0> for Pin<PC1, AltFunc1> {}
|
||||||
|
impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
|
||||||
|
|
||||||
|
// SPI 1
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
|
||||||
|
impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
|
||||||
|
impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
|
||||||
|
impl PinMosi<pac::Spi1> for Pin<PC11, AltFunc2> {}
|
||||||
|
impl PinMiso<pac::Spi1> for Pin<PC10, AltFunc2> {}
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi1> for Pin<PG3, AltFunc2> {}
|
||||||
|
impl PinMiso<pac::Spi1> for Pin<PG4, AltFunc2> {}
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi1> for Pin<PE13, AltFunc2> {}
|
||||||
|
impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
|
||||||
|
impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
|
||||||
|
impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
|
||||||
|
impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
|
||||||
|
|
||||||
|
// SPI 2
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi2> for Pin<PA5, AltFunc2> {}
|
||||||
|
impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
|
||||||
|
impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
|
||||||
|
|
||||||
|
impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
|
||||||
|
impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
|
||||||
|
impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
|
||||||
|
|
||||||
|
// SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
|
||||||
|
|
||||||
|
// SPI 0 HW CS pins
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
pac::Spi0, SpiId::Spi0:
|
||||||
|
(PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
|
||||||
|
(PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
|
||||||
|
(PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
|
||||||
|
(PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0),
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
pac::Spi1, SpiId::Spi1:
|
||||||
|
(PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb),
|
||||||
|
(PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb),
|
||||||
|
(PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb),
|
||||||
|
(PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
|
||||||
|
(PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
|
||||||
|
(PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
|
||||||
|
(PB1, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pb),
|
||||||
|
(PB0, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pb),
|
||||||
|
(PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
|
||||||
|
(PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
|
||||||
|
(PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
|
||||||
|
(PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe),
|
||||||
|
(PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe),
|
||||||
|
(PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
|
||||||
|
(PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
|
||||||
|
(PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
|
||||||
|
(PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
|
||||||
|
(PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
|
||||||
|
(PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf),
|
||||||
|
(PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
pac::Spi2, SpiId::Spi2:
|
||||||
|
(PA4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pa),
|
||||||
|
(PA3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pa),
|
||||||
|
(PA2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pa),
|
||||||
|
(PA1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pa),
|
||||||
|
(PA0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pa),
|
||||||
|
(PA8, AltFunc2, HwChipSelectId::Id6, HwCs6Spi2Pa),
|
||||||
|
(PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
|
||||||
|
(PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
|
||||||
|
(PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
|
||||||
|
(PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf),
|
||||||
|
(PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf),
|
||||||
|
(PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf),
|
||||||
|
);
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Config
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait TransferConfigProvider {
|
||||||
|
fn sod(&mut self, sod: bool);
|
||||||
|
fn blockmode(&mut self, blockmode: bool);
|
||||||
|
fn mode(&mut self, mode: Mode);
|
||||||
|
fn frequency(&mut self, spi_clk: Hertz);
|
||||||
|
fn hw_cs_id(&self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct contains all configuration parameter which are transfer specific
|
||||||
|
/// and might change for transfers to different SPI slaves
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct TransferConfig<HwCs> {
|
||||||
|
pub spi_clk: Hertz,
|
||||||
|
pub mode: Mode,
|
||||||
|
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
|
||||||
|
/// false
|
||||||
|
pub hw_cs: Option<HwCs>,
|
||||||
|
pub sod: bool,
|
||||||
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
|
/// duration of multiple data words
|
||||||
|
pub blockmode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||||
|
/// the SPI constructor.
|
||||||
|
pub struct ErasedTransferConfig {
|
||||||
|
pub spi_clk: Hertz,
|
||||||
|
pub mode: Mode,
|
||||||
|
pub sod: bool,
|
||||||
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
|
/// duration of multiple data words
|
||||||
|
pub blockmode: bool,
|
||||||
|
pub hw_cs: HwChipSelectId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransferConfig<NoneT> {
|
||||||
|
pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self {
|
||||||
|
TransferConfig {
|
||||||
|
spi_clk: spi_clk.into(),
|
||||||
|
mode,
|
||||||
|
hw_cs: None,
|
||||||
|
sod,
|
||||||
|
blockmode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
|
||||||
|
pub fn new(
|
||||||
|
spi_clk: impl Into<Hertz>,
|
||||||
|
mode: Mode,
|
||||||
|
hw_cs: Option<HwCs>,
|
||||||
|
blockmode: bool,
|
||||||
|
sod: bool,
|
||||||
|
) -> Self {
|
||||||
|
TransferConfig {
|
||||||
|
spi_clk: spi_clk.into(),
|
||||||
|
mode,
|
||||||
|
hw_cs,
|
||||||
|
sod,
|
||||||
|
blockmode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downgrade(self) -> ErasedTransferConfig {
|
||||||
|
ErasedTransferConfig {
|
||||||
|
spi_clk: self.spi_clk,
|
||||||
|
mode: self.mode,
|
||||||
|
sod: self.sod,
|
||||||
|
blockmode: self.blockmode,
|
||||||
|
hw_cs: HwCs::CS_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
|
||||||
|
/// Slave Output Disable
|
||||||
|
fn sod(&mut self, sod: bool) {
|
||||||
|
self.sod = sod;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blockmode(&mut self, blockmode: bool) {
|
||||||
|
self.blockmode = blockmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mode(&mut self, mode: Mode) {
|
||||||
|
self.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frequency(&mut self, spi_clk: Hertz) {
|
||||||
|
self.spi_clk = spi_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hw_cs_id(&self) -> u8 {
|
||||||
|
HwCs::CS_ID as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||||
|
pub struct SpiConfig {
|
||||||
|
/// Serial clock rate divider. Together with the CLKPRESCALE register, it determines
|
||||||
|
/// the SPI clock rate in master mode. 0 by default. Specifying a higher value
|
||||||
|
/// limits the maximum attainable SPI speed
|
||||||
|
pub ser_clock_rate_div: u8,
|
||||||
|
/// By default, configure SPI for master mode (ms == false)
|
||||||
|
ms: bool,
|
||||||
|
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
||||||
|
pub slave_output_disable: bool,
|
||||||
|
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
|
||||||
|
pub loopback_mode: bool,
|
||||||
|
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
|
||||||
|
pub master_delayer_capture: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiConfig {
|
||||||
|
pub fn loopback(mut self, enable: bool) -> Self {
|
||||||
|
self.loopback_mode = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn master_mode(mut self, master: bool) -> Self {
|
||||||
|
self.ms = !master;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slave_output_disable(mut self, sod: bool) -> Self {
|
||||||
|
self.slave_output_disable = sod;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Word Size
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Configuration trait for the Word Size used by the SPI peripheral
|
||||||
|
pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
|
||||||
|
const MASK: u32;
|
||||||
|
fn word_reg() -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WordProvider for u8 {
|
||||||
|
const MASK: u32 = 0xff;
|
||||||
|
fn word_reg() -> u8 {
|
||||||
|
0x07
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WordProvider for u16 {
|
||||||
|
const MASK: u32 = 0xffff;
|
||||||
|
fn word_reg() -> u8 {
|
||||||
|
0x0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SpiRegBlock = pac::spi0::RegisterBlock;
|
||||||
|
|
||||||
|
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||||
|
/// format is the same for all SPI blocks.
|
||||||
|
pub trait Instance: Deref<Target = SpiRegBlock> {
|
||||||
|
const IDX: u8;
|
||||||
|
const PERIPH_SEL: PeripheralSelect;
|
||||||
|
|
||||||
|
fn ptr() -> *const SpiRegBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for pac::Spi0 {
|
||||||
|
const IDX: u8 = 0;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const SpiRegBlock {
|
||||||
|
Self::ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for pac::Spi1 {
|
||||||
|
const IDX: u8 = 1;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const SpiRegBlock {
|
||||||
|
Self::ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for pac::Spi2 {
|
||||||
|
const IDX: u8 = 2;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const SpiRegBlock {
|
||||||
|
Self::ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Spi
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct SpiBase<SpiInstance, Word = u8> {
|
||||||
|
spi: SpiInstance,
|
||||||
|
cfg: SpiConfig,
|
||||||
|
apb1_clk: Hertz,
|
||||||
|
/// Fill word for read-only SPI transactions.
|
||||||
|
pub fill_word: Word,
|
||||||
|
blockmode: bool,
|
||||||
|
word: PhantomData<Word>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Spi<SpiInstance, Pins, Word = u8> {
|
||||||
|
inner: SpiBase<SpiInstance, Word>,
|
||||||
|
pins: Pins,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
||||||
|
match mode {
|
||||||
|
embedded_hal::spi::MODE_0 => (false, false),
|
||||||
|
embedded_hal::spi::MODE_1 => (false, true),
|
||||||
|
embedded_hal::spi::MODE_2 => (true, false),
|
||||||
|
embedded_hal::spi::MODE_3 => (true, true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
||||||
|
let clk_prescale =
|
||||||
|
self.apb1_clk.raw() / (spi_clk.into().raw() * (self.cfg.ser_clock_rate_div as u32 + 1));
|
||||||
|
self.spi
|
||||||
|
.clkprescale()
|
||||||
|
.write(|w| unsafe { w.bits(clk_prescale) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||||
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
||||||
|
self.spi.ctrl0().modify(|_, w| {
|
||||||
|
w.spo().bit(cpo_bit);
|
||||||
|
w.sph().bit(cph_bit)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_tx_fifo(&self) {
|
||||||
|
self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_rx_fifo(&self) {
|
||||||
|
self.spi.fifo_clr().write(|w| w.rxfifo().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32 {
|
||||||
|
self.spi.perid().read().bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
|
||||||
|
if hw_cs == HwChipSelectId::Invalid {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.spi.ctrl1().modify(|_, w| {
|
||||||
|
w.sod().clear_bit();
|
||||||
|
unsafe {
|
||||||
|
w.ss().bits(hw_cs as u8);
|
||||||
|
}
|
||||||
|
w
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_hw_cs_with_pin<HwCs: OptionalHwCs<SpiInstance>>(&mut self, _: &HwCs) {
|
||||||
|
self.cfg_hw_cs(HwCs::CS_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_hw_cs_disable(&mut self) {
|
||||||
|
self.spi.ctrl1().modify(|_, w| {
|
||||||
|
w.sod().set_bit();
|
||||||
|
w
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
|
||||||
|
&mut self,
|
||||||
|
transfer_cfg: &TransferConfig<HwCs>,
|
||||||
|
) {
|
||||||
|
self.cfg_clock(transfer_cfg.spi_clk);
|
||||||
|
self.cfg_mode(transfer_cfg.mode);
|
||||||
|
self.blockmode = transfer_cfg.blockmode;
|
||||||
|
self.spi.ctrl1().modify(|_, w| {
|
||||||
|
if transfer_cfg.sod {
|
||||||
|
w.sod().set_bit();
|
||||||
|
} else if transfer_cfg.hw_cs.is_some() {
|
||||||
|
w.sod().clear_bit();
|
||||||
|
unsafe {
|
||||||
|
w.ss().bits(HwCs::CS_ID as u8);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.sod().clear_bit();
|
||||||
|
}
|
||||||
|
if transfer_cfg.blockmode {
|
||||||
|
w.blockmode().set_bit();
|
||||||
|
} else {
|
||||||
|
w.blockmode().clear_bit();
|
||||||
|
}
|
||||||
|
w
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a word to the slave
|
||||||
|
#[inline(always)]
|
||||||
|
fn send_blocking(&self, word: Word) {
|
||||||
|
// TODO: Upper limit for wait cycles to avoid complete hangups?
|
||||||
|
while self.spi.status().read().tnf().bit_is_clear() {}
|
||||||
|
self.send(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn send(&self, word: Word) {
|
||||||
|
self.spi.data().write(|w| unsafe { w.bits(word.into()) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_blocking(&self) -> Word {
|
||||||
|
// TODO: Upper limit for wait cycles to avoid complete hangups?
|
||||||
|
while self.spi.status().read().rne().bit_is_clear() {}
|
||||||
|
self.read_single_word()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_single_word(&self) -> Word {
|
||||||
|
(self.spi.data().read().bits() & Word::MASK)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
|
||||||
|
if words.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut status_reg = self.spi.status().read();
|
||||||
|
// Wait until all bytes have been transferred.
|
||||||
|
while status_reg.tfe().bit_is_clear() {
|
||||||
|
// Ignore all received read words.
|
||||||
|
if status_reg.rne().bit_is_set() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
}
|
||||||
|
status_reg = self.spi.status().read();
|
||||||
|
}
|
||||||
|
// Ignore all received read words.
|
||||||
|
if status_reg.rne().bit_is_set() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize {
|
||||||
|
if self.blockmode {
|
||||||
|
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
|
||||||
|
}
|
||||||
|
// Fill the first half of the write FIFO
|
||||||
|
let mut current_write_idx = 0;
|
||||||
|
for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) {
|
||||||
|
self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx]));
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if self.blockmode {
|
||||||
|
self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit())
|
||||||
|
}
|
||||||
|
current_write_idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
SpiI: Instance,
|
||||||
|
Sck: PinSck<SpiI>,
|
||||||
|
Miso: PinMiso<SpiI>,
|
||||||
|
Mosi: PinMosi<SpiI>,
|
||||||
|
Word: WordProvider,
|
||||||
|
> Spi<SpiI, (Sck, Miso, Mosi), Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
/// Create a new SPI struct
|
||||||
|
///
|
||||||
|
/// You can delete the pin type information by calling the
|
||||||
|
/// [`downgrade`](Self::downgrade) function
|
||||||
|
///
|
||||||
|
/// ## Arguments
|
||||||
|
/// * `spi` - SPI bus to use
|
||||||
|
/// * `pins` - Pins to be used for SPI transactions. These pins are consumed
|
||||||
|
/// to ensure the pins can not be used for other purposes anymore
|
||||||
|
/// * `spi_cfg` - Configuration specific to the SPI bus
|
||||||
|
/// * `transfer_cfg` - Optional initial transfer configuration which includes
|
||||||
|
/// configuration which can change across individual SPI transfers like SPI mode
|
||||||
|
/// or SPI clock. If only one device is connected, this configuration only needs
|
||||||
|
/// to be done once.
|
||||||
|
/// * `syscfg` - Can be passed optionally to enable the peripheral clock
|
||||||
|
pub fn new(
|
||||||
|
spi: SpiI,
|
||||||
|
pins: (Sck, Miso, Mosi),
|
||||||
|
clocks: &crate::clock::Clocks,
|
||||||
|
spi_cfg: SpiConfig,
|
||||||
|
syscfg: &mut pac::Sysconfig,
|
||||||
|
transfer_cfg: Option<&ErasedTransferConfig>,
|
||||||
|
) -> Self {
|
||||||
|
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
|
||||||
|
// This is done in the C HAL.
|
||||||
|
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
|
||||||
|
let SpiConfig {
|
||||||
|
ser_clock_rate_div,
|
||||||
|
ms,
|
||||||
|
slave_output_disable,
|
||||||
|
loopback_mode,
|
||||||
|
master_delayer_capture,
|
||||||
|
} = spi_cfg;
|
||||||
|
let mut mode = embedded_hal::spi::MODE_0;
|
||||||
|
let mut clk_prescale = 0x02;
|
||||||
|
let mut ss = 0;
|
||||||
|
let mut init_blockmode = false;
|
||||||
|
let apb1_clk = clocks.apb1();
|
||||||
|
if let Some(transfer_cfg) = transfer_cfg {
|
||||||
|
mode = transfer_cfg.mode;
|
||||||
|
clk_prescale =
|
||||||
|
apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1));
|
||||||
|
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
|
||||||
|
ss = transfer_cfg.hw_cs as u8;
|
||||||
|
}
|
||||||
|
init_blockmode = transfer_cfg.blockmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
||||||
|
spi.ctrl0().write(|w| {
|
||||||
|
unsafe {
|
||||||
|
w.size().bits(Word::word_reg());
|
||||||
|
w.scrdv().bits(ser_clock_rate_div);
|
||||||
|
// Clear clock phase and polarity. Will be set to correct value for each
|
||||||
|
// transfer
|
||||||
|
w.spo().bit(cpo_bit);
|
||||||
|
w.sph().bit(cph_bit)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spi.ctrl1().write(|w| {
|
||||||
|
w.lbm().bit(loopback_mode);
|
||||||
|
w.sod().bit(slave_output_disable);
|
||||||
|
w.ms().bit(ms);
|
||||||
|
w.mdlycap().bit(master_delayer_capture);
|
||||||
|
w.blockmode().bit(init_blockmode);
|
||||||
|
unsafe { w.ss().bits(ss) }
|
||||||
|
});
|
||||||
|
|
||||||
|
spi.fifo_clr().write(|w| {
|
||||||
|
w.rxfifo().set_bit();
|
||||||
|
w.txfifo().set_bit()
|
||||||
|
});
|
||||||
|
spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) });
|
||||||
|
// Enable the peripheral as the last step as recommended in the
|
||||||
|
// programmers guide
|
||||||
|
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
||||||
|
Spi {
|
||||||
|
inner: SpiBase {
|
||||||
|
spi,
|
||||||
|
cfg: spi_cfg,
|
||||||
|
apb1_clk,
|
||||||
|
fill_word: Default::default(),
|
||||||
|
blockmode: init_blockmode,
|
||||||
|
word: PhantomData,
|
||||||
|
},
|
||||||
|
pins,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
|
||||||
|
self.inner.cfg_clock(spi_clk);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||||
|
self.inner.cfg_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_fill_word(&mut self, fill_word: Word) {
|
||||||
|
self.inner.fill_word = fill_word;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_word(&self) -> Word {
|
||||||
|
self.inner.fill_word
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32 {
|
||||||
|
self.inner.perid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(&mut self, transfer_cfg: &TransferConfig<HwCs>) {
|
||||||
|
self.inner.cfg_transfer(transfer_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases the SPI peripheral and associated pins
|
||||||
|
pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
|
||||||
|
(self.inner.spi, self.pins, self.inner.cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downgrade(self) -> SpiBase<SpiI, Word> {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changing the word size also requires a type conversion
|
||||||
|
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
|
||||||
|
From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16>
|
||||||
|
{
|
||||||
|
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self {
|
||||||
|
old_spi
|
||||||
|
.inner
|
||||||
|
.spi
|
||||||
|
.ctrl0()
|
||||||
|
.modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
|
||||||
|
Spi {
|
||||||
|
inner: SpiBase {
|
||||||
|
spi: old_spi.inner.spi,
|
||||||
|
cfg: old_spi.inner.cfg,
|
||||||
|
blockmode: old_spi.inner.blockmode,
|
||||||
|
fill_word: Default::default(),
|
||||||
|
apb1_clk: old_spi.inner.apb1_clk,
|
||||||
|
word: PhantomData,
|
||||||
|
},
|
||||||
|
pins: old_spi.pins,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changing the word size also requires a type conversion
|
||||||
|
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
|
||||||
|
From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
|
||||||
|
{
|
||||||
|
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
|
||||||
|
old_spi
|
||||||
|
.inner
|
||||||
|
.spi
|
||||||
|
.ctrl0()
|
||||||
|
.modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
|
||||||
|
Spi {
|
||||||
|
inner: SpiBase {
|
||||||
|
spi: old_spi.inner.spi,
|
||||||
|
cfg: old_spi.inner.cfg,
|
||||||
|
blockmode: old_spi.inner.blockmode,
|
||||||
|
apb1_clk: old_spi.inner.apb1_clk,
|
||||||
|
fill_word: Default::default(),
|
||||||
|
word: PhantomData,
|
||||||
|
},
|
||||||
|
pins: old_spi.pins,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(words)?;
|
||||||
|
let mut current_read_idx = 0;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping(None);
|
||||||
|
loop {
|
||||||
|
if current_write_idx < words.len() {
|
||||||
|
self.send_blocking(self.fill_word);
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx < words.len() {
|
||||||
|
words[current_read_idx] = self.read_blocking();
|
||||||
|
current_read_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx >= words.len() && current_write_idx >= words.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(words)?;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
|
||||||
|
while current_write_idx < words.len() {
|
||||||
|
self.send_blocking(words[current_write_idx]);
|
||||||
|
current_write_idx += 1;
|
||||||
|
// Ignore received words.
|
||||||
|
if self.spi.status().read().rne().bit_is_set() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(write)?;
|
||||||
|
let mut current_read_idx = 0;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping(Some(write));
|
||||||
|
while current_read_idx < read.len() || current_write_idx < write.len() {
|
||||||
|
if current_write_idx < write.len() {
|
||||||
|
self.send_blocking(write[current_write_idx]);
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx < read.len() {
|
||||||
|
read[current_read_idx] = self.read_blocking();
|
||||||
|
current_read_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(words)?;
|
||||||
|
let mut current_read_idx = 0;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
|
||||||
|
|
||||||
|
while current_read_idx < words.len() || current_write_idx < words.len() {
|
||||||
|
if current_write_idx < words.len() {
|
||||||
|
self.send_blocking(words[current_write_idx]);
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx < words.len() && current_read_idx < current_write_idx {
|
||||||
|
words[current_read_idx] = self.read_blocking();
|
||||||
|
current_read_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
let status_reg = self.spi.status().read();
|
||||||
|
while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() {
|
||||||
|
if status_reg.rne().bit_is_set() {
|
||||||
|
self.read_single_word();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
SpiI: Instance,
|
||||||
|
Word: WordProvider,
|
||||||
|
Sck: PinSck<SpiI>,
|
||||||
|
Miso: PinMiso<SpiI>,
|
||||||
|
Mosi: PinMosi<SpiI>,
|
||||||
|
> embedded_hal::spi::ErrorType for Spi<SpiI, (Sck, Miso, Mosi), Word>
|
||||||
|
{
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
SpiI: Instance,
|
||||||
|
Word: WordProvider,
|
||||||
|
Sck: PinSck<SpiI>,
|
||||||
|
Miso: PinMiso<SpiI>,
|
||||||
|
Mosi: PinMosi<SpiI>,
|
||||||
|
> embedded_hal::spi::SpiBus<Word> for Spi<SpiI, (Sck, Miso, Mosi), Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
|
self.inner.read(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
||||||
|
self.inner.write(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||||
|
self.inner.transfer(read, write)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
|
self.inner.transfer_in_place(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.inner.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,8 +2,804 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
|
//! TODO.
|
||||||
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
|
use core::cell::Cell;
|
||||||
pub use vorago_shared_periphs::timer::*;
|
|
||||||
|
|
||||||
pub const TIM_IRQ_OFFSET: usize = 48;
|
use cortex_m::interrupt::Mutex;
|
||||||
|
|
||||||
|
use crate::clock::Clocks;
|
||||||
|
use crate::gpio::{
|
||||||
|
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
|
||||||
|
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3,
|
||||||
|
PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, PD0, PD1, PD10, PD11, PD12, PD13, PD14, PD15, PD2, PD3,
|
||||||
|
PD4, PD5, PD6, PD7, PD8, PD9, PE0, PE1, PE10, PE11, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5,
|
||||||
|
PE6, PE7, PE8, PE9, PF0, PF1, PF10, PF11, PF12, PF13, PF14, PF15, PF2, PF3, PF4, PF5, PF6, PF7,
|
||||||
|
PF8, PF9, PG0, PG1, PG2, PG3, PG6,
|
||||||
|
};
|
||||||
|
use crate::time::Hertz;
|
||||||
|
use crate::typelevel::Sealed;
|
||||||
|
use crate::{disable_interrupt, prelude::*};
|
||||||
|
use crate::{enable_interrupt, pac};
|
||||||
|
|
||||||
|
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Defintions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Interrupt events
|
||||||
|
//pub enum Event {
|
||||||
|
/// Timer timed out / count down ended
|
||||||
|
//TimeOut,
|
||||||
|
//}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub struct CascadeCtrl {
|
||||||
|
/// Enable Cascade 0 signal active as a requirement for counting
|
||||||
|
pub enb_start_src_csd0: bool,
|
||||||
|
/// Invert Cascade 0, making it active low
|
||||||
|
pub inv_csd0: bool,
|
||||||
|
/// Enable Cascade 1 signal active as a requirement for counting
|
||||||
|
pub enb_start_src_csd1: bool,
|
||||||
|
/// Invert Cascade 1, making it active low
|
||||||
|
pub inv_csd1: bool,
|
||||||
|
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
|
||||||
|
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
|
||||||
|
pub dual_csd_op: bool,
|
||||||
|
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
|
||||||
|
/// cascade signal active, but once the counter is active, cascade control will be ignored
|
||||||
|
pub trg_csd0: bool,
|
||||||
|
/// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1
|
||||||
|
pub trg_csd1: bool,
|
||||||
|
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
|
||||||
|
/// to the REQ_STOP control bit, but signalled by a Cascade source
|
||||||
|
pub enb_stop_src_csd2: bool,
|
||||||
|
/// Invert Cascade 2, making it active low
|
||||||
|
pub inv_csd2: bool,
|
||||||
|
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
|
||||||
|
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
|
||||||
|
/// ignored
|
||||||
|
pub trg_csd2: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum CascadeSel {
|
||||||
|
Sel0 = 0,
|
||||||
|
Sel1 = 1,
|
||||||
|
Sel2 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct InvalidCascadeSourceId;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum CascadeSource {
|
||||||
|
PortA(u8),
|
||||||
|
PortB(u8),
|
||||||
|
PortC(u8),
|
||||||
|
PortD(u8),
|
||||||
|
PortE(u8),
|
||||||
|
Tim(u8),
|
||||||
|
TxEv,
|
||||||
|
AdcIrq,
|
||||||
|
RomSbe,
|
||||||
|
RomMbe,
|
||||||
|
Ram0Sbe,
|
||||||
|
Ram0Mbe,
|
||||||
|
Ram1Sbe,
|
||||||
|
Ram2Mbe,
|
||||||
|
WdogIrq,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CascadeSource {
|
||||||
|
fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
|
||||||
|
let port_check = |base: u8, id: u8| {
|
||||||
|
if id > 15 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
Ok(base + id)
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
CascadeSource::PortA(id) => port_check(0, *id),
|
||||||
|
CascadeSource::PortB(id) => port_check(16, *id),
|
||||||
|
CascadeSource::PortC(id) => port_check(32, *id),
|
||||||
|
CascadeSource::PortD(id) => port_check(48, *id),
|
||||||
|
CascadeSource::PortE(id) => port_check(65, *id),
|
||||||
|
CascadeSource::Tim(id) => {
|
||||||
|
if *id > 23 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
Ok(80 + id)
|
||||||
|
}
|
||||||
|
CascadeSource::TxEv => Ok(104),
|
||||||
|
CascadeSource::AdcIrq => Ok(105),
|
||||||
|
CascadeSource::RomSbe => Ok(106),
|
||||||
|
CascadeSource::RomMbe => Ok(106),
|
||||||
|
CascadeSource::Ram0Sbe => Ok(108),
|
||||||
|
CascadeSource::Ram0Mbe => Ok(109),
|
||||||
|
CascadeSource::Ram1Sbe => Ok(110),
|
||||||
|
CascadeSource::Ram2Mbe => Ok(111),
|
||||||
|
CascadeSource::WdogIrq => Ok(112),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Valid TIM and PIN combinations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait TimPin {
|
||||||
|
const DYN: DynPinId;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ValidTim {
|
||||||
|
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
|
||||||
|
const TIM_ID: u8;
|
||||||
|
const IRQ: pac::Interrupt;
|
||||||
|
|
||||||
|
fn clock(clocks: &Clocks) -> Hertz {
|
||||||
|
if Self::TIM_ID <= 15 {
|
||||||
|
clocks.apb1()
|
||||||
|
} else {
|
||||||
|
clocks.apb2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tim_markers {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
($TimX:path, $id:expr, $Irq:path),
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
impl ValidTim for $TimX {
|
||||||
|
const TIM_ID: u8 = $id;
|
||||||
|
const IRQ: pac::Interrupt = $Irq;
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tim_markers!(
|
||||||
|
(pac::Tim0, 0, pac::Interrupt::TIM0),
|
||||||
|
(pac::Tim1, 1, pac::Interrupt::TIM1),
|
||||||
|
(pac::Tim2, 2, pac::Interrupt::TIM2),
|
||||||
|
(pac::Tim3, 3, pac::Interrupt::TIM3),
|
||||||
|
(pac::Tim4, 4, pac::Interrupt::TIM4),
|
||||||
|
(pac::Tim5, 5, pac::Interrupt::TIM5),
|
||||||
|
(pac::Tim6, 6, pac::Interrupt::TIM6),
|
||||||
|
(pac::Tim7, 7, pac::Interrupt::TIM7),
|
||||||
|
(pac::Tim8, 8, pac::Interrupt::TIM8),
|
||||||
|
(pac::Tim9, 9, pac::Interrupt::TIM9),
|
||||||
|
(pac::Tim10, 10, pac::Interrupt::TIM10),
|
||||||
|
(pac::Tim11, 11, pac::Interrupt::TIM11),
|
||||||
|
(pac::Tim12, 12, pac::Interrupt::TIM12),
|
||||||
|
(pac::Tim13, 13, pac::Interrupt::TIM13),
|
||||||
|
(pac::Tim14, 14, pac::Interrupt::TIM14),
|
||||||
|
(pac::Tim15, 15, pac::Interrupt::TIM15),
|
||||||
|
(pac::Tim16, 16, pac::Interrupt::TIM16),
|
||||||
|
(pac::Tim17, 17, pac::Interrupt::TIM17),
|
||||||
|
(pac::Tim18, 18, pac::Interrupt::TIM18),
|
||||||
|
(pac::Tim19, 19, pac::Interrupt::TIM19),
|
||||||
|
(pac::Tim20, 20, pac::Interrupt::TIM20),
|
||||||
|
(pac::Tim21, 21, pac::Interrupt::TIM21),
|
||||||
|
(pac::Tim22, 22, pac::Interrupt::TIM22),
|
||||||
|
(pac::Tim23, 23, pac::Interrupt::TIM23),
|
||||||
|
);
|
||||||
|
|
||||||
|
pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
|
||||||
|
|
||||||
|
macro_rules! valid_pin_and_tims {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
($PinX:ident, $AltFunc:ident, $TimX:path),
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
impl TimPin for Pin<$PinX, $AltFunc>
|
||||||
|
where
|
||||||
|
$PinX: PinId,
|
||||||
|
{
|
||||||
|
const DYN: DynPinId = $PinX::DYN;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
PinInstance: TimPin,
|
||||||
|
Tim: ValidTim
|
||||||
|
> ValidTimAndPin<PinInstance, Tim> for (Pin<$PinX, $AltFunc>, $TimX)
|
||||||
|
where
|
||||||
|
Pin<$PinX, $AltFunc>: TimPin,
|
||||||
|
$PinX: PinId,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_pin_and_tims!(
|
||||||
|
(PA0, AltFunc1, pac::Tim0),
|
||||||
|
(PA1, AltFunc1, pac::Tim1),
|
||||||
|
(PA2, AltFunc1, pac::Tim2),
|
||||||
|
(PA3, AltFunc1, pac::Tim3),
|
||||||
|
(PA4, AltFunc1, pac::Tim4),
|
||||||
|
(PA5, AltFunc1, pac::Tim5),
|
||||||
|
(PA6, AltFunc1, pac::Tim6),
|
||||||
|
(PA7, AltFunc1, pac::Tim7),
|
||||||
|
(PA10, AltFunc2, pac::Tim23),
|
||||||
|
(PA11, AltFunc2, pac::Tim22),
|
||||||
|
(PA12, AltFunc2, pac::Tim21),
|
||||||
|
(PA13, AltFunc2, pac::Tim20),
|
||||||
|
(PA14, AltFunc2, pac::Tim19),
|
||||||
|
(PA15, AltFunc2, pac::Tim18),
|
||||||
|
(PB0, AltFunc2, pac::Tim17),
|
||||||
|
(PB1, AltFunc2, pac::Tim16),
|
||||||
|
(PB2, AltFunc2, pac::Tim15),
|
||||||
|
(PB3, AltFunc2, pac::Tim14),
|
||||||
|
(PB4, AltFunc2, pac::Tim13),
|
||||||
|
(PB5, AltFunc2, pac::Tim12),
|
||||||
|
(PB6, AltFunc2, pac::Tim11),
|
||||||
|
(PB7, AltFunc2, pac::Tim10),
|
||||||
|
(PB8, AltFunc2, pac::Tim9),
|
||||||
|
(PB9, AltFunc2, pac::Tim8),
|
||||||
|
(PB10, AltFunc2, pac::Tim7),
|
||||||
|
(PB11, AltFunc2, pac::Tim6),
|
||||||
|
(PB12, AltFunc2, pac::Tim5),
|
||||||
|
(PB13, AltFunc2, pac::Tim4),
|
||||||
|
(PB14, AltFunc2, pac::Tim3),
|
||||||
|
(PB15, AltFunc2, pac::Tim2),
|
||||||
|
(PC0, AltFunc2, pac::Tim1),
|
||||||
|
(PC1, AltFunc2, pac::Tim0),
|
||||||
|
(PD0, AltFunc2, pac::Tim0),
|
||||||
|
(PD1, AltFunc2, pac::Tim1),
|
||||||
|
(PD2, AltFunc2, pac::Tim2),
|
||||||
|
(PD3, AltFunc2, pac::Tim3),
|
||||||
|
(PD4, AltFunc2, pac::Tim4),
|
||||||
|
(PD5, AltFunc2, pac::Tim5),
|
||||||
|
(PD6, AltFunc2, pac::Tim6),
|
||||||
|
(PD7, AltFunc2, pac::Tim7),
|
||||||
|
(PD8, AltFunc2, pac::Tim8),
|
||||||
|
(PD9, AltFunc2, pac::Tim9),
|
||||||
|
(PD10, AltFunc2, pac::Tim10),
|
||||||
|
(PD11, AltFunc2, pac::Tim11),
|
||||||
|
(PD12, AltFunc2, pac::Tim12),
|
||||||
|
(PD13, AltFunc2, pac::Tim13),
|
||||||
|
(PD14, AltFunc2, pac::Tim14),
|
||||||
|
(PD15, AltFunc2, pac::Tim15),
|
||||||
|
(PE0, AltFunc2, pac::Tim16),
|
||||||
|
(PE1, AltFunc2, pac::Tim17),
|
||||||
|
(PE2, AltFunc2, pac::Tim18),
|
||||||
|
(PE3, AltFunc2, pac::Tim19),
|
||||||
|
(PE4, AltFunc2, pac::Tim20),
|
||||||
|
(PE5, AltFunc2, pac::Tim21),
|
||||||
|
(PE6, AltFunc2, pac::Tim22),
|
||||||
|
(PE7, AltFunc2, pac::Tim23),
|
||||||
|
(PE8, AltFunc3, pac::Tim16),
|
||||||
|
(PE9, AltFunc3, pac::Tim17),
|
||||||
|
(PE10, AltFunc3, pac::Tim18),
|
||||||
|
(PE11, AltFunc3, pac::Tim19),
|
||||||
|
(PE12, AltFunc3, pac::Tim20),
|
||||||
|
(PE13, AltFunc3, pac::Tim21),
|
||||||
|
(PE14, AltFunc3, pac::Tim22),
|
||||||
|
(PE15, AltFunc3, pac::Tim23),
|
||||||
|
(PF0, AltFunc3, pac::Tim0),
|
||||||
|
(PF1, AltFunc3, pac::Tim1),
|
||||||
|
(PF2, AltFunc3, pac::Tim2),
|
||||||
|
(PF3, AltFunc3, pac::Tim3),
|
||||||
|
(PF4, AltFunc3, pac::Tim4),
|
||||||
|
(PF5, AltFunc3, pac::Tim5),
|
||||||
|
(PF6, AltFunc3, pac::Tim6),
|
||||||
|
(PF7, AltFunc3, pac::Tim7),
|
||||||
|
(PF8, AltFunc3, pac::Tim8),
|
||||||
|
(PF9, AltFunc3, pac::Tim9),
|
||||||
|
(PF10, AltFunc3, pac::Tim10),
|
||||||
|
(PF11, AltFunc3, pac::Tim11),
|
||||||
|
(PF12, AltFunc3, pac::Tim12),
|
||||||
|
(PF13, AltFunc2, pac::Tim19),
|
||||||
|
(PF14, AltFunc2, pac::Tim20),
|
||||||
|
(PF15, AltFunc2, pac::Tim21),
|
||||||
|
(PG0, AltFunc2, pac::Tim22),
|
||||||
|
(PG1, AltFunc2, pac::Tim23),
|
||||||
|
(PG2, AltFunc1, pac::Tim9),
|
||||||
|
(PG3, AltFunc1, pac::Tim10),
|
||||||
|
(PG6, AltFunc1, pac::Tim12),
|
||||||
|
);
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Register Interface for TIM registers and TIM pins
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Clear the reset bit of the TIM, holding it in reset
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Only the bit related to the corresponding TIM peripheral is modified
|
||||||
|
#[inline]
|
||||||
|
fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
||||||
|
syscfg
|
||||||
|
.tim_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
|
||||||
|
syscfg
|
||||||
|
.tim_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TimRegBlock = pac::tim0::RegisterBlock;
|
||||||
|
|
||||||
|
/// Register interface.
|
||||||
|
///
|
||||||
|
/// This interface provides valid TIM pins a way to access their corresponding TIM
|
||||||
|
/// registers
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Users should only implement the [`tim_id`] function. No default function
|
||||||
|
/// implementations should be overridden. The implementing type must also have
|
||||||
|
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
|
||||||
|
/// pin ID is a singleton.
|
||||||
|
pub(super) unsafe trait TimRegInterface {
|
||||||
|
fn tim_id(&self) -> u8;
|
||||||
|
|
||||||
|
const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
|
||||||
|
|
||||||
|
/// All 24 TIM blocks are identical. This helper functions returns the correct
|
||||||
|
/// memory mapped peripheral depending on the TIM ID.
|
||||||
|
#[inline(always)]
|
||||||
|
fn reg(&self) -> &TimRegBlock {
|
||||||
|
unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn mask_32(&self) -> u32 {
|
||||||
|
1 << self.tim_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the reset bit of the TIM, holding it in reset
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Only the bit related to the corresponding TIM peripheral is modified
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn assert_tim_reset(&self, syscfg: &mut pac::Sysconfig) {
|
||||||
|
assert_tim_reset(syscfg, self.tim_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn deassert_time_reset(&self, syscfg: &mut pac::Sysconfig) {
|
||||||
|
deassert_tim_reset(syscfg, self.tim_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a safe register interface for [`ValidTimAndPin`]s
|
||||||
|
///
|
||||||
|
/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to
|
||||||
|
/// access the corresponding registers.
|
||||||
|
pub(super) struct TimAndPinRegister<Pin: TimPin, Tim: ValidTim> {
|
||||||
|
pin: Pin,
|
||||||
|
tim: Tim,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct TimRegister<TIM: ValidTim> {
|
||||||
|
tim: TIM,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TIM: ValidTim> TimRegister<TIM> {
|
||||||
|
#[inline]
|
||||||
|
pub(super) unsafe fn new(tim: TIM) -> Self {
|
||||||
|
TimRegister { tim }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn release(self) -> TIM {
|
||||||
|
self.tim
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<Tim: ValidTim> TimRegInterface for TimRegister<Tim> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn tim_id(&self) -> u8 {
|
||||||
|
Tim::TIM_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> TimAndPinRegister<Pin, Tim>
|
||||||
|
where
|
||||||
|
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
pub(super) unsafe fn new(pin: Pin, tim: Tim) -> Self {
|
||||||
|
TimAndPinRegister { pin, tim }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn release(self) -> (Pin, Tim) {
|
||||||
|
(self.pin, self.tim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<Pin: TimPin, Tim: ValidTim> TimRegInterface for TimAndPinRegister<Pin, Tim> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn tim_id(&self) -> u8 {
|
||||||
|
Tim::TIM_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct TimDynRegister {
|
||||||
|
tim_id: u8,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pin_id: DynPinId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pin: TimPin, Tim: ValidTim> From<TimAndPinRegister<Pin, Tim>> for TimDynRegister {
|
||||||
|
fn from(_reg: TimAndPinRegister<Pin, Tim>) -> Self {
|
||||||
|
Self {
|
||||||
|
tim_id: Tim::TIM_ID,
|
||||||
|
pin_id: Pin::DYN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl TimRegInterface for TimDynRegister {
|
||||||
|
#[inline(always)]
|
||||||
|
fn tim_id(&self) -> u8 {
|
||||||
|
self.tim_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Timers
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Hardware timers
|
||||||
|
pub struct CountdownTimer<TIM: ValidTim> {
|
||||||
|
tim: TimRegister<TIM>,
|
||||||
|
curr_freq: Hertz,
|
||||||
|
clock: Hertz,
|
||||||
|
rst_val: u32,
|
||||||
|
last_cnt: u32,
|
||||||
|
listening: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
|
||||||
|
syscfg
|
||||||
|
.tim_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<TIM: ValidTim> TimRegInterface for CountdownTimer<TIM> {
|
||||||
|
#[inline]
|
||||||
|
fn tim_id(&self) -> u8 {
|
||||||
|
TIM::TIM_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Tim: ValidTim> CountdownTimer<Tim> {
|
||||||
|
/// Create a new countdown timer, but does not start it.
|
||||||
|
///
|
||||||
|
/// You can use [Self::start] to start the countdown timer, and you may optionally call
|
||||||
|
/// [Self::listen] to enable interrupts for the TIM peripheral as well.
|
||||||
|
pub fn new(syscfg: &mut pac::Sysconfig, tim: Tim, clocks: &Clocks) -> Self {
|
||||||
|
enable_tim_clk(syscfg, Tim::TIM_ID);
|
||||||
|
assert_tim_reset(syscfg, Tim::TIM_ID);
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
deassert_tim_reset(syscfg, Tim::TIM_ID);
|
||||||
|
|
||||||
|
CountdownTimer {
|
||||||
|
tim: unsafe { TimRegister::new(tim) },
|
||||||
|
clock: Tim::clock(clocks),
|
||||||
|
rst_val: 0,
|
||||||
|
curr_freq: 0_u32.Hz(),
|
||||||
|
listening: false,
|
||||||
|
last_cnt: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn start(&mut self, timeout: impl Into<Hertz>) {
|
||||||
|
self.load(timeout);
|
||||||
|
self.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the
|
||||||
|
/// IRQSEL peripheral for the provided interrupt and unmasks the interrupt
|
||||||
|
#[inline]
|
||||||
|
pub fn listen(&mut self) {
|
||||||
|
self.listening = true;
|
||||||
|
self.enable_interrupt();
|
||||||
|
unsafe { enable_interrupt(Tim::IRQ) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
|
||||||
|
/// flag and restart the time if configured correctly
|
||||||
|
pub fn wait(&mut self) -> nb::Result<(), void::Void> {
|
||||||
|
let cnt = self.tim.reg().cnt_value().read().bits();
|
||||||
|
if (cnt > self.last_cnt) || cnt == 0 {
|
||||||
|
self.last_cnt = self.rst_val;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
self.last_cnt = cnt;
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn unlisten(&mut self) {
|
||||||
|
self.listening = true;
|
||||||
|
self.disable_interrupt();
|
||||||
|
disable_interrupt(Tim::IRQ);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_interrupt(&mut self) {
|
||||||
|
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable_interrupt(&mut self) {
|
||||||
|
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
|
||||||
|
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
|
||||||
|
syscfg
|
||||||
|
.tim_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) });
|
||||||
|
self.tim.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the count down timer with a timeout but do not start it.
|
||||||
|
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
||||||
|
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
|
||||||
|
self.curr_freq = timeout.into();
|
||||||
|
self.rst_val = self.clock.raw() / self.curr_freq.raw();
|
||||||
|
self.set_reload(self.rst_val);
|
||||||
|
self.set_count(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_reload(&mut self, val: u32) {
|
||||||
|
self.tim.reg().rst_value().write(|w| unsafe { w.bits(val) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_count(&mut self, val: u32) {
|
||||||
|
self.tim.reg().cnt_value().write(|w| unsafe { w.bits(val) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn count(&self) -> u32 {
|
||||||
|
self.tim.reg().cnt_value().read().bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable the counter, setting both enable and active bit to 0
|
||||||
|
#[inline]
|
||||||
|
pub fn auto_disable(self, enable: bool) -> Self {
|
||||||
|
if enable {
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| w.auto_disable().set_bit());
|
||||||
|
} else {
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| w.auto_disable().clear_bit());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This option only applies when the Auto-Disable functionality is 0.
|
||||||
|
///
|
||||||
|
/// The active bit is changed to 0 when count reaches 0, but the counter stays
|
||||||
|
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
|
||||||
|
#[inline]
|
||||||
|
pub fn auto_deactivate(self, enable: bool) -> Self {
|
||||||
|
if enable {
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| w.auto_deactivate().set_bit());
|
||||||
|
} else {
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.ctrl()
|
||||||
|
.modify(|_, w| w.auto_deactivate().clear_bit());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the cascade parameters
|
||||||
|
#[inline]
|
||||||
|
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
|
||||||
|
self.tim.reg().csd_ctrl().write(|w| {
|
||||||
|
w.csden0().bit(ctrl.enb_start_src_csd0);
|
||||||
|
w.csdinv0().bit(ctrl.inv_csd0);
|
||||||
|
w.csden1().bit(ctrl.enb_start_src_csd1);
|
||||||
|
w.csdinv1().bit(ctrl.inv_csd1);
|
||||||
|
w.dcasop().bit(ctrl.dual_csd_op);
|
||||||
|
w.csdtrg0().bit(ctrl.trg_csd0);
|
||||||
|
w.csdtrg1().bit(ctrl.trg_csd1);
|
||||||
|
w.csden2().bit(ctrl.enb_stop_src_csd2);
|
||||||
|
w.csdinv2().bit(ctrl.inv_csd2);
|
||||||
|
w.csdtrg2().bit(ctrl.trg_csd2)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
|
||||||
|
let id = src.id()?;
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.cascade0()
|
||||||
|
.write(|w| unsafe { w.cassel().bits(id) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
|
||||||
|
let id = src.id()?;
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.cascade1()
|
||||||
|
.write(|w| unsafe { w.cassel().bits(id) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
|
||||||
|
let id = src.id()?;
|
||||||
|
self.tim
|
||||||
|
.reg()
|
||||||
|
.cascade2()
|
||||||
|
.write(|w| unsafe { w.cassel().bits(id) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn curr_freq(&self) -> Hertz {
|
||||||
|
self.curr_freq
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn listening(&self) -> bool {
|
||||||
|
self.listening
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Tim: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<Tim> {
|
||||||
|
fn delay_ns(&mut self, ns: u32) {
|
||||||
|
let ticks = (u64::from(ns)) * (u64::from(self.clock.raw())) / 1_000_000_000;
|
||||||
|
|
||||||
|
let full_cycles = ticks >> 32;
|
||||||
|
let mut last_count;
|
||||||
|
let mut new_count;
|
||||||
|
if full_cycles > 0 {
|
||||||
|
self.set_reload(u32::MAX);
|
||||||
|
self.set_count(u32::MAX);
|
||||||
|
self.enable();
|
||||||
|
|
||||||
|
for _ in 0..full_cycles {
|
||||||
|
// Always ensure that both values are the same at the start.
|
||||||
|
new_count = self.count();
|
||||||
|
last_count = new_count;
|
||||||
|
loop {
|
||||||
|
new_count = self.count();
|
||||||
|
if new_count == 0 {
|
||||||
|
// Wait till timer has wrapped.
|
||||||
|
while self.count() == 0 {
|
||||||
|
cortex_m::asm::nop()
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Timer has definitely wrapped.
|
||||||
|
if new_count > last_count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_count = new_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ticks = (ticks & u32::MAX as u64) as u32;
|
||||||
|
self.disable();
|
||||||
|
if ticks > 1 {
|
||||||
|
self.set_reload(ticks);
|
||||||
|
self.set_count(ticks);
|
||||||
|
self.enable();
|
||||||
|
last_count = ticks;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
new_count = self.count();
|
||||||
|
if new_count == 0 || (new_count > last_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_count = new_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// MS tick implementations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
// Set up a millisecond timer on TIM0. Please note that the user still has to provide an IRQ handler
|
||||||
|
// which should call [default_ms_irq_handler].
|
||||||
|
pub fn set_up_ms_tick<Tim: ValidTim>(
|
||||||
|
sys_cfg: &mut pac::Sysconfig,
|
||||||
|
tim: Tim,
|
||||||
|
clocks: &Clocks,
|
||||||
|
) -> CountdownTimer<Tim> {
|
||||||
|
let mut ms_timer = CountdownTimer::new(sys_cfg, tim, clocks);
|
||||||
|
ms_timer.listen();
|
||||||
|
ms_timer.start(1000.Hz());
|
||||||
|
ms_timer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function can be called in a specified interrupt handler to increment
|
||||||
|
/// the MS counter
|
||||||
|
pub fn default_ms_irq_handler() {
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
let mut ms = MS_COUNTER.borrow(cs).get();
|
||||||
|
ms += 1;
|
||||||
|
MS_COUNTER.borrow(cs).set(ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current MS tick count
|
||||||
|
pub fn get_ms_ticks() -> u32 {
|
||||||
|
cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);
|
||||||
|
|
||||||
|
impl<Tim: ValidTim> DelayMs<Tim> {
|
||||||
|
pub fn new(timer: CountdownTimer<Tim>) -> Option<Self> {
|
||||||
|
if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self(timer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This assumes that the user has already set up a MS tick timer with [set_up_ms_tick]
|
||||||
|
impl embedded_hal::delay::DelayNs for DelayMs {
|
||||||
|
fn delay_ns(&mut self, ns: u32) {
|
||||||
|
let ns_as_ms = ns / 1_000_000;
|
||||||
|
if self.0.curr_freq() != Hertz::from_raw(1000) || !self.0.listening() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let start_time = get_ms_ticks();
|
||||||
|
while get_ms_ticks() - start_time < ns_as_ms {
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
155
va416xx-hal/src/typelevel.rs
Normal file
155
va416xx-hal/src/typelevel.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
//! Module supporting type-level programming
|
||||||
|
//!
|
||||||
|
//! This module is identical to the
|
||||||
|
//! [atsamd typelevel](https://docs.rs/atsamd-hal/latest/atsamd_hal/typelevel/index.html).
|
||||||
|
|
||||||
|
use core::ops::{Add, Sub};
|
||||||
|
|
||||||
|
use typenum::{Add1, Bit, Sub1, UInt, Unsigned, B1, U0};
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
/// Super trait used to mark traits with an exhaustive set of
|
||||||
|
/// implementations
|
||||||
|
pub trait Sealed {}
|
||||||
|
|
||||||
|
impl Sealed for u8 {}
|
||||||
|
impl Sealed for i8 {}
|
||||||
|
impl Sealed for u16 {}
|
||||||
|
impl Sealed for i16 {}
|
||||||
|
impl Sealed for u32 {}
|
||||||
|
impl Sealed for i32 {}
|
||||||
|
impl Sealed for f32 {}
|
||||||
|
|
||||||
|
/// Mapping from an instance of a countable type to its successor
|
||||||
|
pub trait Increment {
|
||||||
|
/// Successor type of `Self`
|
||||||
|
type Inc;
|
||||||
|
/// Consume an instance of `Self` and return its successor
|
||||||
|
fn inc(self) -> Self::Inc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mapping from an instance of a countable type to its predecessor
|
||||||
|
pub trait Decrement {
|
||||||
|
/// Predecessor type of `Self`
|
||||||
|
type Dec;
|
||||||
|
/// Consume an instance of `Self` and return its predecessor
|
||||||
|
fn dec(self) -> Self::Dec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use private::Decrement as PrivateDecrement;
|
||||||
|
pub(crate) use private::Increment as PrivateIncrement;
|
||||||
|
pub(crate) use private::Sealed;
|
||||||
|
|
||||||
|
/// Type-level version of the [`None`] variant
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoneT;
|
||||||
|
|
||||||
|
impl Sealed for NoneT {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Is
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/// Marker trait for type identity
|
||||||
|
///
|
||||||
|
/// This trait is used as part of the [`AnyKind`] trait pattern. It represents
|
||||||
|
/// the concept of type identity, because all implementors have
|
||||||
|
/// `<Self as Is>::Type == Self`. When used as a trait bound with a specific
|
||||||
|
/// type, it guarantees that the corresponding type parameter is exactly the
|
||||||
|
/// specific type. Stated differently, it guarantees that `T == Specific` in
|
||||||
|
/// the following example.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// where T: Is<Type = Specific>
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Moreover, the super traits guarantee that any instance of or reference to a
|
||||||
|
/// type `T` can be converted into the `Specific` type.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// fn example<T>(mut any: T)
|
||||||
|
/// where
|
||||||
|
/// T: Is<Type = Specific>,
|
||||||
|
/// {
|
||||||
|
/// let specific_mut: &mut Specific = any.as_mut();
|
||||||
|
/// let specific_ref: &Specific = any.as_ref();
|
||||||
|
/// let specific: Specific = any.into();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`AnyKind`]: #anykind-trait-pattern
|
||||||
|
pub trait Is
|
||||||
|
where
|
||||||
|
Self: Sealed,
|
||||||
|
Self: From<IsType<Self>>,
|
||||||
|
Self: Into<IsType<Self>>,
|
||||||
|
Self: AsRef<IsType<Self>>,
|
||||||
|
Self: AsMut<IsType<Self>>,
|
||||||
|
{
|
||||||
|
type Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type alias for [`Is::Type`]
|
||||||
|
pub type IsType<T> = <T as Is>::Type;
|
||||||
|
|
||||||
|
impl<T> Is for T
|
||||||
|
where
|
||||||
|
T: Sealed + AsRef<T> + AsMut<T>,
|
||||||
|
{
|
||||||
|
type Type = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Counting
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/// Implement `Sealed` for [`U0`]
|
||||||
|
impl Sealed for U0 {}
|
||||||
|
|
||||||
|
/// Implement `Sealed` for all type-level, [`Unsigned`] integers *except* [`U0`]
|
||||||
|
impl<U: Unsigned, B: Bit> Sealed for UInt<U, B> {}
|
||||||
|
|
||||||
|
/// Trait mapping each countable type to its successor
|
||||||
|
///
|
||||||
|
/// This trait maps each countable type to its corresponding successor type. The
|
||||||
|
/// actual implementation of this trait is contained within `PrivateIncrement`.
|
||||||
|
/// Access to `PrivateIncrement` is restricted, so that safe HAL APIs can be
|
||||||
|
/// built with it.
|
||||||
|
pub trait Increment: PrivateIncrement {}
|
||||||
|
|
||||||
|
impl<T: PrivateIncrement> Increment for T {}
|
||||||
|
|
||||||
|
/// Trait mapping each countable type to its predecessor
|
||||||
|
///
|
||||||
|
/// This trait maps each countable type to its corresponding predecessor type.
|
||||||
|
/// The actual implementation of this trait is contained within
|
||||||
|
/// `PrivateDecrement`. Access to `PrivateDecrement` is restricted, so that safe
|
||||||
|
/// HAL APIs can be built with it.
|
||||||
|
pub trait Decrement: PrivateDecrement {}
|
||||||
|
|
||||||
|
impl<T: PrivateDecrement> Decrement for T {}
|
||||||
|
|
||||||
|
impl<N> PrivateIncrement for N
|
||||||
|
where
|
||||||
|
N: Unsigned + Add<B1>,
|
||||||
|
Add1<N>: Unsigned,
|
||||||
|
{
|
||||||
|
type Inc = Add1<N>;
|
||||||
|
#[inline]
|
||||||
|
fn inc(self) -> Self::Inc {
|
||||||
|
Self::Inc::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> PrivateDecrement for N
|
||||||
|
where
|
||||||
|
N: Unsigned + Sub<B1>,
|
||||||
|
Sub1<N>: Unsigned,
|
||||||
|
{
|
||||||
|
type Dec = Sub1<N>;
|
||||||
|
#[inline]
|
||||||
|
fn dec(self) -> Self::Dec {
|
||||||
|
Self::Dec::default()
|
||||||
|
}
|
||||||
|
}
|
1016
va416xx-hal/src/uart.rs
Normal file
1016
va416xx-hal/src/uart.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
|||||||
//! # API for the UART peripheral
|
|
||||||
//!
|
|
||||||
//! The core of this API are the [Uart], [Rx] and [Tx] structures.
|
|
||||||
//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver
|
|
||||||
//! using interrupts.
|
|
||||||
//!
|
|
||||||
//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART
|
|
||||||
//! peripheral.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
|
|
||||||
//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/rtic/src/bin/uart-echo-rtic.rs)
|
|
||||||
//! - [Flashloader exposing a CCSDS interface via UART](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
|
|
||||||
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
|
|
||||||
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
|
|
||||||
pub use vorago_shared_periphs::uart::*;
|
|
@ -3,26 +3,21 @@
|
|||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Watchdog simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/wdt.rs)
|
//! - [Watchdog simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/wdt.rs)
|
||||||
use vorago_shared_periphs::{
|
|
||||||
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{clock::Clocks, pac};
|
use crate::{
|
||||||
use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
|
clock::{Clocks, PeripheralSelect},
|
||||||
|
pac,
|
||||||
|
prelude::SyscfgExt,
|
||||||
|
};
|
||||||
|
use crate::{disable_interrupt, enable_interrupt};
|
||||||
|
|
||||||
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
|
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
|
||||||
|
|
||||||
/// Watchdog peripheral driver.
|
pub struct WdtController {
|
||||||
pub struct Wdt {
|
|
||||||
clock_freq: Hertz,
|
clock_freq: Hertz,
|
||||||
wdt: pac::WatchDog,
|
wdt: pac::WatchDog,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type alias for backwards compatibility
|
|
||||||
#[deprecated(since = "0.2.0", note = "Please use `Wdt` instead")]
|
|
||||||
pub type WdtController = Wdt;
|
|
||||||
|
|
||||||
/// Enable the watchdog interrupt
|
/// Enable the watchdog interrupt
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -30,22 +25,33 @@ pub type WdtController = Wdt;
|
|||||||
/// This function is `unsafe` because it can break mask-based critical sections.
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn enable_wdt_interrupts() {
|
pub unsafe fn enable_wdt_interrupts() {
|
||||||
enable_nvic_interrupt(pac::Interrupt::WATCHDOG)
|
enable_interrupt(pac::Interrupt::WATCHDOG)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn disable_wdt_interrupts() {
|
pub fn disable_wdt_interrupts() {
|
||||||
disable_nvic_interrupt(pac::Interrupt::WATCHDOG)
|
disable_interrupt(pac::Interrupt::WATCHDOG)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wdt {
|
impl WdtController {
|
||||||
pub fn new(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
|
pub fn new(
|
||||||
Self::start(wdt, clocks, wdt_freq_ms)
|
&self,
|
||||||
|
syscfg: &mut pac::Sysconfig,
|
||||||
|
wdt: pac::WatchDog,
|
||||||
|
clocks: &Clocks,
|
||||||
|
wdt_freq_ms: u32,
|
||||||
|
) -> Self {
|
||||||
|
Self::start(syscfg, wdt, clocks, wdt_freq_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
|
pub fn start(
|
||||||
enable_peripheral_clock(PeripheralSelect::Watchdog);
|
syscfg: &mut pac::Sysconfig,
|
||||||
reset_peripheral_for_cycles(PeripheralSelect::Watchdog, 2);
|
wdt: pac::WatchDog,
|
||||||
|
clocks: &Clocks,
|
||||||
|
wdt_freq_ms: u32,
|
||||||
|
) -> Self {
|
||||||
|
syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog);
|
||||||
|
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Watchdog);
|
||||||
|
|
||||||
let wdt_clock = clocks.apb2();
|
let wdt_clock = clocks.apb2();
|
||||||
let mut wdt_ctrl = Self {
|
let mut wdt_ctrl = Self {
|
||||||
@ -70,12 +76,12 @@ impl Wdt {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn disable_reset(&mut self) {
|
pub fn disable_reset(&mut self) {
|
||||||
self.wdt.wdogcontrol().modify(|_, w| w.resen().clear_bit());
|
self.wdt.wdogcontrol().modify(|_, w| w.resen().clear_bit())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn enable_reset(&mut self) {
|
pub fn enable_reset(&mut self) {
|
||||||
self.wdt.wdogcontrol().modify(|_, w| w.resen().set_bit());
|
self.wdt.wdogcontrol().modify(|_, w| w.resen().set_bit())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
76
va416xx/.github/workflows/ci.yml
vendored
76
va416xx/.github/workflows/ci.yml
vendored
@ -1,44 +1,64 @@
|
|||||||
name: ci
|
on: [push]
|
||||||
on: [push, pull_request]
|
|
||||||
|
name: build
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
name: Check build
|
name: Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
targets: "thumbv7em-none-eabihf"
|
profile: minimal
|
||||||
- run: cargo check --target thumbv7em-none-eabihf
|
toolchain: stable
|
||||||
- run: cargo check --target thumbv7em-none-eabihf --examples
|
target: thumbv7em-none-eabihf
|
||||||
- run: cargo check -p va416xx --target thumbv7em-none-eabihf --all-features
|
override: true
|
||||||
- run: cargo check -p va416xx-hal --target thumbv7em-none-eabihf --features "defmt"
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: check
|
||||||
|
args: --target thumbv7em-none-eabihf
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Check formatting
|
name: Rustfmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: actions-rs/toolchain@v1
|
||||||
- run: cargo fmt --all -- --check
|
with:
|
||||||
|
profile: minimal
|
||||||
docs:
|
toolchain: stable
|
||||||
name: Check Documentation Build
|
override: true
|
||||||
runs-on: ubuntu-latest
|
- run: rustup component add rustfmt
|
||||||
steps:
|
- uses: actions-rs/cargo@v1
|
||||||
- uses: actions/checkout@v4
|
with:
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
command: fmt
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx --all-features
|
args: --all -- --check
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx-hal --features "defmt va41630"
|
|
||||||
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-peb1
|
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
targets: "thumbv7em-none-eabihf"
|
profile: minimal
|
||||||
- run: cargo clippy --target thumbv7em-none-eabihf -- -D warnings
|
toolchain: stable
|
||||||
|
target: thumbv7em-none-eabihf
|
||||||
|
override: true
|
||||||
|
- run: rustup component add clippy
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: clippy
|
||||||
|
args: --target thumbv7em-none-eabihf -- -D warnings
|
||||||
|
|
||||||
|
ci:
|
||||||
|
if: ${{ success() }}
|
||||||
|
# all new jobs must be added to this list
|
||||||
|
needs: [check, fmt, clippy]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: CI succeeded
|
||||||
|
run: exit 0
|
@ -8,14 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
## [v0.4.0] 2025-02-18
|
|
||||||
|
|
||||||
- Re-generated PAC with `svd2rust` v0.35.0 and added optional `defmt` and `Debug` implementations
|
|
||||||
|
|
||||||
## [v0.3.0] 2025-02-13
|
|
||||||
|
|
||||||
- Re-generated PAC with `svd2rust` v0.35.0
|
|
||||||
|
|
||||||
## [v0.2.0] 2024-06-25
|
## [v0.2.0] 2024-06-25
|
||||||
|
|
||||||
- Re-Generated PAC with `svd2rust` v0.33.3
|
- Re-Generated PAC with `svd2rust` v0.33.3
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "va416xx"
|
name = "va416xx"
|
||||||
version = "0.4.0"
|
version = "0.2.0"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "PAC for the Vorago VA416xx family of MCUs"
|
description = "PAC for the Vorago VA416xx family of MCUs"
|
||||||
@ -15,8 +15,6 @@ categories = ["embedded", "no-std", "hardware-support"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
cortex-m = "0.7"
|
||||||
vcell = "0.1.3"
|
vcell = "0.1.3"
|
||||||
|
|
||||||
defmt = { version = "0.3", optional = true }
|
|
||||||
critical-section = { version = "1", optional = true }
|
critical-section = { version = "1", optional = true }
|
||||||
|
|
||||||
[dependencies.cortex-m-rt]
|
[dependencies.cortex-m-rt]
|
||||||
@ -25,8 +23,6 @@ version = ">=0.6.15,<0.8"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
rt = ["cortex-m-rt/device"]
|
rt = ["cortex-m-rt/device"]
|
||||||
# Adds Debug implementation
|
|
||||||
debug = []
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user