112 Commits

Author SHA1 Message Date
be6cd2d609 complicated 2025-04-09 16:15:36 +02:00
5e9526a2d1 this should be sufficient 2025-04-08 19:01:44 +02:00
125619e485 continue CAN 2025-04-08 16:22:02 +02:00
a6c9a6fcdc continue CAN support 2025-04-08 11:08:25 +02:00
e8e7ea9b1c some more const improvements 2025-04-07 14:36:17 +02:00
7d6f69d808 clean up crate structure a bit 2025-04-07 14:32:47 +02:00
0baf8d7b32 need to get familiar with CAN 2025-04-04 12:33:12 +02:00
512de17719 use bitbybit 2025-04-04 12:22:09 +02:00
1a5670b362 small README update 2025-03-11 16:42:16 +01:00
2b8a9dbce4 Merge pull request 'UART embedded-io fixes' (#66) from uart-embedded-io-fixes into main
Reviewed-on: #66
2025-03-10 17:39:54 +01:00
6528dd855f UART embedded-io fixes 2025-03-10 17:37:12 +01:00
4455cb0343 Merge pull request 'bump dependencies' (#65) from bump-dependencies into main
Reviewed-on: #65
2025-03-07 17:25:55 +01:00
2706dbf461 bump dependencies 2025-03-07 17:25:34 +01:00
c3e16b4278 prepare embassy release 2025-03-07 17:22:19 +01:00
2088d7dc8a prepare peb1 release 2025-03-07 17:19:42 +01:00
a44ba7b8a5 Merge pull request 'UART error handling update' (#64) from uart-error-handling-update into main
Reviewed-on: #64
2025-03-07 17:14:02 +01:00
a3c6366e98 UART error handling update 2025-03-07 17:10:42 +01:00
016c421cb8 small docs fix 2025-02-18 19:10:05 +01:00
0e99e04dd1 use released packages 2025-02-18 19:07:32 +01:00
aea3d835f0 Merge pull request 'prepare embassy release' (#63) from prep-embassy-release into main
Reviewed-on: #63
2025-02-18 18:33:02 +01:00
5f39b916fa prepare embassy release 2025-02-18 18:26:38 +01:00
6d8a114f49 Merge pull request 'prepare HAL patch and embassy release' (#62) from prep-hal-patch-embassy-release into main
Reviewed-on: #62
2025-02-18 18:24:09 +01:00
969f0f4ca5 prepare HAL patch and embassy release 2025-02-18 18:20:11 +01:00
b6971ab7eb Merge pull request 'bump all dependencies and prepare BSP and embassy release' (#61) from prep-bsp-embassy-releases into main
Reviewed-on: #61
2025-02-18 16:56:44 +01:00
41b215e326 bump all dependencies and prepare BSP and embassy release 2025-02-18 16:55:08 +01:00
43da650d78 Merge pull request 'prep HAL release v0.4.0' (#60) from prep-hal-v0.4.0 into main
Reviewed-on: #60
2025-02-18 16:27:58 +01:00
c67f50c96c prep HAL release v0.4.0 2025-02-18 16:26:44 +01:00
770d6cb905 date fix CHANGELOG 2025-02-18 15:16:14 +01:00
9878f3b493 Merge pull request 'update VS Code files' (#59) from update-vscode-files into main
Reviewed-on: #59
2025-02-17 11:38:03 +01:00
1b07d0f258 update VS Code files 2025-02-17 11:36:40 +01:00
d785f8ab88 Merge pull request 'fix for UART example' (#58) from example-fix into main
Reviewed-on: #58
2025-02-17 11:35:40 +01:00
527243ab96 fix for UART example 2025-02-17 11:35:11 +01:00
617ba3cca0 Merge pull request 'PAC changelog' (#57) from pac-changelog into main
Reviewed-on: #57
2025-02-17 11:33:22 +01:00
167cb97f7d PAC changelog 2025-02-17 11:33:07 +01:00
857b233881 Merge pull request 'changelog HAL' (#56) from changelog-hal into main
Reviewed-on: #56
2025-02-17 11:32:47 +01:00
9dcb423976 changelog HAL 2025-02-17 11:30:52 +01:00
186ae6d059 Merge pull request 'UART update' (#54) from uart-update into main
Reviewed-on: #54
2025-02-17 11:28:46 +01:00
54c949421e added async support for UART 2025-02-17 11:28:33 +01:00
910ed58fdf Merge pull request 'CI update' (#55) from ci-update into main
Reviewed-on: #55
2025-02-17 11:26:12 +01:00
ddf50376ec CI update 2025-02-17 11:17:05 +01:00
8fc9d12046 Merge pull request 'Async GPIO implementation' (#53) from add-async-gpio into main
Reviewed-on: #53
2025-02-17 10:57:03 +01:00
0bcf611e46 Async GPIO implementation 2025-02-15 18:51:03 +01:00
7f6b1a7ba5 Merge pull request 'defmt and debug feature for PAC' (#52) from update-pac-demt-debug-feature into main
Reviewed-on: #52
2025-02-14 16:42:50 +01:00
c39694e3cc update CI 2025-02-14 16:39:28 +01:00
3b4dd9d5c3 defmt and debug feature for PAC 2025-02-14 16:34:26 +01:00
3fe3b833a6 Merge pull request 'HAL update' (#51) from hal-update into main
Reviewed-on: #51
2025-02-14 16:31:15 +01:00
14ad647773 HAL update + CHANGELOG 2025-02-14 15:31:19 +01:00
f9d94a9d7e Merge pull request 'Updated GPIO impl' (#50) from update-gpio-impl-2 into main
Reviewed-on: #50
2025-02-14 14:42:43 +01:00
0d2fcd346b Updated GPIO impl 2025-02-14 14:40:16 +01:00
9306bb07a9 Merge pull request 'simplified PWM impl' (#49) from simplify-pwm-impl into main
Reviewed-on: #49
2025-02-14 14:37:09 +01:00
4fa1b17f20 simplified PWM impl 2025-02-13 19:05:40 +01:00
68fbeec9fe Merge pull request 'minor doc improvements' (#47) from minor-docs-improvements into main
Reviewed-on: #47
2025-02-13 18:51:18 +01:00
a8fbe2abb5 minor doc improvements 2025-02-13 18:50:53 +01:00
f37c3f2806 Merge pull request 'minor doc improvements' (#46) from minor-docs-improvements into main
Reviewed-on: #46
2025-02-13 18:26:04 +01:00
0a7642213f minor doc improvements 2025-02-13 18:24:57 +01:00
a3d637fd0c Merge pull request 'added and created embassy library' (#45) from add-embassy-lib into main
Reviewed-on: #45
2025-02-13 18:21:54 +01:00
69f8671412 added and created embassy library 2025-02-13 18:20:51 +01:00
13a86ac291 Merge pull request 'all clippy fixes' (#44) from clippy-fixes into main
Reviewed-on: #44
2025-02-13 17:38:45 +01:00
7089168917 all clippy fixes 2025-02-13 17:38:33 +01:00
bfaa3eebee Merge pull request 'bumped PAC to v0.3.0' (#43) from bump-pac into main
Reviewed-on: #43
2025-02-13 17:38:13 +01:00
273be8b3cf bumped PAC to v0.3.0 2025-02-13 16:11:27 +01:00
464cc60c75 Merge pull request 'regenerate PAC' (#42) from regenerate-pac into main
Reviewed-on: #42
2025-02-13 16:04:50 +01:00
2c9ca004ce regenerate PAC, va416xx v0.3.0 2025-02-13 16:03:19 +01:00
0c040515fe Merge pull request 'probe-rs update' (#40) from probe-rs-update into main
Reviewed-on: #40
2024-11-26 10:25:08 +01:00
f3fd5122cb probe-rs update 2024-11-26 10:24:50 +01:00
969e5bbc42 Merge pull request 'use released HAL dependency' (#39) from prep-bsp-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #39
2024-10-01 10:56:23 +02:00
6960c09627 Merge branch 'main' into prep-bsp-release 2024-10-01 10:56:15 +02:00
25f7b79f28 use released HAL dependency 2024-10-01 10:55:55 +02:00
2cf7554cab Merge pull request 'README and docs update' (#38) from prep-bsp-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #38
2024-10-01 10:54:52 +02:00
ff58fb7b55 prepare release for BSP 2024-10-01 10:54:11 +02:00
b1f63b64ce Merge pull request 'prepare release for BSP' (#37) from prep-bsp-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #37
2024-10-01 10:46:28 +02:00
22cc40c095 prepare release for BSP
Some checks are pending
Rust/va416xx-rs/pipeline/head Build started...
2024-10-01 10:43:06 +02:00
1ca319b433 Merge pull request 'bump version' (#36) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #36
2024-09-30 15:34:11 +02:00
3813c397f7 bump HAL version
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
2024-09-30 15:23:24 +02:00
9d8772bf1f Merge pull request 'prepare HAL release' (#35) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #35
2024-09-30 14:45:54 +02:00
246b084429 prepare HAL release
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-09-30 14:13:11 +02:00
bea5a852a2 Merge pull request 'smaller improvements and fixes' (#34) from smaller-improvements-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #34
2024-09-30 13:50:52 +02:00
e1487c8969 smaller improvements and fixes
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-09-30 12:17:05 +02:00
7e7416efd1 Merge pull request 'Improve UART Impl' (#33) from improve-uart-impl into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #33
2024-09-24 17:47:47 +02:00
42e3cfde8a improve UART impl
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-24 17:22:00 +02:00
a50f7a947a Merge pull request 'UART with IRQ + Embassy example' (#32) from add-uart-embassy-echo-example into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #32
2024-09-24 11:07:00 +02:00
abede6057e UART with IRQ + Embassy example
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-24 10:59:41 +02:00
e04f4336cc Merge pull request 'Flashloader and UART update' (#31) from flashloader-and-uart-update into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #31
2024-09-23 12:00:23 +02:00
aae870c767 Flashloader and UART improvements
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
2024-09-23 11:57:32 +02:00
3e67749452 Merge pull request 'calculate most bootloader properties' (#30) from bootloader-improvements into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #30
2024-09-23 10:26:57 +02:00
5eb38f9c2a fix
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-23 10:02:51 +02:00
5b336a2b41 calculate most bootloader properties
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-20 12:12:21 +02:00
051042ad1b Merge pull request 'Updates and fixes: SPI' (#29) from updates-and-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #29
2024-09-20 11:43:44 +02:00
aa1ed2a20d Updates and fixes
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
- Improve and fix SPI HAL and example
- Fix RTIC example
2024-09-20 11:41:24 +02:00
dfab81a813 Merge pull request 'smaller improvements' (#28) from more-smaller-improvements into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #28
2024-09-18 12:33:57 +02:00
2c0728102a smaller improvements
Some checks are pending
Rust/va416xx-rs/pipeline/head Build started...
2024-09-18 12:31:02 +02:00
5ce2dae236 Merge pull request 'prepare next HAL release' (#27) from prep-hal-release into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #27
2024-09-18 12:13:15 +02:00
19c64a8257 prepare next HAL release
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-09-18 12:01:27 +02:00
4ed0a806a6 Merge pull request 'Start adding embassy example' (#24) from add-embassy-example into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #24
2024-09-17 22:08:31 +02:00
a1a83700f8 Add embassy example
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-17 21:30:57 +02:00
ed175a03fc Merge pull request 'update VS Code settings for RTT block detection' (#26) from add-rtt-addr-detection-update-vscode into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #26
2024-09-17 10:55:14 +02:00
2465248223 update VS Code settings for RTT block detection 2024-09-17 10:54:46 +02:00
1603de11bf Merge pull request 'Make flashload COM more reliable' (#25) from flashloader-use-uart-irq into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #25
2024-09-13 18:27:14 +02:00
78dd7ee5c3 Make flashload COM more reliable
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
Fixes for UART RX with IRQ implementation
2024-09-13 18:26:12 +02:00
cad968342a Merge pull request 'bootloader and flashloader' (#21) from bootloader-flashloader into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #21
2024-09-12 19:48:24 +02:00
d386e5f6a1 Bootloader and Flashloader App
All checks were successful
Rust/va416xx-rs/pipeline/pr-main This commit looks good
2024-09-12 19:41:54 +02:00
deebc88042 Merge pull request 'Device specific support and UART Improvements' (#23) from device-specific-support into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #23
2024-07-19 06:47:25 +02:00
a326e5d058 Device specific support and UART Improvements
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-18 21:32:36 -07:00
dd5beb47f0 README typo
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-04 18:37:47 +02:00
8d84c61327 Merge pull request 'bugfix for updated DMA example' (#20) from dma-bugfix into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #20
2024-07-04 15:52:13 +02:00
5b1e30ea15 bugfix for updated DMA example
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-04 15:34:09 +02:00
baf900107d Merge pull request 'Basic DMA HAL' (#19) from dma-support into main
Some checks failed
Rust/va416xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #19
2024-07-03 22:54:10 +02:00
f47ba0828e changelog
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...
2024-07-03 22:52:07 +02:00
d6efff6832 Merge branch 'main' into dma-support
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build started...
2024-07-03 22:48:33 +02:00
895ad438aa Merge pull request 'small improvements and fixes' (#18) from smaller-improvements-and-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
Reviewed-on: #18
2024-07-03 22:48:08 +02:00
1c7affc4c5 Basic DMA HAL
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-03 15:40:14 +02:00
a2a4b5ff01 small improvements and fixes
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good
2024-07-01 16:02:10 +02:00
610 changed files with 13142 additions and 5450 deletions

View File

@ -1,13 +1,11 @@
[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 = "gdb-multiarch -q -x jlink/jlink-reva.gdb"
# 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}"]
runner = "probe-rs run --chip VA416xx_RAM --protocol swd"
rustflags = [
"-C",
@ -35,6 +33,8 @@ target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
[alias]
rb = "run --bin"
rrb = "run --release --bin"
ut = "test --target=x86_64-unknown-linux-gnu"
genbin = "objcopy --release -- -O binary app.bin"
[env]
DEFMT_LOG = "info"

View File

@ -10,8 +10,10 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "thumbv7em-none-eabihf"
- run: cargo check --target thumbv7em-none-eabihf --release
- run: cargo check --target thumbv7em-none-eabihf --examples --release
- run: cargo check --target thumbv7em-none-eabihf
- run: cargo check --target thumbv7em-none-eabihf --examples
- 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:
name: Run Tests
@ -21,7 +23,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- name: Install nextest
uses: taiki-e/install-action@nextest
- run: cargo nextest run --all-features -p va416xx-hal
- run: cargo nextest run --features va41630 -p va416xx-hal
# I think we can skip those on an embedded crate..
# - run: cargo test --doc -p va108xx-hal
@ -39,7 +41,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- 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 vorago-peb1
- 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:
name: Clippy

3
.gitignore vendored
View File

@ -14,3 +14,6 @@ Cargo.lock
**/*.rs.bk
/app.map
/app.bin
/Embed.toml

View File

@ -1,10 +1,19 @@
[workspace]
resolver = "2"
members = [
"va416xx",
"va416xx-hal",
"va416xx-embassy",
"vorago-peb1",
"bootloader",
"flashloader",
"examples/simple",
"va416xx",
"va416xx-hal",
"vorago-peb1"
"examples/embassy",
"examples/rtic",
]
exclude = [
"flashloader/slot-a-blinky",
"flashloader/slot-b-blinky",
]
[profile.dev]
@ -25,3 +34,12 @@ 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.

View File

@ -1,5 +1,5 @@
[default.general]
chip = "VA416xx"
chip = "VA416xx_RAM"
[default.rtt]
enabled = true

View File

@ -3,7 +3,7 @@
Vorago VA416xx Rust Support
=========
This crate collection provided support to write Rust applications for the VA416XX family
This crate collection provides support to write Rust applications for the VA416XX family
of devices.
## List of crates
@ -14,12 +14,23 @@ This workspace contains the following crates:
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)
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)
BSP crate containing support for the PEB1 development board.
It also contains the following helper crates:
- The `examples` crates contains various example applications for the HAL and the PAC.
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader)
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.
## Using the `.cargo/config.toml` file
@ -47,6 +58,25 @@ 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
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 avilable in the `.cargo/def-config.toml` file to use `probe-rs` for
convenience.
### Pre-Requisites
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
@ -55,6 +85,38 @@ care of installing the pre-requisites first.
### Using CLI
### Using VS Code
Assuming a working debug connection to your VA416xx board, you can debug using VS Code with
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
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
to automatically rebuild and flash your application.
If you would like to use a custom GDB application, you can specify the gdb binary in the following
configuration variables in your `settings.json`:
- `"cortex-debug.gdbPath"`
- `"cortex-debug.gdbPath.linux"`
- `"cortex-debug.gdbPath.windows"`
- `"cortex-debug.gdbPath.osx"`
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
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
@ -88,22 +150,18 @@ 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 the RTT Viewer
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 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.
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`
to automatically rebuild and flash your application.
## Learning (Embedded) Rust
If you would like to use a custom GDB application, you can specify the gdb binary in the following
configuration variables in your `settings.json`:
If you are unfamiliar with Rust on Embedded Systems or Rust in general, the following resources
are recommended:
- `"cortex-debug.gdbPath"`
- `"cortex-debug.gdbPath.linux"`
- `"cortex-debug.gdbPath.windows"`
- `"cortex-debug.gdbPath.osx"`
The provided VS Code configurations also provide an integrated RTT logger, which you can access
via the terminal at `RTT Ch:0 console`.
- [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)

View File

@ -25,7 +25,9 @@ pipeline {
stage('Docs') {
steps {
sh """
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 vorago-peb1
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
"""
}
}
@ -36,7 +38,9 @@ pipeline {
}
stage('Check Examples') {
steps {
sh 'cargo check --target thumbv7em-none-eabihf --examples'
sh """
cargo check --target thumbv7em-none-eabihf --examples
"""
}
}
}

22
bootloader/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "bootloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
panic-rtt-target = { version = "0.2" }
panic-halt = { version = "1" }
rtt-target = { version = "0.6" }
crc = "3"
static_assertions = "1"
[dependencies.va416xx-hal]
version = "0.5"
features = ["va41630"]
[features]
default = []
rtt-panic = []

47
bootloader/README.md Normal file
View File

@ -0,0 +1,47 @@
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.

359
bootloader/src/main.rs Normal file
View File

@ -0,0 +1,359 @@
//! 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};
#[cfg(not(feature = "rtt-panic"))]
use panic_halt as _;
#[cfg(feature = "rtt-panic")]
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
clock::{pll_setup_delay, ClkDivSel, ClkselSys},
edac,
nvm::Nvm,
pac::{self, interrupt},
prelude::*,
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 RTT_PRINTOUT: 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)]
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 RTT_PRINTOUT {
rtt_init_print!();
rprintln!("-- 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 = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
let mut opt_wdt = OptWdt(None);
if WITH_WDT {
opt_wdt.0 = Some(Wdt::start(
&mut dp.sysconfig,
dp.watch_dog,
&clocks,
WDT_FREQ_MS,
));
}
let nvm = Nvm::new(&mut dp.sysconfig, 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 RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
if RTT_PRINTOUT {
rprintln!("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 RTT_PRINTOUT {
rprintln!(
"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 && RTT_PRINTOUT {
rprintln!("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 && RTT_PRINTOUT {
rprintln!("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 && RTT_PRINTOUT {
rprintln!(
"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 && RTT_PRINTOUT {
rprintln!("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 RTT_PRINTOUT {
rprintln!("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 && RTT_PRINTOUT {
rprintln!("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?
}

33
examples/README.md Normal file
View File

@ -0,0 +1,33 @@
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
```

View File

@ -0,0 +1,38 @@
[package]
name = "embassy-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cfg-if = "1"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-io = "0.6"
embedded-hal-async = "1"
embedded-io-async = "0.6"
rtt-target = { version = "0.6" }
heapless = "0.8"
panic-rtt-target = { version = "0.2" }
static_cell = "2"
critical-section = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
ringbuf = { version = "0.4", default-features = false }
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" }
va416xx-embassy = { version = "0.1", 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"]

View File

@ -0,0 +1,371 @@
//! 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]
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 panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::clock::ClkgenExt;
use va416xx_hal::gpio::{
on_interrupt_for_async_gpio_for_port, InputDynPinAsync, InputPinAsync, PinsB, PinsC, PinsD,
PinsE, PinsF, PinsG, Port,
};
use va416xx_hal::time::Hertz;
use va416xx_hal::{
gpio::{DynPin, PinsA},
pac::{self, interrupt},
};
const CHECK_PA0_TO_PA1: bool = true;
const CHECK_PB0_TO_PB1: bool = true;
const CHECK_PC14_TO_PC15: bool = true;
const CHECK_PD2_TO_PD3: bool = true;
const CHECK_PE0_TO_PE1: bool = true;
const CHECK_PF0_TO_PF1: bool = true;
#[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) {
rtt_init_print!();
rprintln!("-- VA416xx Async GPIO Demo --");
let mut 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 = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
let portc = PinsC::new(&mut dp.sysconfig, dp.portc);
let portd = PinsD::new(&mut dp.sysconfig, dp.portd);
let porte = PinsE::new(&mut dp.sysconfig, dp.porte);
let portf = PinsF::new(&mut dp.sysconfig, dp.portf);
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
if CHECK_PA0_TO_PA1 {
let out_pin = porta.pa0.into_readable_push_pull_output();
let in_pin = porta.pa1.into_floating_input();
let out_pin = out_pin.downgrade();
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;
rprintln!("Example PA0 to PA1 done");
}
if CHECK_PB0_TO_PB1 {
let out_pin = portb.pb0.into_readable_push_pull_output();
let in_pin = portb.pb1.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).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;
rprintln!("Example PB0 to PB1 done");
}
if CHECK_PC14_TO_PC15 {
let out_pin = portc.pc14.into_readable_push_pull_output();
let in_pin = portc.pc15.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).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;
rprintln!("Example PC14 to PC15 done");
}
if CHECK_PD2_TO_PD3 {
let out_pin = portd.pd2.into_readable_push_pull_output();
let in_pin = portd.pd3.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).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;
rprintln!("Example PD2 to PD3 done");
}
if CHECK_PE0_TO_PE1 {
let out_pin = porte.pe0.into_readable_push_pull_output();
let in_pin = porte.pe1.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).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;
rprintln!("Example PE0 to PE1 done");
}
if CHECK_PF0_TO_PF1 {
let out_pin = portf.pf0.into_readable_push_pull_output();
let in_pin = portf.pf1.into_floating_input();
let out_pin = out_pin.downgrade();
let in_pin = InputDynPinAsync::new(in_pin.downgrade()).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;
rprintln!("Example PF0 to PF1 done");
}
rprintln!("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,
) {
rprintln!(
"{}: sending SetHigh command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::SetHigh, 20)).await;
async_input.wait_for_high().await.unwrap();
rprintln!(
"{}: Input pin is high now ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: sending SetLow command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::SetLow, 20)).await;
async_input.wait_for_low().await.unwrap();
rprintln!(
"{}: Input pin is low now ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: 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();
rprintln!(
"{}: input pin had rising edge ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: 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();
rprintln!(
"{}: input pin had a falling edge ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: 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();
rprintln!(
"{}: input pin had a falling (any) edge ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: 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();
rprintln!(
"{}: 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: DynPin,
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 => {
rprintln!("{}: Set output high", ctx);
out.set_high().unwrap();
}
GpioCmdType::SetLow => {
rprintln!("{}: Set output low", ctx);
out.set_low().unwrap();
}
GpioCmdType::RisingEdge => {
rprintln!("{}: Rising edge", ctx);
if !out.is_low().unwrap() {
out.set_low().unwrap();
}
out.set_high().unwrap();
}
GpioCmdType::FallingEdge => {
rprintln!("{}: Falling edge", ctx);
if !out.is_high().unwrap() {
out.set_high().unwrap();
}
out.set_low().unwrap();
}
GpioCmdType::CloseTask => {
rprintln!("{}: 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();
}

View File

@ -0,0 +1,111 @@
//! 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]
use core::cell::RefCell;
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 panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
gpio::PinsG,
pac::{self, interrupt},
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) {
rtt_init_print!();
rprintln!("-- VA108xx Async UART RX Demo --");
let mut 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 = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
);
}
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
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 {
rprintln!("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();
rprintln!(
"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 {
rprintln!("UART A errors: {:?}", errors);
}
}

View File

@ -0,0 +1,96 @@
//! 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]
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_io_async::Write;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
gpio::PinsG,
pac::{self, interrupt},
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) {
rtt_init_print!();
rprintln!("-- VA108xx Async UART TX Demo --");
let mut 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 = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
);
}
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
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 {
rprintln!("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);
}

View File

@ -0,0 +1,161 @@
//! 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]
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 panic_rtt_target as _;
use ringbuf::{
traits::{Consumer, Observer, Producer},
StaticRb,
};
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac::{self, interrupt},
prelude::*,
time::Hertz,
uart,
};
pub type SharedUart =
Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt<pac::Uart0>>>>;
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) {
rtt_init_print!();
rprintln!("VA416xx UART-Embassy Example");
let mut 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 = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uart0 = uart::Uart::new(
&mut dp.sysconfig,
dp.uart0,
(tx, rx),
Hertz::from_raw(BAUDRATE),
&clocks,
);
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 = portg.pg5.into_readable_push_pull_output();
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: Pin<PG5, OutputReadablePushPull>) {
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() {
rprintln!("UART error: {:?}", errors);
}
if ringbuf_full {
rprintln!("ringbuffer is full, deleted oldest data");
}
}

View File

@ -0,0 +1,2 @@
#![no_std]
pub const EXTCLK_FREQ: u32 = 40_000_000;

View File

@ -0,0 +1,63 @@
#![no_std]
#![no_main]
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, 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) {
rtt_init_print!();
rprintln!("VA416xx Embassy Demo");
let mut 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 = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks
);
} else {
va416xx_embassy::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim12,
dp.tim11,
&clocks
);
}
}
}
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let mut led = portg.pg5.into_readable_push_pull_output();
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;
rprintln!("Current time: {}", Instant::now().as_secs());
led.toggle();
}
}

22
examples/rtic/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "rtic-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
rtt-target = { version = "0.6" }
rtic-sync = { version = "1.3", features = ["defmt-03"] }
panic-rtt-target = { version = "0.2" }
va416xx-hal = { version = "0.5", features = ["va41630"] }
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]

View File

@ -0,0 +1,30 @@
//! Empty RTIC project template
#![no_main]
#![no_std]
#[rtic::app(device = pac)]
mod app {
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_default};
use va416xx_hal::pac;
#[local]
struct Local {}
#[shared]
struct Shared {}
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC template --");
(Shared {}, Local {})
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
#[allow(clippy::empty_loop)]
loop {}
}
}

70
examples/rtic/src/main.rs Normal file
View File

@ -0,0 +1,70 @@
//! 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::*;
use cortex_m::asm;
use panic_rtt_target as _;
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
use rtt_target::{rprintln, rtt_init_default};
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac,
prelude::*,
};
#[local]
struct Local {
led: Pin<PG5, OutputReadablePushPull>,
}
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC example application --");
// Use the external clock connected to XTAL_N.
let clocks = cx
.device
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(EXTCLK_FREQ)
.freeze(&mut cx.device.sysconfig)
.unwrap();
Mono::start(cx.core.SYST, clocks.sysclk().raw());
let portg = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let led = portg.pg5.into_readable_push_pull_output();
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;
}
}
}

View File

@ -4,15 +4,47 @@ version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m-rt = "0.7"
va416xx-hal = { path = "../../va416xx-hal" }
panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
critical-section = "1"
panic-rtt-target = { version = "0.2" }
rtt-target = { version = "0.6" }
embedded-hal = "1"
embedded-hal-nb = "1"
nb = "1"
embedded-io = "0.6"
panic-halt = "0.2"
vorago-peb1 = { path = "../../vorago-peb1" }
panic-halt = "1"
accelerometer = "0.12"
va416xx-hal = { version = "0.5", features = ["va41630"] }
[dependencies.vorago-peb1]
path = "../../vorago-peb1"
optional = true
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]
[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"]

View File

@ -36,9 +36,12 @@ fn main() -> ! {
let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
loop {
let single_value = adc
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::TempSensor)
.expect("reading single channel value failed");
rprintln!("Read single ADC value on channel 0: {:?}", single_value);
rprintln!(
"Read single ADC value on temperature sensor channel: {:?}",
single_value
);
let read_num = adc
.sweep_and_read_range(0, 7, &mut read_buf)
.expect("ADC range read failed");

View File

@ -3,7 +3,6 @@
#![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};
@ -18,6 +17,6 @@ fn main() -> ! {
let mut led = portg.pg5.into_readable_push_pull_output();
loop {
cortex_m::asm::delay(2_000_000);
led.toggle().ok();
led.toggle();
}
}

View File

@ -0,0 +1,280 @@
//! Simple DMA example
#![no_main]
#![no_std]
use core::cell::Cell;
use cortex_m_rt::entry;
use critical_section::Mutex;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::timer::CountdownTimer;
use va416xx_hal::{
pac::{self, interrupt},
prelude::*,
};
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
// Place the DMA control block into SRAM1 statically. This section needs to be defined in
// memory.x
#[link_section = ".sram1"]
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]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx DMA example");
let mut dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
// statically.
let dma = Dma::new(
&mut dp.sysconfig,
dp.dma,
DmaCfg::default(),
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK),
)
.expect("error creating DMA");
let (mut dma0, _, _, _) = dma.split();
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut src_buf_8_bit: [u8; 65] = [0; 65];
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
let mut src_buf_32_bit: [u32; 17] = [0; 17];
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
loop {
// This example uses stack-allocated buffers.
transfer_example_8_bit(
&mut src_buf_8_bit,
&mut dest_buf_8_bit,
&mut dma0,
&mut delay_ms,
);
delay_ms.delay_ms(500);
// This example uses statically allocated buffers.
transfer_example_16_bit(&mut dma0, &mut delay_ms);
delay_ms.delay_ms(500);
transfer_example_32_bit(
&mut src_buf_32_bit,
&mut dest_buf_32_bit,
&mut dma0,
&mut delay_ms,
);
delay_ms.delay_ms(500);
}
}
fn transfer_example_8_bit(
src_buf: &mut [u8; 65],
dest_buf: &mut [u8; 65],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
) {
(0..64).for_each(|i| {
src_buf[i] = i as u8;
});
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
critical_section::with(|cs| {
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_8_bit(src_buf, dest_buf)
.expect("error preparing transfer");
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
dma0.enable_done_interrupt();
dma0.enable_active_interrupt();
};
// Enable the individual channel.
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
// Use polling for completion status.
loop {
let mut dma_done = false;
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 8 bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
dma_done = true;
}
});
if dma_done {
rprintln!("8-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..64).for_each(|i| {
assert_eq!(dest_buf[i], i as u8);
});
// Sentinel value, should be 0.
assert_eq!(dest_buf[64], 0);
dest_buf.fill(0);
}
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) {
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) };
unsafe {
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
(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;
});
}
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
critical_section::with(|cs| {
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_16_bit(
&*core::ptr::addr_of!(DMA_SRC_BUF[0..32]),
&mut dest_buf_ref[0..32],
)
.expect("error preparing transfer");
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
dma0.enable_done_interrupt();
dma0.enable_active_interrupt();
};
// Enable the individual channel.
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
// Use polling for completion status.
loop {
let mut dma_done = false;
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 16-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
dma_done = true;
}
});
if dma_done {
rprintln!("16-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..32).for_each(|i| {
assert_eq!(
dest_buf_ref[i],
(i as u32 * u16::MAX as u32 / (dest_buf_ref.len() as u32 - 1)) as u16
);
});
// Sentinel value, should be 0.
assert_eq!(dest_buf_ref[32], 0);
dest_buf_ref.fill(0);
}
fn transfer_example_32_bit(
src_buf: &mut [u32; 17],
dest_buf: &mut [u32; 17],
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.
(0..16).for_each(|i| {
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
});
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
critical_section::with(|cs| {
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)
.expect("error preparing transfer");
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
dma0.enable_done_interrupt();
dma0.enable_active_interrupt();
};
// Enable the individual channel.
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
// Use polling for completion status.
loop {
let mut dma_done = false;
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 32-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
dma_done = true;
}
});
if dma_done {
rprintln!("32-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..16).for_each(|i| {
assert_eq!(
dest_buf[i],
(i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32
);
});
// Sentinel value, should be 0.
assert_eq!(dest_buf[16], 0);
dest_buf.fill(0);
}
#[interrupt]
#[allow(non_snake_case)]
fn DMA_DONE0() {
// Notify the main loop that the DMA transfer is finished.
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(true);
});
}
#[interrupt]
#[allow(non_snake_case)]
fn DMA_ACTIVE0() {
// Notify the main loop that the DMA 0 is active now.
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(true);
});
}

View File

@ -11,7 +11,8 @@ use va416xx_hal::{
gpio::PinsA,
pac,
prelude::*,
pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin},
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
timer::CountdownTimer,
};
#[entry]

View File

@ -3,27 +3,26 @@
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
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 va416xx_hal::spi::{Spi, TransferConfig};
use va416xx_hal::spi::{Spi, SpiClkConfig};
use va416xx_hal::{
gpio::{PinsB, PinsC},
pac,
prelude::*,
spi::SpiConfig,
time::Hertz,
};
#[derive(PartialEq, Debug)]
pub enum ExampleSelect {
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
Loopback,
// Send a test buffer and print everything received. You need to tie together MOSI/MISO in this
// mode.
TestBuffer,
// You need to tie together MOSI/MISO in this mode.
MosiMisoTiedTogether,
}
const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback;
@ -49,48 +48,51 @@ fn main() -> ! {
let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
// Configure SPI1 pins.
// Configure SPI0 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_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
.expect("invalid target clock"),
)
.mode(SPI_MODE)
.blockmode(BLOCKMODE);
if EXAMPLE_SEL == ExampleSelect::Loopback {
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.
let mut spi0 = Spi::new(
&mut dp.sysconfig,
&clocks,
dp.spi0,
(sck, miso, mosi),
&clocks,
spi_cfg,
&mut dp.sysconfig,
Some(&transfer_cfg.downgrade()),
);
spi0.set_fill_word(FILL_WORD);
loop {
let mut tx_buf: [u8; 3] = [1, 2, 3];
let mut rx_buf: [u8; 3] = [0; 3];
// Can't really verify correct reply here.
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);
let tx_buf: [u8; 4] = [1, 2, 3, 0];
let mut rx_buf: [u8; 4] = [0; 4];
// Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up.
spi0.write(&[0x42, 0x43]).expect("write failed");
spi0.transfer_in_place(&mut tx_buf)
// Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up.
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");
assert_eq!([1, 2, 3], tx_buf);
assert_eq!([1, 2, 3, 0], inplace_buf);
spi0.transfer(&mut rx_buf, &tx_buf)
.expect("SPI transfer failed");
assert_eq!(rx_buf, tx_buf);
assert_eq!(rx_buf, [1, 2, 3, 0]);
delay_sysclk.delay_ms(500);
}
}

View File

@ -3,12 +3,14 @@
#![no_std]
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m::asm;
use cortex_m_rt::entry;
use critical_section::Mutex;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
prelude::*,
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
@ -35,19 +37,21 @@ fn main() -> ! {
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
second_timer.start(1.Hz());
second_timer.listen();
second_timer.start(1.Hz());
loop {
let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get());
if current_ms - last_ms >= 1000 {
last_ms = current_ms;
let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get());
if current_ms >= last_ms + 1000 {
// To prevent drift.
last_ms += 1000;
rprintln!("MS counter: {}", current_ms);
let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get());
let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get());
rprintln!("Second counter: {}", second);
}
cortex_m::asm::delay(10000);
asm::delay(1000);
}
}
@ -60,7 +64,7 @@ fn TIM0() {
#[interrupt]
#[allow(non_snake_case)]
fn TIM1() {
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
let mut sec = SEC_COUNTER.borrow(cs).get();
sec += 1;
SEC_COUNTER.borrow(cs).set(sec);

View File

@ -33,10 +33,10 @@ fn main() -> ! {
let rx = gpiob.pg1.into_funsel_1();
let uart0 = uart::Uart::new(
&mut dp.sysconfig,
dp.uart0,
(tx, rx),
Hertz::from_raw(115200),
&mut dp.sysconfig,
&clocks,
);
let (mut tx, mut rx) = uart0.split();

View File

@ -3,14 +3,15 @@
#![no_std]
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use critical_section::Mutex;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::prelude::*;
use va416xx_hal::wdt::WdtController;
use va416xx_hal::wdt::Wdt;
static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
@ -40,17 +41,17 @@ fn main() -> ! {
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.unwrap();
enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
let mut last_interrupt_counter = 0;
let mut wdt_ctrl =
WdtController::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
let mut wdt_ctrl = Wdt::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
wdt_ctrl.enable_reset();
loop {
if TEST_MODE != TestMode::AllowReset {
wdt_ctrl.feed();
}
let interrupt_counter = cortex_m::interrupt::free(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
let interrupt_counter = critical_section::with(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
if interrupt_counter > last_interrupt_counter {
rprintln!("interrupt counter has increased to {}", interrupt_counter);
last_interrupt_counter = interrupt_counter;
@ -66,7 +67,7 @@ fn main() -> ! {
#[interrupt]
#[allow(non_snake_case)]
fn WATCHDOG() {
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
WDT_INTRPT_COUNT
.borrow(cs)
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);

1
flashloader/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/venv

28
flashloader/Cargo.toml Normal file
View File

@ -0,0 +1,28 @@
[package]
name = "flashloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
panic-rtt-target = { version = "0.2" }
rtt-target = { version = "0.6" }
rtt-log = "0.5"
log = "0.4"
crc = "3"
rtic-sync = "1"
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 }
cobs = { version = "0.3", default-features = false }
va416xx-hal = { version = "0.5", features = ["va41630"] }
rtic = { version = "2", features = ["thumbv7-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }

66
flashloader/README.md Normal file
View File

@ -0,0 +1,66 @@
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.

430
flashloader/image-loader.py Executable file
View File

@ -0,0 +1,430 @@
#!/usr/bin/env python3
from typing import List, Tuple
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
from tmtccmd.com import ComInterface
import toml
import struct
import logging
import argparse
import time
import enum
from tmtccmd.com.serial_base import SerialCfg
from tmtccmd.com.serial_cobs import SerialCobsComIF
from tmtccmd.com.ser_utils import prompt_com_port
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
_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 ImageLoader:
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
self.com_if = com_if
self.verificator = verificator
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()))
data_available = self.com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
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("received ping completion reply")
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 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 = prompt_com_port()
serial_cfg = SerialCfg(
com_if_id="ser_cobs",
serial_port=serial_port,
baud_rate=BAUD_RATE,
serial_timeout=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
flashloader/loader.toml Normal file
View File

@ -0,0 +1 @@
serial_port = "/dev/ttyUSB0"

View File

@ -0,0 +1,5 @@
spacepackets == 0.24
tmtccmd == 8.0.2
toml == 0.10
pyelftools == 0.31
crcmod == 1.7

2
flashloader/slot-a-blinky/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/app.map

View File

@ -0,0 +1,42 @@
[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.

View File

@ -0,0 +1,24 @@
/* 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
};

View File

@ -0,0 +1,23 @@
//! 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 Normal file
View File

@ -0,0 +1,2 @@
/target
/app.map

View File

@ -0,0 +1,42 @@
[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.

View File

@ -0,0 +1,24 @@
/* 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
};

View File

@ -0,0 +1,23 @@
//! 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();
}
}

9
flashloader/src/lib.rs Normal file
View File

@ -0,0 +1,9 @@
#![no_std]
#[cfg(test)]
mod tests {
#[test]
fn simple() {
assert_eq!(1 + 1, 2);
}
}

549
flashloader/src/main.rs Normal file
View File

@ -0,0 +1,549 @@
//! 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 panic_rtt_target as _;
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;
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;
use panic_rtt_target as _;
use rtic::Mutex;
use rtic_monotonics::systick::prelude::*;
use rtt_target::rprintln;
use satrs::pus::verification::VerificationReportCreator;
use spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::uart::IrqContextTimeoutOrMaxSize;
use va416xx_hal::{
clock::ClkgenExt,
edac,
gpio::PinsG,
nvm::Nvm,
pac,
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<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>,
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) {
//rtt_init_default!();
rtt_log::init();
rprintln!("-- Vorago flashloader --");
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = cx
.device
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut cx.device.sysconfig)
.unwrap();
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig);
let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let tx = gpiog.pg0.into_funsel_1();
let rx = gpiog.pg1.into_funsel_1();
let uart0 = Uart::new(
&mut cx.device.sysconfig,
cx.device.uart0,
(tx, rx),
Hertz::from_raw(UART_BAUDRATE),
&clocks,
);
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 {
log::debug!("RX Info: {:?}", cx.local.rx_context);
log::debug!("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() {
log::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 {
log::warn!("COBS TC queue full");
}
}
} else {
log::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() {
log::warn!("UART error: {:?}", result.errors.unwrap());
}
}
Err(e) => {
log::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();
log::info!(target: "TC Handler", "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() {
log::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 mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
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(&mut sys_cfg));
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 {
rprintln!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR);
}
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
rprintln!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR);
}
}
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
log::info!(target: "TC Handler", "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 {
log::warn!(
target: "TC Handler",
"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 {
log::warn!(target: "TC Handler", "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() {
log::warn!(
target: "TC Handler",
"invalid data length {} for raw mem write detected",
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!(
target: "TC Handler",
"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 mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
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);
log::info!(
target: "TC Handler",
"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;
cx.local
.uart_tx
.write(&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();
}

View File

@ -1,6 +1,6 @@
target remote localhost:2331
monitor halt
monitor reset
# *try* to stop at the user entry point (it might be gone due to inlining)
break main

View File

@ -11,4 +11,13 @@ MEMORY
/* 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) - 4;
_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
};

24
scripts/memory_app_a.x Normal file
View File

@ -0,0 +1,24 @@
/* 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
};

24
scripts/memory_app_b.x Normal file
View File

@ -0,0 +1,24 @@
/* 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
};

2
scripts/unittest.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p va416xx-hal --features alloc

View File

@ -0,0 +1,21 @@
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

View File

@ -0,0 +1,40 @@
[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]
critical-section = "1"
embassy-sync = "0.6"
embassy-executor = "0.7"
embassy-time-driver = "0.2"
embassy-time-queue-utils = "0.1"
portable-atomic = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va416xx-hal = { version = ">=0.4, <=0.5" }
[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"]

10
va416xx-embassy/README.md Normal file
View File

@ -0,0 +1,10 @@
[![Crates.io](https://img.shields.io/crates/v/va416xx-embassy)](https://crates.io/crates/va416xx-embassy)
[![docs.rs](https://img.shields.io/docsrs/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.

3
va416xx-embassy/docs.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --open

398
va416xx-embassy/src/lib.rs Normal file
View File

@ -0,0 +1,398 @@
//! # 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 core::{
cell::{Cell, RefCell},
sync::atomic::{AtomicU32, Ordering},
};
use critical_section::{CriticalSection, Mutex};
use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ};
use embassy_time_queue_utils::Queue;
use once_cell::sync::OnceCell;
use va416xx_hal::{
clock::Clocks,
enable_nvic_interrupt,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
pwm::ValidTim,
timer::{
assert_tim_reset_for_two_cycles, enable_tim_clk, get_tim_raw, TimRegInterface,
TIM_IRQ_OFFSET,
},
};
time_driver_impl!(
static TIME_DRIVER: TimerDriver = TimerDriver {
periods: AtomicU32::new(0),
alarms: Mutex::new(AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});
/// 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);
/// Expose the time driver so the user can specify the IRQ handlers themselves.
pub fn time_driver() -> &'static TimerDriver {
&TIME_DRIVER
}
/// 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.
///
/// # Safety
///
/// This has to be called once at initialization time to initiate the time driver for
/// embassy.
pub unsafe fn init<
TimekeeperTim: TimRegInterface + ValidTim,
AlarmTim: TimRegInterface + ValidTim,
>(
syscfg: &mut pac::Sysconfig,
irq_router: &pac::IrqRouter,
timekeeper: TimekeeperTim,
alarm: AlarmTim,
clocks: &Clocks,
) {
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
TimekeeperTim::ID,
TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Timekeeper TIM and IRQ missmatch"
);
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
AlarmTim::ID,
ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Alarm TIM and IRQ missmatch"
);
enable_and_init_irq_router(syscfg, irq_router);
TIME_DRIVER.init(syscfg, timekeeper, alarm, clocks)
}
struct AlarmState {
timestamp: Cell<u64>,
}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
unsafe impl Send for AlarmState {}
static SCALE: OnceCell<u64> = OnceCell::new();
static TIMEKEEPER_TIM: OnceCell<u8> = OnceCell::new();
static ALARM_TIM: OnceCell<u8> = OnceCell::new();
pub struct TimerDriver {
periods: AtomicU32,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<AlarmState>,
queue: Mutex<RefCell<Queue>>,
}
impl TimerDriver {
fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
&self,
syscfg: &mut pac::Sysconfig,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
clocks: &Clocks,
) {
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
return;
}
ALARM_TIM.set(alarm_tim.tim_id()).ok();
TIMEKEEPER_TIM.set(timekeeper_tim.tim_id()).ok();
enable_tim_clk(syscfg, timekeeper_tim.tim_id());
assert_tim_reset_for_two_cycles(syscfg, alarm_tim.tim_id());
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
SCALE
.set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64)
.unwrap();
let timekeeper_tim_regs = timekeeper_tim.reg_block();
timekeeper_tim_regs
.rst_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Decrementing counter.
timekeeper_tim_regs
.cnt_value()
.write(|w| unsafe { w.bits(u32::MAX) });
// Switch on. Timekeeping should always be done.
unsafe {
enable_nvic_interrupt(TimekeeperTim::IRQ);
}
timekeeper_tim_regs
.ctrl()
.modify(|_, w| w.irq_enb().set_bit());
timekeeper_tim_regs.enable().write(|w| unsafe { w.bits(1) });
enable_tim_clk(syscfg, AlarmTim::ID);
assert_tim_reset_for_two_cycles(syscfg, AlarmTim::ID);
let alarm_tim_regs = alarm_tim.reg_block();
// Explicitely disable alarm timer until needed.
alarm_tim_regs.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
unsafe {
enable_nvic_interrupt(AlarmTim::IRQ);
}
}
fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock {
TIMEKEEPER_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
ALARM_TIM
.get()
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
.unwrap()
}
/// Should be called inside the IRQ of the timekeeper timer.
///
/// # Safety
///
/// This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_timekeeping(&self) {
self.next_period();
}
/// Should be called inside the IRQ of the alarm timer.
///
/// # Safety
///
///This function has to be called once by the TIM IRQ used for the timekeeping.
pub unsafe fn on_interrupt_alarm(&self) {
critical_section::with(|cs| {
if self.alarms.borrow(cs).timestamp.get() <= self.now() {
self.trigger_alarm(cs)
}
})
}
fn next_period(&self) {
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
let t = (period as u64) << 32;
critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs);
let at = alarm.timestamp.get();
if at < t {
self.trigger_alarm(cs);
} else {
let alarm_tim = Self::alarm_tim();
let remaining_ticks = (at - t) * *SCALE.get().unwrap();
if remaining_ticks <= u32::MAX as u64 {
alarm_tim.enable().write(|w| unsafe { w.bits(0) });
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(remaining_ticks as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
}
})
}
fn trigger_alarm(&self, cs: CriticalSection) {
Self::alarm_tim().ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = &self.alarms.borrow(cs);
// Setting the maximum value disables the alarm.
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
let mut next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
}
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
if SCALE.get().is_none() {
return false;
}
let alarm_tim = Self::alarm_tim();
alarm_tim.ctrl().modify(|_, w| {
w.irq_enb().clear_bit();
w.enable().clear_bit()
});
let alarm = self.alarms.borrow(cs);
alarm.timestamp.set(timestamp);
let t = self.now();
if timestamp <= t {
alarm.timestamp.set(u64::MAX);
return false;
}
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
// the interrupts are enabled or not. When they are enabled at a later point, the
// right value is already set.
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
// is not missed.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
let timer_ticks = (safe_timestamp - t).checked_mul(*SCALE.get().unwrap());
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
alarm_tim
.cnt_value()
.write(|w| unsafe { w.bits(timer_ticks.unwrap() as u32) });
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
}
// If it's too far in the future, don't enable timer yet.
// It will be enabled later by `next_period`.
true
}
}
impl Driver for TimerDriver {
fn now(&self) -> u64 {
if SCALE.get().is_none() {
return 0;
}
let mut period1: u32;
let mut period2: u32;
let mut counter_val: u32;
loop {
// Acquire ensures that we get the latest value of `periods` and
// no instructions can be reordered before the load.
period1 = self.periods.load(Ordering::Acquire);
counter_val = u32::MAX - Self::timekeeper_tim().cnt_value().read().bits();
// Double read to protect against race conditions when the counter is overflowing.
period2 = self.periods.load(Ordering::Relaxed);
if period1 == period2 {
let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
return now;
}
}
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
}

128
va416xx-hal/CHANGELOG.md Normal file
View File

@ -0,0 +1,128 @@
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

View File

@ -1,6 +1,6 @@
[package]
name = "va416xx-hal"
version = "0.1.0"
version = "0.5.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "HAL for the Vorago VA416xx family of MCUs"
@ -12,33 +12,48 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
critical-section = "1"
nb = "1"
paste = "1"
libm = "0.2"
embedded-hal-nb = "1"
embedded-hal-async = "1"
embedded-hal = "1"
embedded-io = "0.6"
embedded-io-async = "0.6"
embedded-can = "0.4"
num_enum = { version = "0.7", default-features = false }
typenum = "1"
bitflags = "2"
defmt = { version = "0.3", optional = true }
bitbybit = "1.3"
arbitrary-int = "1.3"
derive-mmio = "0.4"
fugit = "0.3"
delegate = "0.12"
delegate = ">=0.12, <=0.13"
heapless = "0.8"
void = { version = "1", default-features = false }
thiserror = { version = "2", default-features = false }
portable-atomic = "1"
embassy-sync = "0.6"
va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
[dependencies.void]
version = "1"
default-features = false
[dependencies.va416xx]
default-features = false
version = "0.2"
features = ["critical-section"]
defmt = { version = "1", optional = true }
[features]
default = ["rt", "revb"]
rt = ["va416xx/rt"]
defmt = ["dep:defmt", "fugit/defmt"]
alloc = []
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03"]
va41630 = ["device-selected"]
va41620 = ["device-selected"]
va41629 = ["device-selected"]
va41628 = ["device-selected"]
device-selected = []
revb = []
[package.metadata.docs.rs]
all-features = true
features = ["va41630", "defmt"]
rustdoc-args = ["--generate-link-to-definition"]

View File

@ -11,6 +11,17 @@ 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 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 an application requires the `thumbv7em-none-eabihf` cross-compiler toolchain.
@ -22,12 +33,6 @@ rustup target add thumbv7em-none-eabihf
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
If you have a custom board, you might be interested in setting up a new binary crate for your
@ -56,10 +61,18 @@ is contained within the
[dependencies.va416xx-hal]
version = "<Most Recent Version>"
features = ["rt"]
features = ["va41630"]
```
6. Build the application with `cargo build`
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.
## 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/)

3
va416xx-hal/docs.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --features "defmt va41630" --open

View File

@ -1,3 +1,9 @@
//! 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 crate::clock::Clocks;
@ -46,6 +52,8 @@ pub enum ChannelSelect {
}
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 {
const AnIn0 = 1;
const AnIn1 = 1 << 1;
@ -66,34 +74,28 @@ bitflags::bitflags! {
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("ADC empty error")]
pub struct AdcEmptyError;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid channel range error")]
pub struct InvalidChannelRangeError;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("buffer too small")]
pub struct BufferTooSmallError;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AdcRangeReadError {
InvalidChannelRange(InvalidChannelRangeError),
BufferTooSmall(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)
}
#[error("invalid channel range: {0}")]
InvalidChannelRange(#[from] InvalidChannelRangeError),
#[error("buffer too small: {0}")]
BufferTooSmall(#[from] BufferTooSmallError),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@ -129,6 +131,18 @@ impl ChannelValue {
pub enum ChannelTagEnabled {}
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> {
adc: pac::Adc,
phantom: PhantomData<TagEnabled>,
@ -154,34 +168,44 @@ impl Adc<ChannelTagDisabled> {
lower_bound_idx: u8,
upper_bound_idx: u8,
rx_buf: &mut [u16],
) -> Result<(), AdcRangeReadError> {
) -> Result<usize, AdcRangeReadError> {
self.generic_prepare_range_sweep_and_wait_until_ready(
lower_bound_idx,
upper_bound_idx,
rx_buf.len(),
)?;
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
let fifo_entry_count = 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;
}
Ok(())
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(
&self,
ch_select: MultiChannelSelect,
rx_buf: &mut [u16],
) -> Result<(), BufferTooSmallError> {
) -> Result<usize, BufferTooSmallError> {
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
let fifo_entry_count = 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;
}
Ok(())
Ok(fifo_entry_count as usize)
}
pub fn try_read_single_value(&self) -> nb::Result<Option<u16>, ()> {
self.generic_try_read_single_value()
.map(|v| v.map(|v| v & 0xfff))
}
#[inline(always)]
pub fn channel_tag_enabled(&self) -> bool {
false
}
}
impl Adc<ChannelTagEnabled> {
@ -230,17 +254,21 @@ impl Adc<ChannelTagEnabled> {
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(
&self,
ch_select: MultiChannelSelect,
rx_buf: &mut [ChannelValue],
) -> Result<(), BufferTooSmallError> {
) -> Result<usize, BufferTooSmallError> {
self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?;
for i in 0..self.adc.status().read().fifo_entry_cnt().bits() {
let fifo_entry_count = 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.create_channel_value(self.adc.fifo_data().read().bits() as u16);
}
Ok(())
Ok(fifo_entry_count as usize)
}
#[inline]
@ -250,11 +278,16 @@ impl Adc<ChannelTagEnabled> {
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> {
fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self {
syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc);
syscfg.enable_peripheral_clock(crate::PeripheralSelect::Adc);
adc.ctrl().write(|w| unsafe { w.bits(0) });
let adc = Self {
adc,
@ -274,11 +307,6 @@ impl<TagEnabled> Adc<TagEnabled> {
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)]
pub fn clear_fifo(&self) {
self.adc.fifo_clr().write(|w| unsafe { w.bits(1) });
@ -326,8 +354,6 @@ impl<TagEnabled> Adc<TagEnabled> {
ch_select |= 1 << i;
}
self.generic_trigger_sweep(ch_select);
cortex_m::asm::nop();
cortex_m::asm::nop();
while self.adc.status().read().adc_busy().bit_is_set() {
cortex_m::asm::nop();
}

View File

@ -0,0 +1,117 @@
#[derive(Debug, thiserror::Error)]
#[error("invalid data size error {0}")]
pub struct InvalidDataSizeError(usize);
pub struct CanFrameNormal {
id: embedded_can::Id,
size: usize,
data: [u8; 8],
}
impl CanFrameNormal {
pub fn new(id: embedded_can::Id, data: &[u8]) -> Self {
let size = data.len();
let mut data_array = [0; 8];
data_array[..size].copy_from_slice(data);
Self {
id,
size,
data: data_array,
}
}
pub fn id(&self) -> embedded_can::Id {
self.id
}
pub fn data(&self) -> &[u8] {
&self.data[..self.size]
}
pub fn dlc(&self) -> usize {
self.size
}
}
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
}
}
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> {
let id: embedded_can::Id = id.into();
Some(Self::Normal(CanFrameNormal::new(id, data)))
}
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(_) => &[],
}
}
}

606
va416xx-hal/src/can/mod.rs Normal file
View File

@ -0,0 +1,606 @@
//! CAN driver.
//!
//! The VA416xx CAN module is based on the CP3UB26 module.
use arbitrary_int::{u11, u15, u2, u3, u4, u7, Number};
use embedded_can::Frame;
use regs::{
BaseId, BufStatusAndControl, Control, DataDirection, ExtendedId, MmioCan, TimingConfig,
};
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
use libm::roundf;
pub mod frame;
pub mod regs;
pub use frame::*;
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;
#[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].
pub 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() },
}
}
}
/// 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)
}
/// 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>, 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 = apb1_clock / (bitrate * prescaler as u32);
// This is taken from the Python CAN library. NBT should not be too small.
if nom_bit_time < 8 {
break;
}
let actual_bitrate = apb1_clock / (prescaler as u32 * nom_bit_time);
let bitrate_deviation = ((actual_bitrate.raw() as i32 - bitrate.raw() as i32).abs() as f32)
/ bitrate.raw() as f32;
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,
});
}
Ok(configs)
}
pub trait Instance {
const ID: CanId;
const PERIPH_SEL: PeripheralSelect;
}
impl Instance for va416xx::Can0 {
const ID: CanId = CanId::Can0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0;
}
impl Instance for va416xx::Can1 {
const ID: CanId = CanId::Can1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
}
#[derive(Debug, Clone, Copy)]
pub struct ClockConfig {
prescaler: u8,
tseg1: u8,
tseg2: u8,
sjw: u8,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid buffer index {0}")]
pub struct InvalidBufferIndexError(usize);
#[derive(Debug, thiserror::Error)]
#[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,
}
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.
///
/// 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 prescaler = roundf(
clocks.apb1().raw() as f32
/ (bitrate.raw() as f32 * (1.0 + tseg1.as_u32() as f32 + tseg2.as_u32() as f32)),
) as u32;
if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
return Err(ClockConfigError::CanNotFindPrescaler);
}
let actual_bitrate = clocks.apb1() / (prescaler * (1 + tseg1.as_u32() + tseg2.as_u32()));
let bitrate_deviation = ((actual_bitrate.raw() as i32 - bitrate.raw() as i32).abs() as f32)
/ bitrate.raw() as f32;
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)
}
pub fn sjw_reg_value(&self) -> u2 {
u2::new(self.sjw.value() - 1)
}
pub fn tseg1_reg_value(&self) -> u4 {
u4::new(self.tseg1.value() - 1)
}
pub fn tseg2_reg_value(&self) -> u3 {
u3::new(self.tseg2.value() - 1)
}
pub fn prescaler_reg_value(&self) -> u7 {
u7::new(self.prescaler.value() - 2)
}
}
pub struct Can {
regs: regs::MmioCan<'static>,
id: CanId,
}
impl Can {
pub fn new<CanI: Instance>(_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.msg_buf_block_mut(i).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(ExtendedId::new_with_raw_value(0));
self.regs.write_gmskb(BaseId::new_with_raw_value(0));
}
/// 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(
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.
pub fn set_base_mask_for_exact_id_match(&mut self) {
self.regs.write_bmskx(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.
pub fn set_base_mask_for_all_match(&mut self) {
self.regs
.write_bmskx(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
}
#[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 enable_bufflock(&mut self) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_bufflock(true);
ctrl
});
}
#[inline]
pub fn enable(&mut self) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_enable(true);
ctrl
});
}
}
#[derive(Debug)]
pub enum ChannelState {
Idle,
Receiving,
Transmitting,
AwaitingRtrReply,
}
pub struct CanChannel {
can_id: CanId,
idx: usize,
regs: regs::MmioCanMsgBuf<'static>,
mode: ChannelState,
}
impl core::fmt::Debug for CanChannel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CanChannel")
.field("can_id", &self.can_id)
.field("idx", &self.idx)
.field("mode", &self.mode)
.finish()
}
}
impl CanChannel {
pub fn configure_for_reception_with_standard_id(
&mut self,
standard_id: embedded_can::StandardId,
set_rtr: bool,
) -> Result<(), InvalidBufferIndexError> {
let mut id1_reg = standard_id.as_raw() << 5;
if set_rtr {
id1_reg |= 1 << 4;
}
self.regs
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
self.regs.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_status(regs::BufferState::RxReady)
.build(),
);
Ok(())
}
pub fn configure_for_reception_with_extended_id(
&mut self,
extended_id: embedded_can::ExtendedId,
set_rtr: bool,
) -> Result<(), InvalidBufferIndexError> {
let mut regs = unsafe { self.can_id.steal_regs() };
let mut cmb_block = regs.msg_buf_block_mut(self.idx);
let id_raw = extended_id.as_raw();
let id1_reg = (((id_raw >> 18) & 0x7FF) << 4) as u16 | ((id_raw >> 15) & 0b111) as u16;
cmb_block.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
let id0_reg = ((id_raw & 0x7FFF) << 1) as u16 | set_rtr as u16;
cmb_block.write_id0(ExtendedId::new_with_raw_value(id0_reg as u32));
cmb_block.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_status(regs::BufferState::RxReady)
.build(),
);
self.mode = ChannelState::Receiving;
Ok(())
}
pub fn configure_for_transmission(
&mut self,
tx_priority: u4,
) -> Result<(), InvalidBufferIndexError> {
let mut regs = unsafe { self.can_id.steal_regs() };
let mut cmb_block = regs.msg_buf_block_mut(self.idx);
cmb_block.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(tx_priority)
.with_status(regs::BufferState::TxNotActive)
.build(),
);
self.mode = ChannelState::Receiving;
Ok(())
}
/// 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.regs.read_id0();
let id1 = self.regs.read_id1();
let data0 = self.regs.read_data0();
let data1 = self.regs.read_data1();
let data2 = self.regs.read_data2();
let data3 = self.regs.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] = data3.data_upper_byte().as_u8(),
1 => data[i] = data3.data_lower_byte().as_u8(),
2 => data[i] = data2.data_upper_byte().as_u8(),
3 => data[i] = data2.data_lower_byte().as_u8(),
4 => data[i] = data1.data_upper_byte().as_u8(),
5 => data[i] = data1.data_lower_byte().as_u8(),
6 => data[i] = data0.data_upper_byte().as_u8(),
7 => data[i] = data0.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.regs.read_stat_ctrl().dlc().as_usize()).into()
} else {
let dlc = self.regs.read_stat_ctrl().dlc();
read_data(dlc);
CanFrameNormal::new(id, &data[0..dlc.as_usize()]).into()
}
}
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.regs.modify_stat_ctrl(|mut ctrl| {
ctrl.set_status(regs::BufferState::TxOnce);
ctrl
});
}
fn write_id(&mut self, id: embedded_can::Id, is_remote: bool) {
match id {
embedded_can::Id::Standard(standard_id) => {
self.regs.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.regs.write_id0(ExtendedId::new_with_raw_value(0));
}
embedded_can::Id::Extended(extended_id) => {
let id_raw = extended_id.as_raw();
self.regs.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.regs.write_id0(
ExtendedId::builder()
.with_mask_14_0(u15::new((id_raw & 0x7FFF) as u16))
.with_xrtr(is_remote)
.build(),
);
}
}
}
}
pub struct CanWorker {
can: Can,
channels: [CanChannel; 15],
}
#[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);
}
}

349
va416xx-hal/src/can/regs.rs Normal file
View File

@ -0,0 +1,349 @@
//! Custom register definitions for the CAN register block to circumvent PAC API / SVD
//! shortcomings.
use arbitrary_int::{u11, u15, u2, u3, u4, u7};
pub const CAN_0_BASE: usize = 0x4001_4000;
pub const CAN_1_BASE: usize = 0x4001_4400;
#[derive(Debug)]
#[bitbybit::bitenum(u4)]
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)]
status: 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(8..=15, rw)]
data_upper_byte: u8,
#[bits(8..=15, rw)]
data_lower_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,
}
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_data3(TwoBytesData::new_with_raw_value(0));
self.write_data2(TwoBytesData::new_with_raw_value(0));
self.write_data1(TwoBytesData::new_with_raw_value(0));
self.write_data0(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],
}
#[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(derive_mmio::Mmio)]
#[repr(C)]
pub struct Can {
#[mmio(inner)]
cmb0: CanMsgBuf,
#[mmio(inner)]
cmb1: CanMsgBuf,
#[mmio(inner)]
cmb2: CanMsgBuf,
#[mmio(inner)]
cmb3: CanMsgBuf,
#[mmio(inner)]
cmb4: CanMsgBuf,
#[mmio(inner)]
cmb5: CanMsgBuf,
#[mmio(inner)]
cmb6: CanMsgBuf,
#[mmio(inner)]
cmb7: CanMsgBuf,
#[mmio(inner)]
cmb8: CanMsgBuf,
#[mmio(inner)]
cmb9: CanMsgBuf,
#[mmio(inner)]
cmb10: CanMsgBuf,
#[mmio(inner)]
cmb11: CanMsgBuf,
#[mmio(inner)]
cmb12: CanMsgBuf,
#[mmio(inner)]
cmb13: CanMsgBuf,
// This CAN message buffer has different mask registers.
#[mmio(inner)]
cmb14: CanMsgBuf,
/// 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: u32,
#[mmio(PureRead)]
error_counter: ErrorCounter,
#[mmio(PureRead)]
diag: u32,
#[mmio(PureRead)]
timer: u32,
}
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)
}
}
impl MmioCan<'_> {
// TODO: It would be nice if derive-mmio could generate this for us..
pub fn msg_buf_block_mut(&mut self, idx: usize) -> MmioCanMsgBuf<'static> {
assert!(idx < 15, "invalid index for CAN message buffer");
match idx {
0 => unsafe { self.steal_cmb0() },
1 => unsafe { self.steal_cmb1() },
2 => unsafe { self.steal_cmb2() },
3 => unsafe { self.steal_cmb3() },
4 => unsafe { self.steal_cmb4() },
5 => unsafe { self.steal_cmb5() },
6 => unsafe { self.steal_cmb6() },
7 => unsafe { self.steal_cmb7() },
8 => unsafe { self.steal_cmb8() },
9 => unsafe { self.steal_cmb9() },
10 => unsafe { self.steal_cmb10() },
11 => unsafe { self.steal_cmb11() },
12 => unsafe { self.steal_cmb12() },
13 => unsafe { self.steal_cmb13() },
14 => unsafe { self.steal_cmb14() },
_ => unreachable!(),
}
}
}

View File

@ -10,52 +10,17 @@
//! # Examples
//!
//! - [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::pac;
use crate::{pac, PeripheralSelect, SyscfgExt as _};
use crate::time::Hertz;
pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
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)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FilterClkSel {
SysClk = 0,
Clk1 = 1,
@ -67,81 +32,6 @@ pub enum FilterClkSel {
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.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -310,6 +200,12 @@ impl ClkgenCfgr {
self
}
#[inline]
pub fn pll_cfg(mut self, pll_cfg: PllCfg) -> Self {
self.pll_cfg = Some(pll_cfg);
self
}
#[inline]
pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self {
self.ref_clk_sel = ref_clk_sel;
@ -317,7 +213,7 @@ impl ClkgenCfgr {
}
/// Configures all clocks and return a clock configuration structure containing the final
/// frozen clock.
/// frozen clocks.
///
/// 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
@ -424,7 +320,9 @@ impl ClkgenCfgr {
}
}
}
None => self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit()),
None => {
self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit());
}
}
if self.clk_lost_detection {
@ -447,11 +345,22 @@ impl ClkgenCfgr {
.ctrl0()
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
Ok(Clocks {
sysclk: final_sysclk,
apb1: final_sysclk / 2,
apb2: final_sysclk / 4,
#[cfg(not(feature = "va41628"))]
adc_clk: 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.
// ADC clock (must be 2-12.5 MHz)
// 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)
let adc_clk = if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 {
self.clkgen
.ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) });
@ -461,14 +370,7 @@ impl ClkgenCfgr {
.ctrl1()
.modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) });
final_sysclk / 8
};
Ok(Clocks {
sysclk: final_sysclk,
apb1: final_sysclk / 2,
apb2: final_sysclk / 4,
adc_clk,
})
}
}
}
@ -483,37 +385,68 @@ pub struct Clocks {
sysclk: Hertz,
apb1: Hertz,
apb2: Hertz,
#[cfg(not(feature = "va41628"))]
adc_clk: Hertz,
}
impl Clocks {
/// Returns the frequency of the HBO clock
pub fn hbo(&self) -> Hertz {
pub const fn hbo(&self) -> Hertz {
HBO_FREQ
}
/// Returns the frequency of the APB0 which is equal to the system clock.
pub fn apb0(&self) -> Hertz {
///
/// This clock is the reference clock for the following peripherals:
///
/// - Ethernet
/// - SpaceWire
/// - IRQ Router
/// - DMA
/// - Clock Generator
pub const fn apb0(&self) -> Hertz {
self.sysclk()
}
/// Returns system clock divied by 2.
pub fn apb1(&self) -> Hertz {
///
/// This clock is the reference clock for the following peripherals:
///
/// - Timer[15:0]
/// - UART2
/// - SPI
/// - I2C
/// - CAN
/// - GPIO
/// - IOCONFIG
/// - System Config
pub const fn apb1(&self) -> Hertz {
self.apb1
}
/// Returns system clock divied by 4.
pub fn apb2(&self) -> Hertz {
///
/// This clock is the reference clock for the following peripherals:
///
/// - Timer[23:16]
/// - TRNG
/// - UART[1:0]
/// - DAC
/// - ADC
/// - Watchdog
/// - Utility
pub const fn apb2(&self) -> Hertz {
self.apb2
}
/// Returns the system (core) frequency
pub fn sysclk(&self) -> Hertz {
pub const fn sysclk(&self) -> Hertz {
self.sysclk
}
/// Returns the ADC clock frequency which has a separate divider.
pub fn adc_clk(&self) -> Hertz {
#[cfg(not(feature = "va41628"))]
pub const fn adc_clk(&self) -> Hertz {
self.adc_clk
}
}

View File

@ -1,9 +1,11 @@
//! 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 crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt},
pac,
};
use crate::{clock::Clocks, pac, PeripheralSelect, SyscfgExt as _};
pub type DacRegisterBlock = pac::dac0::RegisterBlock;

587
va416xx-hal/src/dma.rs Normal file
View File

@ -0,0 +1,587 @@
//! API for the DMA peripheral
//!
//! ## Examples
//!
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
use arbitrary_int::{u10, u2, u3, u4};
use crate::{enable_nvic_interrupt, pac, PeripheralClock, PeripheralSelect, SyscfgExt as _};
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
/// DMA cycle control values.
///
/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CycleControl {
/// Indicates that the data structure is invalid.
Stop = 0b000,
/// The controller must receive a new request prior to entering the arbitration
/// process, to enable the DMA cycle to complete. This means that the DMA will only
/// continue to do transfers as long as a trigger signal is still active. Therefore,
/// this should not be used for momentary triggers like a timer.
Basic = 0b001,
/// The controller automatically inserts a request for the appropriate channel during the
/// arbitration process. This means that the initial request is sufficient to enable the
/// DMA cycle to complete.
Auto = 0b010,
/// This is used to support continuous data flow. Both primary and alternate data structure
/// are used. The primary data structure is used first. When the first transfer is complete, an
/// interrupt can be generated, and the DMA switches to the alternate data structure. When the
/// second transfer is complete, the primary data structure is used. This pattern continues
/// until software disables the channel.
PingPong = 0b011,
MemScatterGatherPrimary = 0b100,
MemScatterGatherAlternate = 0b101,
PeriphScatterGatherPrimary = 0b110,
PeriphScatterGatherAlternate = 0b111,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddrIncrement {
Byte = 0b00,
Halfword = 0b01,
Word = 0b10,
None = 0b11,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DataSize {
Byte = 0b00,
Halfword = 0b01,
Word = 0b10,
}
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RPower {
EachTransfer = 0b0000,
Every2 = 0b0001,
Every4 = 0b0010,
Every8 = 0b0011,
Every16 = 0b0100,
Every32 = 0b0101,
Every64 = 0b0110,
Every128 = 0b0111,
Every256 = 0b1000,
Every512 = 0b1001,
Every1024Min = 0b1010,
Every1024 = 0b1111,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("Invalid DMA control block address")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCtrlBlockAddrError;
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelConfig {
#[bits(30..=31, rw)]
dest_inc: u2,
#[bits(28..=29, rw)]
dest_size: u2,
#[bits(26..=27, rw)]
src_inc: u2,
#[bits(24..=25, rw)]
src_size: u2,
#[bits(21..=23, rw)]
dest_prot_ctrl: u3,
#[bits(18..=20, rw)]
src_prot_ctrl: u3,
#[bits(14..=17, rw)]
r_power: u4,
#[bits(4..=13, rw)]
n_minus_1: u10,
#[bit(3, rw)]
next_useburst: bool,
#[bits(0..=2, rw)]
cycle_ctrl: u3,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaChannelControl {
pub src_end_ptr: u32,
pub dest_end_ptr: u32,
pub cfg: ChannelConfig,
padding: u32,
}
impl DmaChannelControl {
const fn new() -> Self {
Self {
src_end_ptr: 0,
dest_end_ptr: 0,
cfg: ChannelConfig::new_with_raw_value(0),
padding: 0,
}
}
}
impl Default for DmaChannelControl {
fn default() -> Self {
Self::new()
}
}
#[repr(C)]
#[repr(align(128))]
pub struct DmaCtrlBlock {
pub pri: [DmaChannelControl; 4],
pub alt: [DmaChannelControl; 4],
}
impl DmaCtrlBlock {
pub const fn new() -> Self {
Self {
pri: [DmaChannelControl::new(); 4],
alt: [DmaChannelControl::new(); 4],
}
}
}
impl Default for DmaCtrlBlock {
fn default() -> Self {
Self::new()
}
}
impl DmaCtrlBlock {
/// This function creates a DMA control block at the specified memory address.
///
/// 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.
/// For example, the control block can be placed in the SRAM1.
pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddrError> {
if addr & BASE_PTR_ADDR_MASK > 0 {
return Err(InvalidCtrlBlockAddrError);
}
let ctrl_block_ptr = addr as *mut DmaCtrlBlock;
unsafe { core::ptr::write(ctrl_block_ptr, DmaCtrlBlock::default()) }
Ok(ctrl_block_ptr)
}
}
pub struct Dma {
dma: pac::Dma,
ctrl_block: *mut DmaCtrlBlock,
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaTransferInitError {
#[error("source and destination buffer length mismatch: {src_len} != {dest_len}")]
SourceDestLenMissmatch { src_len: usize, dest_len: usize },
/// Overflow when calculating the source or destination end address.
#[error("address overflow")]
AddrOverflow,
/// Transfer size larger than 1024 units.
#[error("transfer size too large: {0}, 1024 is the allowed maximum")]
TransferSizeTooLarge(usize),
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaCfg {
pub bufferable: bool,
pub cacheable: bool,
pub privileged: bool,
}
pub struct DmaChannel {
channel: u8,
done_interrupt: pac::Interrupt,
active_interrupt: pac::Interrupt,
pub dma: pac::Dma,
pub ch_ctrl_pri: &'static mut DmaChannelControl,
pub ch_ctrl_alt: &'static mut DmaChannelControl,
}
impl DmaChannel {
#[inline(always)]
pub fn channel(&self) -> u8 {
self.channel
}
#[inline(always)]
pub fn enable(&mut self) {
self.dma
.chnl_enable_set()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn is_enabled(&mut self) -> bool {
((self.dma.chnl_enable_set().read().bits() >> self.channel) & 0b1) != 0
}
#[inline(always)]
pub fn disable(&mut self) {
self.dma
.chnl_enable_clr()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn trigger_with_sw_request(&mut self) {
self.dma
.chnl_sw_request()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn state_raw(&self) -> u8 {
self.dma.status().read().state().bits()
}
#[inline(always)]
pub fn select_primary_structure(&self) {
self.dma
.chnl_pri_alt_clr()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
#[inline(always)]
pub fn select_alternate_structure(&self) {
self.dma
.chnl_pri_alt_set()
.write(|w| unsafe { w.bits(1 << self.channel) });
}
/// Enables the DMA_DONE interrupt for the DMA channel.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
pub unsafe fn enable_done_interrupt(&mut self) {
enable_nvic_interrupt(self.done_interrupt);
}
/// Enables the DMA_ACTIVE interrupt for the DMA channel.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
pub unsafe fn enable_active_interrupt(&mut self) {
enable_nvic_interrupt(self.active_interrupt);
}
/// Prepares a 8-bit DMA transfer from memory to memory.
///
/// This function does not enable the DMA channel and interrupts and only prepares
/// the DMA control block parameters for the transfer. It configures the primary channel control
/// structure to perform the transfer.
///
/// 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
/// start the DMA transfer.
///
/// # 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,
source: &[u8],
dest: &mut [u8],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
len,
(source.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Byte,
AddrIncrement::Byte,
);
Ok(())
}
/// Prepares a 16-bit DMA transfer from memory to memory.
///
/// This function does not enable the DMA channel and interrupts and only prepares
/// the DMA control block parameters for the transfer. It configures the primary channel control
/// structure to perform the transfer.
///
/// 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
/// start the DMA transfer.
///
/// # 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,
source: &[u16],
dest: &mut [u16],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
len,
(source.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u16>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u16>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Halfword,
AddrIncrement::Halfword,
);
Ok(())
}
/// Prepares a 32-bit DMA transfer from memory to memory.
///
/// This function does not enable the DMA channel and interrupts and only prepares
/// the DMA control block parameters for the transfer. It configures the primary channel control
/// structure to perform the transfer.
///
/// 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
/// start the DMA transfer.
///
/// # 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,
source: &[u32],
dest: &mut [u32],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
len,
(source.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u32>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
(dest.as_ptr() as u32)
.checked_add(len as u32 * core::mem::size_of::<u32>() as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Word,
AddrIncrement::Word,
);
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(u3::new(CycleControl::Basic as u8));
self.ch_ctrl_pri
.cfg
.set_src_size(u2::new(DataSize::Byte as u8));
self.ch_ctrl_pri
.cfg
.set_src_inc(u2::new(AddrIncrement::Byte as u8));
self.ch_ctrl_pri
.cfg
.set_dest_size(u2::new(DataSize::Byte as u8));
self.ch_ctrl_pri
.cfg
.set_dest_inc(u2::new(AddrIncrement::None as u8));
self.ch_ctrl_pri.cfg.set_n_minus_1(u10::new(len as u16));
self.ch_ctrl_pri
.cfg
.set_r_power(u4::new(RPower::Every8 as u8));
self.select_primary_structure();
Ok(())
}
// 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
// 1 and the source and end pointer need to point to the last transfer address.
fn common_mem_transfer_checks(
src_len: usize,
dest_len: usize,
) -> Result<usize, DmaTransferInitError> {
if src_len != dest_len {
return Err(DmaTransferInitError::SourceDestLenMissmatch { src_len, dest_len });
}
if src_len > MAX_DMA_TRANSFERS_PER_CYCLE {
return Err(DmaTransferInitError::TransferSizeTooLarge(src_len));
}
Ok(src_len - 1)
}
fn generic_mem_to_mem_transfer_init(
&mut self,
n_minus_one: usize,
src_end_ptr: u32,
dest_end_ptr: u32,
data_size: DataSize,
addr_incr: AddrIncrement,
) {
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
self.ch_ctrl_pri
.cfg
.set_cycle_ctrl(u3::new(CycleControl::Auto as u8));
self.ch_ctrl_pri.cfg.set_src_size(u2::new(data_size as u8));
self.ch_ctrl_pri.cfg.set_src_inc(u2::new(addr_incr as u8));
self.ch_ctrl_pri.cfg.set_dest_size(u2::new(data_size as u8));
self.ch_ctrl_pri.cfg.set_dest_inc(u2::new(addr_incr as u8));
self.ch_ctrl_pri
.cfg
.set_n_minus_1(u10::new(n_minus_one as u16));
self.ch_ctrl_pri
.cfg
.set_r_power(u4::new(RPower::Every4 as u8));
self.select_primary_structure();
}
}
impl Dma {
/// Create a new DMA instance.
///
/// You can also place the [DmaCtrlBlock] statically using a global static mutable
/// instance and the [DmaCtrlBlock::new] const constructor This also allows to place the control
/// block in a memory section using the [link_section](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute)
/// attribute and then creating a mutable pointer to it using [core::ptr::addr_of_mut].
///
/// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
/// control block at a specific address.
pub fn new(
syscfg: &mut pac::Sysconfig,
dma: pac::Dma,
cfg: DmaCfg,
ctrl_block: *mut DmaCtrlBlock,
) -> Result<Self, InvalidCtrlBlockAddrError> {
// The conversion to u32 is safe here because we are on a 32-bit system.
let raw_addr = ctrl_block as u32;
if raw_addr & BASE_PTR_ADDR_MASK > 0 {
return Err(InvalidCtrlBlockAddrError);
}
syscfg.enable_peripheral_clock(PeripheralClock::Dma);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
let dma = Dma { dma, ctrl_block };
dma.dma
.ctrl_base_ptr()
.write(|w| unsafe { w.bits(raw_addr) });
dma.set_protection_bits(&cfg);
dma.enable();
Ok(dma)
}
#[inline(always)]
pub fn enable(&self) {
self.dma.cfg().write(|w| w.master_enable().set_bit());
}
#[inline(always)]
pub fn disable(&self) {
self.dma.cfg().write(|w| w.master_enable().clear_bit());
}
#[inline(always)]
pub fn set_protection_bits(&self, cfg: &DmaCfg) {
self.dma.cfg().write(|w| unsafe {
w.chnl_prot_ctrl().bits(
cfg.privileged as u8 | ((cfg.bufferable as u8) << 1) | ((cfg.cacheable as u8) << 2),
)
});
}
/// Split the DMA instance into four DMA channels which can be used individually. This allows
/// using the inidividual DMA channels in separate tasks.
pub fn split(self) -> (DmaChannel, DmaChannel, DmaChannel, DmaChannel) {
// Safety: The DMA channel API only operates on its respective channels.
(
DmaChannel {
channel: 0,
done_interrupt: pac::Interrupt::DMA_DONE0,
active_interrupt: pac::Interrupt::DMA_ACTIVE0,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[0] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[0] },
},
DmaChannel {
channel: 1,
done_interrupt: pac::Interrupt::DMA_DONE1,
active_interrupt: pac::Interrupt::DMA_ACTIVE1,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[1] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[1] },
},
DmaChannel {
channel: 2,
done_interrupt: pac::Interrupt::DMA_DONE2,
active_interrupt: pac::Interrupt::DMA_ACTIVE2,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[2] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[2] },
},
DmaChannel {
channel: 3,
done_interrupt: pac::Interrupt::DMA_DONE3,
active_interrupt: pac::Interrupt::DMA_ACTIVE3,
dma: unsafe { pac::Dma::steal() },
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[3] },
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[3] },
},
)
}
}

66
va416xx-hal/src/edac.rs Normal file
View File

@ -0,0 +1,66 @@
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()
});
}

View File

@ -0,0 +1,448 @@
//! # Async GPIO functionality for the VA416xx family.
//!
//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement
//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting
//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers
//! which must be provided for async support to work. However, it provides the
//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all
//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument.
//!
//! # Example
//!
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
use core::future::Future;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::digital::Wait;
use portable_atomic::AtomicBool;
use va416xx::{self as pac};
use crate::enable_nvic_interrupt;
use super::{
pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port,
NUM_PINS_PORT_A_TO_F,
};
static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_C: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_D: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_E: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static WAKERS_FOR_PORT_F: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
[const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_C: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_D: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_E: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
static EDGE_DETECTION_PORT_F: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
[const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("port G does not support async functionality")]
pub struct PortGDoesNotSupportAsyncError;
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AsyncDynPinError {
#[error("invalid pin type: {0}")]
InvalidPinType(#[from] InvalidPinTypeError),
#[error("port g does not support async functionality: {0}")]
PortGDoesNotSupportAsync(#[from] PortGDoesNotSupportAsyncError),
}
/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
///
/// This function should be called in all interrupt handlers which handle any GPIO interrupts
/// matching the [Port] argument.
/// The handler will wake the corresponding wakers for the pins that triggered an interrupts
/// as well as update the static edge detection structures. This allows the pin future tocomplete
/// complete async operations.
pub fn on_interrupt_for_async_gpio_for_port(
port: Port,
) -> Result<(), PortGDoesNotSupportAsyncError> {
let periphs = unsafe { pac::Peripherals::steal() };
let (irq_enb, edge_status, wakers, edge_detection) = match port {
Port::A => (
periphs.porta.irq_enb().read().bits(),
periphs.porta.edge_status().read().bits(),
&WAKERS_FOR_PORT_A,
&EDGE_DETECTION_PORT_A,
),
Port::B => (
periphs.portb.irq_enb().read().bits(),
periphs.portb.edge_status().read().bits(),
&WAKERS_FOR_PORT_B,
&EDGE_DETECTION_PORT_B,
),
Port::C => (
periphs.portc.irq_enb().read().bits(),
periphs.portc.edge_status().read().bits(),
&WAKERS_FOR_PORT_C,
&EDGE_DETECTION_PORT_C,
),
Port::D => (
periphs.portd.irq_enb().read().bits(),
periphs.portd.edge_status().read().bits(),
&WAKERS_FOR_PORT_D,
&EDGE_DETECTION_PORT_D,
),
Port::E => (
periphs.porte.irq_enb().read().bits(),
periphs.porte.edge_status().read().bits(),
&WAKERS_FOR_PORT_E,
&EDGE_DETECTION_PORT_E,
),
Port::F => (
periphs.portf.irq_enb().read().bits(),
periphs.portf.edge_status().read().bits(),
&WAKERS_FOR_PORT_F,
&EDGE_DETECTION_PORT_F,
),
Port::G => return Err(PortGDoesNotSupportAsyncError),
};
on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection);
Ok(())
}
#[inline]
fn on_interrupt_for_port(
mut irq_enb: u32,
edge_status: u32,
wakers: &'static [AtomicWaker],
edge_detection: &'static [AtomicBool],
) {
while irq_enb != 0 {
let bit_pos = irq_enb.trailing_zeros() as usize;
let bit_mask = 1 << bit_pos;
wakers[bit_pos].wake();
if edge_status & bit_mask != 0 {
edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed);
// Clear the processed bit
irq_enb &= !bit_mask;
}
}
}
/// Input pin future which implements the [Future] trait.
///
/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this
/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this
/// struture is granted to allow writing custom async structures.
pub struct InputPinFuture {
pin_id: DynPinId,
waker_group: &'static [AtomicWaker],
edge_detection_group: &'static [AtomicBool],
}
impl InputPinFuture {
pub fn new_with_dyn_pin(
pin: &mut DynPin,
edge: InterruptEdge,
) -> Result<Self, AsyncDynPinError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()).into());
}
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError.into());
}
let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
edge_detection_group[pin.id().num() as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
// Unwraps okay, checked for PORT G previously
pin.configure_edge_interrupt(edge).unwrap();
unsafe { enable_nvic_interrupt(pin.irq_id().unwrap()) };
pin.enable_interrupt();
Ok(Self {
pin_id: pin.id(),
waker_group,
edge_detection_group,
})
}
pub fn new_with_pin<I: PinId, C: InputConfig>(
pin: &mut Pin<I, pin::Input<C>>,
edge: InterruptEdge,
) -> Result<Self, PortGDoesNotSupportAsyncError> {
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError);
}
let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
edge_detection_group[pin.id().num() as usize]
.store(false, core::sync::atomic::Ordering::Relaxed);
// Unwraps okay, checked for PORT G previously
pin.configure_edge_interrupt(edge);
unsafe { enable_nvic_interrupt(I::IRQ.unwrap()) };
pin.enable_interrupt();
Ok(Self {
pin_id: pin.id(),
waker_group,
edge_detection_group,
})
}
#[inline]
pub fn pin_group_to_waker_and_edge_detection_group(
group: Port,
) -> (&'static [AtomicWaker], &'static [AtomicBool]) {
match group {
Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()),
Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()),
Port::C => (WAKERS_FOR_PORT_C.as_ref(), EDGE_DETECTION_PORT_C.as_ref()),
Port::D => (WAKERS_FOR_PORT_D.as_ref(), EDGE_DETECTION_PORT_D.as_ref()),
Port::E => (WAKERS_FOR_PORT_E.as_ref(), EDGE_DETECTION_PORT_E.as_ref()),
Port::F => (WAKERS_FOR_PORT_F.as_ref(), EDGE_DETECTION_PORT_F.as_ref()),
_ => panic!("unexpected pin group G"),
}
}
}
impl Drop for InputPinFuture {
fn drop(&mut self) {
// The API ensures that we actually own the pin, so stealing it here is okay.
unsafe { DynPin::steal(self.pin_id) }.disable_interrupt();
}
}
impl Future for InputPinFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let idx = self.pin_id.num() as usize;
self.waker_group[idx].register(cx.waker());
if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
return core::task::Poll::Ready(());
}
core::task::Poll::Pending
}
}
pub struct InputDynPinAsync {
pin: DynPin,
}
impl InputDynPinAsync {
/// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be
/// passed as well and is used to route and enable the interrupt.
///
/// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// for the asynchronous functionality to work.
pub fn new(pin: DynPin) -> Result<Self, AsyncDynPinError> {
if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode()).into());
}
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError.into());
}
Ok(Self { pin })
}
/// Asynchronously wait until the pin is high.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut =
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
if self.pin.is_high().unwrap() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin is low.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_low(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut =
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
if self.pin.is_low().unwrap() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin sees a falling edge.
pub async fn wait_for_falling_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::HighToLow)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees a rising edge.
pub async fn wait_for_rising_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::LowToHigh)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::BothEdges)
.unwrap()
.await;
}
pub fn release(self) -> DynPin {
self.pin
}
}
impl embedded_hal::digital::ErrorType for InputDynPinAsync {
type Error = core::convert::Infallible;
}
impl Wait for InputDynPinAsync {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
self.wait_for_high().await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
self.wait_for_low().await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_rising_edge().await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_falling_edge().await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_any_edge().await;
Ok(())
}
}
pub struct InputPinAsync<I: PinId, C: InputConfig> {
pin: Pin<I, pin::Input<C>>,
}
impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
/// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be
/// passed as well and is used to route and enable the interrupt.
///
/// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// for the asynchronous functionality to work.
pub fn new(pin: Pin<I, pin::Input<C>>) -> Result<Self, PortGDoesNotSupportAsyncError> {
if pin.id().port() == Port::G {
return Err(PortGDoesNotSupportAsyncError);
}
Ok(Self { pin })
}
/// Asynchronously wait until the pin is high.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_high(&mut self) {
// Unwrap okay, checked pin in constructor.
let fut = InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
if self.pin.is_high() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin is low.
///
/// This returns immediately if the pin is already high.
pub async fn wait_for_low(&mut self) {
let fut = InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
if self.pin.is_low() {
return;
}
fut.await;
}
/// Asynchronously wait until the pin sees falling edge.
pub async fn wait_for_falling_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::HighToLow)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees rising edge.
pub async fn wait_for_rising_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::LowToHigh)
.unwrap()
.await;
}
/// Asynchronously wait until the pin sees any edge (either rising or falling).
pub async fn wait_for_any_edge(&mut self) {
// Unwrap okay, checked pin in constructor.
InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::BothEdges)
.unwrap()
.await;
}
pub fn release(self) -> Pin<I, pin::Input<C>> {
self.pin
}
}
impl<I: PinId, C: InputConfig> embedded_hal::digital::ErrorType for InputPinAsync<I, C> {
type Error = core::convert::Infallible;
}
impl<I: PinId, C: InputConfig> Wait for InputPinAsync<I, C> {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
self.wait_for_high().await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
self.wait_for_low().await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_rising_edge().await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_falling_edge().await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
self.wait_for_any_edge().await;
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -22,55 +22,52 @@
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
#[derive(Debug, PartialEq, Eq)]
//==================================================================================================
// Errors, Definitions and Constants
//==================================================================================================
pub const NUM_PINS_PORT_A_TO_F: usize = 16;
pub const NUM_PINS_PORT_G: usize = 8;
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A_TO_F * 6 + NUM_PINS_PORT_G;
pub const NUM_GPIO_PINS_WITH_IRQ: usize = NUM_GPIO_PINS - NUM_PINS_PORT_G;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("pin is masked")]
pub struct IsMaskedError;
macro_rules! common_reg_if_functions {
() => {
paste::paste!(
#[inline]
pub fn datamask(&self) -> bool {
self.regs.datamask()
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Port {
A,
B,
C,
D,
E,
F,
G,
}
#[inline]
pub fn clear_datamask(self) -> Self {
self.regs.clear_datamask();
self
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptEdge {
HighToLow,
LowToHigh,
BothEdges,
}
#[inline]
pub fn set_datamask(self) -> Self {
self.regs.set_datamask();
self
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptLevel {
Low = 0,
High = 1,
}
#[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();
}
);
};
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PinState {
Low = 0,
High = 1,
}
pub mod pin;
@ -79,4 +76,5 @@ pub use pin::*;
pub mod dynpin;
pub use dynpin::*;
mod reg;
pub mod asynch;
pub use asynch::*;

View File

@ -19,7 +19,7 @@
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
//! [`PinMode`].
//!
//! ```
//! ```ignore
//! pub struct Pin<I, M>
//! where
//! I: PinId,
@ -49,14 +49,14 @@
//! within the [PinsA] struct can be moved out and used individually.
//!
//!
//! ```no_run
//! ```no_run,ignore
//! let mut peripherals = Peripherals::take().unwrap();
//! let pinsa = PinsA::new(peripherals.porta);
//! ```
//!
//! Pins can be converted between modes using several different methods.
//!
//! ```no_run
//! ```no_run,ignore
//! // 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
@ -68,42 +68,19 @@
//! # 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`].
//! in the corresponding [`PinMode`]s, namely: [embedded_hal::digital::InputPin],
//! [embedded_hal::digital::OutputPin] and [embedded_hal::digital::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 va416xx::{self as pac, Porta, Portb, Portc, Portd, Porte, Portf, Portg};
use super::{
reg::RegisterInterface, DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode,
DynAlternate, DynInput, DynOutput, DynPin, DynPinId, DynPinMode, InputPinAsync, InterruptEdge,
InterruptLevel, PinState, Port, PortGDoesNotSupportAsyncError,
};
//==================================================================================================
// 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
//==================================================================================================
@ -292,20 +269,24 @@ impl<C: AlternateConfig> PinMode for Alternate<C> {
pub trait PinId: Sealed {
/// Corresponding [DynPinId]
const DYN: DynPinId;
const IRQ: Option<pac::Interrupt>;
}
macro_rules! pin_id {
($Group:ident, $Id:ident, $NUM:literal) => {
($Port:ident, $Id:ident, $NUM:literal, $Irq:expr, $(, $meta: meta)?) => {
// Need paste macro to use ident in doc attribute
paste::paste! {
$(#[$meta])?
#[doc = "Pin ID representing pin " $Id]
pub enum $Id {}
$(#[$meta])?
impl Sealed for $Id {}
$(#[$meta])?
impl PinId for $Id {
const DYN: DynPinId = DynPinId {
group: DynGroup::$Group,
num: $NUM,
};
const DYN: DynPinId = DynPinId::new(Port::$Port, $NUM);
const IRQ: Option<pac::Interrupt> = $Irq;
}
}
};
@ -316,10 +297,10 @@ macro_rules! pin_id {
//==================================================================================================
/// A type-level GPIO pin, parameterized by [`PinId`] and [`PinMode`] types
#[derive(Debug)]
pub struct Pin<I: PinId, M: PinMode> {
pub(in crate::gpio) regs: Registers<I>,
mode: PhantomData<M>,
inner: DynPin,
mode: PhantomData<(I, M)>,
}
impl<I: PinId, M: PinMode> Pin<I, M> {
@ -331,38 +312,48 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
/// 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> {
pub(crate) const unsafe fn new() -> Pin<I, M> {
Pin {
regs: Registers::new(),
inner: DynPin::new(I::DYN, M::DYN),
mode: PhantomData,
}
}
#[inline]
pub const fn id(&self) -> DynPinId {
self.inner.id()
}
#[inline(always)]
pub const fn irq_id(&self) -> Option<pac::Interrupt> {
I::IRQ
}
/// 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>();
self.inner.change_mode(N::DYN);
}
// 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
/// Configure the pin for function select 1. See Programmer Guide p. 286 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
/// Configure the pin for function select 2. See Programmer Guide p. 286 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
/// Configure the pin for function select 3. See Programmer Guide p. 286 for the function table
#[inline]
pub fn into_funsel_3(self) -> Pin<I, AltFunc3> {
self.into_mode()
@ -404,26 +395,76 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
self.into_mode()
}
common_reg_if_functions!();
#[inline]
pub(crate) fn _set_high(&mut self) {
self.regs.write_pin(true)
pub fn is_low(&self) -> bool {
!self.inner.read_pin()
}
#[inline]
pub(crate) fn _set_low(&mut self) {
self.regs.write_pin(false)
pub fn is_high(&self) -> bool {
self.inner.read_pin()
}
#[inline]
pub(crate) fn _is_low(&self) -> bool {
!self.regs.read_pin()
pub fn datamask(&self) -> bool {
self.inner.datamask()
}
#[inline]
pub(crate) fn _is_high(&self) -> bool {
self.regs.read_pin()
pub fn clear_datamask(&mut self) {
self.inner.clear_datamask()
}
#[inline]
pub fn set_datamask(&mut self) {
self.inner.set_datamask()
}
#[inline]
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.inner.is_high_masked()
}
#[inline]
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.inner.is_low_masked()
}
#[inline]
pub fn downgrade(self) -> DynPin {
self.inner
}
// Those only serve for the embedded HAL implementations which have different mutability.
#[inline]
fn is_low_mut(&mut self) -> bool {
self.is_low()
}
#[inline]
fn is_high_mut(&mut self) -> bool {
self.is_high()
}
#[inline]
pub fn enable_interrupt(&mut self) {
self.inner.enable_interrupt();
}
#[inline]
pub fn disable_interrupt(&mut self) {
self.inner.disable_interrupt();
}
/// Configure the pin for an edge interrupt but does not enable the interrupt.
pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) {
self.inner.configure_edge_interrupt(edge_type).unwrap();
}
/// Configure the pin for a level interrupt but does not enable the interrupt.
pub fn configure_level_interrupt(&mut self, level_type: InterruptLevel) {
self.inner.configure_level_interrupt(level_type).unwrap();
}
}
@ -515,58 +556,61 @@ impl<P: AnyPin> AsMut<P> for SpecificPin<P> {
//==================================================================================================
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
/// Convert the pin into an async pin. The pin can be converted back by calling
/// [InputPinAsync::release]
pub fn into_async_input(self) -> Result<InputPinAsync<I, C>, PortGDoesNotSupportAsyncError> {
InputPinAsync::new(self)
}
}
impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
/// See p.53 of the programmers guide for more information.
#[inline]
pub fn set_high(&mut self) {
self.inner.write_pin(true)
}
#[inline]
pub fn set_low(&mut self) {
self.inner.write_pin(false)
}
#[inline]
pub fn toggle(&mut self) {
self.inner.toggle().unwrap()
}
#[inline]
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.inner.set_high_masked()
}
#[inline]
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.inner.set_low_masked()
}
/// 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
pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
self.inner.configure_delay(delay_1, delay_2).unwrap();
}
/// 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
pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) {
self.inner
.configure_pulse_mode(enable, default_state)
.unwrap();
}
}
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
pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
self.inner.configure_filter_type(filter, clksel).unwrap();
}
}
@ -574,7 +618,7 @@ impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
// Embedded HAL traits
//==================================================================================================
impl<I, M> ErrorType for Pin<I, M>
impl<I, M> embedded_hal::digital::ErrorType for Pin<I, M>
where
I: PinId,
M: PinMode,
@ -582,104 +626,69 @@ where
type Error = Infallible;
}
impl<I: PinId, C: OutputConfig> OutputPin for Pin<I, Output<C>> {
impl<I: PinId, C: OutputConfig> embedded_hal::digital::OutputPin for Pin<I, Output<C>> {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self._set_high();
self.set_high();
Ok(())
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self._set_low();
self.set_low();
Ok(())
}
}
impl<I, C> InputPin for Pin<I, Input<C>>
impl<I, C> embedded_hal::digital::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())
Ok(self.is_high_mut())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
Ok(self.is_low_mut())
}
}
impl<I, C> StatefulOutputPin for Pin<I, Output<C>>
impl<I, C> embedded_hal::digital::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())
Ok(self.is_high())
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
Ok(self.is_low())
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
self.toggle();
Ok(())
}
}
impl<I, C> InputPin for Pin<I, Output<C>>
impl<I, C> embedded_hal::digital::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())
Ok(self.is_high_mut())
}
#[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);
Ok(self.is_low_mut())
}
}
@ -689,13 +698,14 @@ impl<I: PinId> Registers<I> {
macro_rules! pins {
(
$Port:ident, $PinsName:ident, $($Id:ident,)+,
$Port:ident, $PinsName:ident, $($Id:ident $(, $meta:meta)?)+,
) => {
paste::paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName {
port: $Port,
$(
$(#[$meta])?
#[doc = "Pin " $Id]
pub [<$Id:lower>]: Pin<$Id, Reset>,
)+
@ -718,6 +728,7 @@ macro_rules! pins {
port,
// Safe because we only create one `Pin` per `PinId`
$(
$(#[$meta])?
[<$Id:lower>]: unsafe { Pin::new() },
)+
}
@ -739,18 +750,31 @@ macro_rules! pins {
}
}
macro_rules! declare_pins {
macro_rules! declare_pins_with_irq {
(
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+]
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
) => {
pins!($Port, $PinsName, $($Id,)+,);
pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
$(
pin_id!($Group, $Id, $NUM);
paste::paste! {
pin_id!($Group, $Id, $NUM, Some(pac::Interrupt::[<$Port:upper $NUM>]), $(, $meta)?);
}
)+
}
}
declare_pins!(
macro_rules! declare_pins {
(
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
) => {
pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
$(
pin_id!($Group, $Id, $NUM, None, $(, $meta)?);
)+
}
}
declare_pins_with_irq!(
A,
PinsA,
Porta,
@ -770,11 +794,11 @@ declare_pins!(
(PA12, 12),
(PA13, 13),
(PA14, 14),
(PA15, 15),
(PA15, 15)
]
);
declare_pins!(
declare_pins_with_irq!(
B,
PinsB,
Portb,
@ -784,21 +808,21 @@ declare_pins!(
(PB2, 2),
(PB3, 3),
(PB4, 4),
(PB5, 5),
(PB6, 6),
(PB7, 7),
(PB8, 8),
(PB9, 9),
(PB10, 10),
(PB11, 11),
(PB5, 5, cfg(not(feature = "va41628"))),
(PB6, 6, cfg(not(feature = "va41628"))),
(PB7, 7, cfg(not(feature = "va41628"))),
(PB8, 8, cfg(not(feature = "va41628"))),
(PB9, 9, cfg(not(feature = "va41628"))),
(PB10, 10, cfg(not(feature = "va41628"))),
(PB11, 11, cfg(not(feature = "va41628"))),
(PB12, 12),
(PB13, 13),
(PB14, 14),
(PB15, 15),
(PB15, 15)
]
);
declare_pins!(
declare_pins_with_irq!(
C,
PinsC,
Portc,
@ -816,37 +840,37 @@ declare_pins!(
(PC10, 10),
(PC11, 11),
(PC12, 12),
(PC13, 13),
(PC13, 13, cfg(not(feature = "va41628"))),
(PC14, 14),
(PC15, 15),
(PC15, 15, cfg(not(feature = "va41628")))
]
);
declare_pins!(
declare_pins_with_irq!(
D,
PinsD,
Portd,
[
(PD0, 0),
(PD1, 1),
(PD2, 2),
(PD3, 3),
(PD4, 4),
(PD5, 5),
(PD6, 6),
(PD7, 7),
(PD8, 8),
(PD9, 9),
(PD0, 0, cfg(not(feature = "va41628"))),
(PD1, 1, cfg(not(feature = "va41628"))),
(PD2, 2, cfg(not(feature = "va41628"))),
(PD3, 3, cfg(not(feature = "va41628"))),
(PD4, 4, cfg(not(feature = "va41628"))),
(PD5, 5, cfg(not(feature = "va41628"))),
(PD6, 6, cfg(not(feature = "va41628"))),
(PD7, 7, cfg(not(feature = "va41628"))),
(PD8, 8, cfg(not(feature = "va41628"))),
(PD9, 9, cfg(not(feature = "va41628"))),
(PD10, 10),
(PD11, 11),
(PD12, 12),
(PD13, 13),
(PD14, 14),
(PD15, 15),
(PD15, 15)
]
);
declare_pins!(
declare_pins_with_irq!(
E,
PinsE,
Porte,
@ -861,36 +885,36 @@ declare_pins!(
(PE7, 7),
(PE8, 8),
(PE9, 9),
(PE10, 10),
(PE11, 11),
(PE10, 10, cfg(not(feature = "va41628"))),
(PE11, 11, cfg(not(feature = "va41628"))),
(PE12, 12),
(PE13, 13),
(PE14, 14),
(PE15, 15),
(PE15, 15)
]
);
declare_pins!(
declare_pins_with_irq!(
F,
PinsF,
Portf,
[
(PF0, 0),
(PF1, 1),
(PF2, 2),
(PF3, 3),
(PF4, 4),
(PF5, 5),
(PF6, 6),
(PF7, 7),
(PF8, 8),
(PF2, 2, cfg(not(feature = "va41628"))),
(PF3, 3, cfg(not(feature = "va41628"))),
(PF4, 4, cfg(not(feature = "va41628"))),
(PF5, 5, cfg(not(feature = "va41628"))),
(PF6, 6, cfg(not(feature = "va41628"))),
(PF7, 7, cfg(not(feature = "va41628"))),
(PF8, 8, cfg(not(feature = "va41628"))),
(PF9, 9),
(PF10, 10),
(PF10, 10, cfg(not(feature = "va41628"))),
(PF11, 11),
(PF12, 12),
(PF13, 13),
(PF14, 14),
(PF15, 15),
(PF15, 15)
]
);
@ -906,6 +930,6 @@ declare_pins!(
(PG4, 4),
(PG5, 5),
(PG6, 6),
(PG7, 7),
(PG7, 7)
]
);

View File

@ -1,387 +0,0 @@
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()));
}
}
}
}

View File

@ -4,11 +4,7 @@
//!
//! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
use crate::{
clock::{Clocks, PeripheralSelect},
pac,
prelude::SyscfgExt,
time::Hertz,
typelevel::Sealed,
clock::Clocks, enable_peripheral_clock, pac, time::Hertz, typelevel::Sealed, PeripheralSelect,
};
use core::{marker::PhantomData, ops::Deref};
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
@ -28,37 +24,42 @@ pub enum FifoEmptyMode {
EndTransaction = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ClockTooSlowForFastI2c;
#[error("clock too slow for fast I2C mode")]
pub struct ClockTooSlowForFastI2cError;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("invalid timing parameters")]
pub struct InvalidTimingParamsError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
InvalidTimingParams,
#[error("arbitration lost")]
ArbitrationLost,
#[error("nack address")]
NackAddr,
/// Data not acknowledged in write operation
#[error("data not acknowledged in write operation")]
NackData,
/// Not enough data received in read operation
#[error("insufficient data received")]
InsufficientDataReceived,
/// Number of bytes in transfer too large (larger than 0x7fe)
#[error("data too large (larger than 0x7fe)")]
DataTooLarge,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InitError {
/// Wrong address used in constructor
#[error("wrong address mode")]
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)
}
#[error("clock too slow for fast I2C mode: {0}")]
ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
}
impl embedded_hal::i2c::Error for Error {
@ -71,7 +72,7 @@ impl embedded_hal::i2c::Error for Error {
Error::NackData => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
}
Error::DataTooLarge | Error::InsufficientDataReceived | Error::InvalidTimingParams => {
Error::DataTooLarge | Error::InsufficientDataReceived => {
embedded_hal::i2c::ErrorKind::Other
}
}
@ -153,9 +154,12 @@ impl Instance for pac::I2c2 {
// Config
//==================================================================================================
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TrTfThighTlow(u8, u8, u8, u8);
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TimingCfg {
// 4 bit max width
tr: u8,
@ -179,7 +183,7 @@ impl TimingCfg {
pub fn new(
first_16_bits: TrTfThighTlow,
second_16_bits: TsuStoTsuStaThdStaTBuf,
) -> Result<Self, Error> {
) -> Result<Self, InvalidTimingParamsError> {
if first_16_bits.0 > 0xf
|| first_16_bits.1 > 0xf
|| first_16_bits.2 > 0xf
@ -189,7 +193,7 @@ impl TimingCfg {
|| second_16_bits.2 > 0xf
|| second_16_bits.3 > 0xf
{
return Err(Error::InvalidTimingParams);
return Err(InvalidTimingParamsError);
}
Ok(TimingCfg {
tr: first_16_bits.0,
@ -204,13 +208,13 @@ impl TimingCfg {
}
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.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)
}
}
@ -230,6 +234,7 @@ impl Default for TimingCfg {
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MasterConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
@ -256,6 +261,8 @@ impl Default for MasterConfig {
impl Sealed for MasterConfig {}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SlaveConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
@ -313,13 +320,12 @@ impl<I2C> I2cBase<I2C> {
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);
) -> Result<Self, ClockTooSlowForFastI2cError> {
enable_peripheral_clock(I2c::PERIPH_SEL);
let mut i2c_base = I2cBase {
i2c,
@ -379,7 +385,7 @@ impl<I2c: Instance> I2cBase<I2c> {
if let Some(max_words) = max_words {
self.i2c
.s0_maxwords()
.write(|w| unsafe { w.bits(1 << 31 | max_words as u32) });
.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
@ -396,12 +402,12 @@ impl<I2c: Instance> I2cBase<I2c> {
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) })
.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) })
.write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) });
}
}
@ -421,23 +427,26 @@ impl<I2c: Instance> I2cBase<I2c> {
});
}
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2c> {
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
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);
return Err(ClockTooSlowForFastI2cError);
}
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> {
pub fn cfg_clk_scale(
&mut self,
speed_mode: I2cSpeed,
) -> Result<(), ClockTooSlowForFastI2cError> {
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) });
.write(|w| unsafe { w.bits(((speed_mode as u32) << 31) | clk_div as u32) });
Ok(())
}
@ -468,13 +477,12 @@ pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
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> {
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cMaster {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, Some(&cfg), None)?,
i2c_base: I2cBase::new(i2c, clocks, speed_mode, Some(&cfg), None)?,
addr: PhantomData,
}
.enable_master())
@ -729,13 +737,12 @@ pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
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> {
) -> Result<Self, ClockTooSlowForFastI2cError> {
Ok(I2cSlave {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, None, Some(&cfg))?,
i2c_base: I2cBase::new(i2c, clocks, speed_mode, None, Some(&cfg))?,
addr: PhantomData,
}
.enable_slave())
@ -876,7 +883,6 @@ 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,
@ -884,18 +890,17 @@ impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
if let I2cAddress::TenBit(_) = cfg.addr {
return Err(InitError::WrongAddrMode);
}
Ok(Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)?)
Ok(Self::new_generic(i2c, 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)
) -> Result<Self, ClockTooSlowForFastI2cError> {
Self::new_generic(i2c, cfg, clocks, speed_mode)
}
}

View File

@ -0,0 +1,23 @@
//! IRQ Router peripheral support.
use crate::{pac, PeripheralSelect, SyscfgExt as _};
/// 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(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) {
sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter);
sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter);
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);
}
}

View File

@ -1,18 +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]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(test)]
extern crate std;
use gpio::Port;
pub use va416xx as device;
pub use va416xx as pac;
pub mod prelude;
pub mod adc;
pub mod can;
pub mod clock;
pub mod dac;
pub mod dma;
pub mod edac;
pub mod gpio;
pub mod i2c;
pub mod irq_router;
pub mod pwm;
pub mod spi;
pub mod time;
@ -21,7 +52,94 @@ pub mod typelevel;
pub mod uart;
pub mod wdt;
#[cfg(feature = "va41630")]
pub mod nvm;
#[cfg(not(feature = "va41628"))]
pub mod adc;
#[cfg(not(feature = "va41628"))]
pub mod dac;
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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;
#[inline(always)]
pub fn enable_peripheral_clock(clock: PeripheralSelect) {
// Safety: Only bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
}
#[inline(always)]
pub fn disable_peripheral_clock(clock: PeripheralSelect) {
// Safety: Only bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
}
#[inline(always)]
pub fn assert_periph_reset(periph: PeripheralSelect) {
// Safety: Only reset bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) });
}
#[inline(always)]
pub fn deassert_periph_reset(periph: PeripheralSelect) {
// Safety: Only rest bit of peripheral is modified.
unsafe { pac::Sysconfig::steal() }
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) });
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(periph: PeripheralSelect) {
assert_periph_reset(periph);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_periph_reset(periph);
}
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FunSel {
Sel0 = 0b00,
Sel1 = 0b01,
@ -29,20 +147,91 @@ pub enum FunSel {
Sel3 = 0b11,
}
#[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
/// that most of the structures and APIs in this library will automatically correctly configure
/// the pin or statically expect the correct pin type.
#[inline]
pub fn port_function_select(
ioconfig: &mut pac::Ioconfig,
port: Port,
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: 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(clock)
}
#[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
disable_peripheral_clock(clock)
}
#[inline(always)]
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
assert_periph_reset(clock)
}
#[inline(always)]
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
deassert_periph_reset(clock)
}
#[inline(always)]
fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
assert_periph_reset_for_two_cycles(periph)
}
}
/// Enable a specific interrupt using the NVIC peripheral.
///
/// # Safety
///
/// This function is `unsafe` because it can break mask-based critical sections.
#[inline]
pub unsafe fn enable_interrupt(irq: pac::Interrupt) {
unsafe {
cortex_m::peripheral::NVIC::unmask(irq);
}
pub unsafe fn enable_nvic_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::unmask(irq);
}
/// Disable a specific interrupt using the NVIC peripheral.
#[inline]
pub fn disable_interrupt(irq: pac::Interrupt) {
pub fn disable_nvic_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::mask(irq);
}

274
va416xx-hal/src/nvm.rs Normal file
View File

@ -0,0 +1,274 @@
//! 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 crate::clock::{Clocks, SyscfgExt};
use crate::pac;
use crate::spi::{
mode_to_cpo_cph_bit, spi_clk_config_from_div, Instance, 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(syscfg: &mut pac::Sysconfig, spi: pac::Spi3, _clocks: &Clocks) -> Self {
crate::clock::enable_peripheral_clock(syscfg, pac::Spi3::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(pac::Spi3::PERIPH_SEL);
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, sys_cfg: &mut pac::Sysconfig) {
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);
crate::clock::disable_peripheral_clock(sys_cfg, pac::Spi3::PERIPH_SEL);
}
/// This function calls [Self::shutdown] and gives back the peripheral structure.
pub fn release(mut self, sys_cfg: &mut pac::Sysconfig) -> pac::Spi3 {
self.shutdown(sys_cfg);
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(unsafe { &mut pac::Sysconfig::steal() });
}
}
}

View File

@ -1,4 +1,5 @@
//! Prelude
pub use crate::clock::{ClkgenExt, SyscfgExt};
pub use crate::clock::ClkgenExt;
pub use crate::SyscfgExt;
pub use fugit::ExtU32 as _;
pub use fugit::RateExtU32 as _;

View File

@ -9,12 +9,16 @@ use core::convert::Infallible;
use core::marker::PhantomData;
use crate::pac;
use crate::time::Hertz;
pub use crate::timer::ValidTim;
use crate::timer::{TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTimAndPin};
use crate::{clock::Clocks, gpio::DynPinId};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX;
pub struct PwmBase {
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct PwmCommon {
clock: Hertz,
/// For PWMB, this is the upper limit
current_duty: u16,
@ -32,123 +36,13 @@ enum StatusSelPwm {
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,
inner: ReducedPwmPin<Mode>,
mode: PhantomData<Mode>,
}
@ -164,13 +58,17 @@ where
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),
},
inner: ReducedPwmPin::<Mode>::new(
Tim::ID,
Pin::DYN,
PwmCommon {
clock: Tim::clock(clocks),
current_duty: 0,
current_lower_limit: 0,
current_period: initial_period.into(),
current_rst_val: 0,
},
),
reg: unsafe { TimAndPinRegister::new(pin_and_tim.0, pin_and_tim.1) },
mode: PhantomData,
};
@ -182,11 +80,53 @@ where
pin
}
pub fn downgrade(self) -> ReducedPwmPin<Mode> {
self.inner
}
pub fn release(self) -> (Pin, Tim) {
self.reg.release()
}
pwm_common_func!();
#[inline]
fn enable_pwm_a(&mut self) {
self.inner.enable_pwm_a();
}
#[inline]
fn enable_pwm_b(&mut self) {
self.inner.enable_pwm_b();
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.inner.get_period()
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.inner.set_period(period);
}
#[inline]
pub fn disable(&mut self) {
self.inner.disable();
}
#[inline]
pub fn enable(&mut self) {
self.inner.enable();
}
#[inline]
pub fn period(&self) -> Hertz {
self.inner.period()
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.inner.duty()
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for PwmPin<Pin, Tim, PwmB>
@ -196,7 +136,7 @@ where
fn from(other: PwmPin<Pin, Tim, PwmA>) -> Self {
let mut pwmb = Self {
reg: other.reg,
pwm_base: other.pwm_base,
inner: other.inner.into(),
mode: PhantomData,
};
pwmb.enable_pwm_b();
@ -211,7 +151,7 @@ where
fn from(other: PwmPin<PIN, TIM, PwmB>) -> Self {
let mut pwmb = Self {
reg: other.reg,
pwm_base: other.pwm_base,
inner: other.inner.into(),
mode: PhantomData,
};
pwmb.enable_pwm_a();
@ -259,33 +199,105 @@ where
/// Reduced version where type information is deleted
pub struct ReducedPwmPin<Mode = PwmA> {
reg: TimDynRegister,
pwm_base: PwmBase,
pin_id: DynPinId,
dyn_reg: TimDynRegister,
common: PwmCommon,
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,
impl<Mode> ReducedPwmPin<Mode> {
pub(crate) fn new(tim_id: u8, pin_id: DynPinId, common: PwmCommon) -> Self {
Self {
dyn_reg: TimDynRegister { tim_id, pin_id },
common,
mode: PhantomData,
}
}
#[inline]
fn enable_pwm_a(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
}
#[inline]
fn enable_pwm_b(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.common.current_period
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.common.current_period = period.into();
// Avoid division by 0
if self.common.current_period.raw() == 0 {
return;
}
self.common.current_rst_val = self.common.clock.raw() / self.common.current_period.raw();
self.dyn_reg
.reg_block()
.rst_value()
.write(|w| unsafe { w.bits(self.common.current_rst_val) });
}
#[inline]
pub fn disable(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
#[inline]
pub fn enable(&mut self) {
self.dyn_reg
.reg_block()
.ctrl()
.modify(|_, w| w.enable().set_bit());
}
#[inline]
pub fn period(&self) -> Hertz {
self.common.current_period
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.common.current_duty
}
}
impl<MODE> ReducedPwmPin<MODE> {
pwm_common_func!();
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for ReducedPwmPin<PwmA>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(value: PwmPin<Pin, Tim, PwmA>) -> Self {
value.downgrade()
}
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmB>> for ReducedPwmPin<PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(value: PwmPin<Pin, Tim, PwmB>) -> Self {
value.downgrade()
}
}
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,
dyn_reg: other.dyn_reg,
common: other.common,
mode: PhantomData,
};
pwmb.enable_pwm_b();
@ -296,9 +308,8 @@ impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<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,
dyn_reg: other.dyn_reg,
common: other.common,
mode: PhantomData,
};
pwmb.enable_pwm_a();
@ -310,15 +321,83 @@ impl From<ReducedPwmPin<PwmB>> for ReducedPwmPin<PwmA> {
// PWMB implementations
//==================================================================================================
impl<PIN: TimPin, TIM: ValidTim> PwmPin<PIN, TIM, PwmB>
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
where
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pwmb_func!();
pub fn pwmb_lower_limit(&self) -> u16 {
self.inner.pwmb_lower_limit()
}
pub fn pwmb_upper_limit(&self) -> u16 {
self.inner.pwmb_upper_limit()
}
/// 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.inner.set_pwmb_lower_limit(duty);
}
/// 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.inner.set_pwmb_upper_limit(duty);
}
}
impl ReducedPwmPin<PwmB> {
pwmb_func!();
#[inline(always)]
pub fn pwmb_lower_limit(&self) -> u16 {
self.common.current_lower_limit
}
#[inline(always)]
pub fn pwmb_upper_limit(&self) -> u16 {
self.common.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
#[inline(always)]
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
self.common.current_lower_limit = duty;
let pwmb_val: u64 = (self.common.current_rst_val as u64
* self.common.current_lower_limit as u64)
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.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.common.current_duty = duty;
let pwma_val: u64 = (self.common.current_rst_val as u64 * self.common.current_duty as u64)
/ DUTY_MAX as u64;
self.dyn_reg
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
}
}
//==================================================================================================
@ -341,12 +420,12 @@ impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
#[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))
self.common.current_duty = duty;
let pwma_val: u64 = (self.common.current_rst_val as u64
* (DUTY_MAX as u64 - self.common.current_duty as u64))
/ DUTY_MAX as u64;
self.reg
.reg()
self.dyn_reg
.reg_block()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())
@ -361,15 +440,7 @@ impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin,
#[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(())
self.inner.set_duty_cycle(duty)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,38 +2,82 @@
//!
//! ## Examples
//!
//! TODO.
//! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs)
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m::asm;
use critical_section::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,
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1,
PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6,
PE7, PE8, PE9, PF0, PF1, PF11, PF12, PF13, PF14, PF15, PF9, PG0, PG1, PG2, PG3, PG6,
};
#[cfg(not(feature = "va41628"))]
use crate::gpio::{
PB10, PB11, PB5, PB6, PB7, PB8, PB9, PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PE10,
PE11, PF10, PF2, PF3, PF4, PF5, PF6, PF7, PF8,
};
use crate::time::Hertz;
use crate::typelevel::Sealed;
use crate::{disable_interrupt, prelude::*};
use crate::{enable_interrupt, pac};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt, pac, prelude::*};
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
pub const TIM_IRQ_OFFSET: usize = 48;
/// Get the peripheral block of a TIM peripheral given the index.
///
/// This function panics if the given index is greater than 23.
///
/// # Safety
///
/// This returns a direct handle to the peripheral block, which allows to circumvent ownership
/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not
/// used by any other software component.
#[inline(always)]
pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock {
match tim_idx {
0 => unsafe { &*pac::Tim0::ptr() },
1 => unsafe { &*pac::Tim1::ptr() },
2 => unsafe { &*pac::Tim2::ptr() },
3 => unsafe { &*pac::Tim3::ptr() },
4 => unsafe { &*pac::Tim4::ptr() },
5 => unsafe { &*pac::Tim5::ptr() },
6 => unsafe { &*pac::Tim6::ptr() },
7 => unsafe { &*pac::Tim7::ptr() },
8 => unsafe { &*pac::Tim8::ptr() },
9 => unsafe { &*pac::Tim9::ptr() },
10 => unsafe { &*pac::Tim10::ptr() },
11 => unsafe { &*pac::Tim11::ptr() },
12 => unsafe { &*pac::Tim12::ptr() },
13 => unsafe { &*pac::Tim13::ptr() },
14 => unsafe { &*pac::Tim14::ptr() },
15 => unsafe { &*pac::Tim15::ptr() },
16 => unsafe { &*pac::Tim16::ptr() },
17 => unsafe { &*pac::Tim17::ptr() },
18 => unsafe { &*pac::Tim18::ptr() },
19 => unsafe { &*pac::Tim19::ptr() },
20 => unsafe { &*pac::Tim20::ptr() },
21 => unsafe { &*pac::Tim21::ptr() },
22 => unsafe { &*pac::Tim22::ptr() },
23 => unsafe { &*pac::Tim23::ptr() },
_ => {
panic!("invalid alarm timer index")
}
}
}
//==================================================================================================
// Defintions
//==================================================================================================
/// Interrupt events
//pub enum Event {
/// Timer timed out / count down ended
//TimeOut,
//}
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CascadeCtrl {
/// Enable Cascade 0 signal active as a requirement for counting
pub enb_start_src_csd0: bool,
@ -137,11 +181,11 @@ pub trait TimPin {
pub trait ValidTim {
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
const TIM_ID: u8;
const ID: u8;
const IRQ: pac::Interrupt;
fn clock(clocks: &Clocks) -> Hertz {
if Self::TIM_ID <= 15 {
if Self::ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
@ -157,13 +201,21 @@ macro_rules! tim_markers {
) => {
$(
impl ValidTim for $TimX {
const TIM_ID: u8 = $id;
const ID: u8 = $id;
const IRQ: pac::Interrupt = $Irq;
}
)+
};
}
pub const fn const_clock<Tim: ValidTim + ?Sized>(_: &Tim, clocks: &Clocks) -> Hertz {
if Tim::ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
}
}
tim_markers!(
(pac::Tim0, 0, pac::Interrupt::TIM0),
(pac::Tim1, 1, pac::Interrupt::TIM1),
@ -196,10 +248,11 @@ pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
macro_rules! valid_pin_and_tims {
(
$(
($PinX:ident, $AltFunc:ident, $TimX:path),
($PinX:ident, $AltFunc:ident, $TimX:path $(, $meta: meta)?),
)+
) => {
$(
$(#[$meta])?
impl TimPin for Pin<$PinX, $AltFunc>
where
$PinX: PinId,
@ -207,6 +260,7 @@ macro_rules! valid_pin_and_tims {
const DYN: DynPinId = $PinX::DYN;
}
$(#[$meta])?
impl<
PinInstance: TimPin,
Tim: ValidTim
@ -217,6 +271,7 @@ macro_rules! valid_pin_and_tims {
{
}
$(#[$meta])?
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
)+
};
@ -242,29 +297,29 @@ valid_pin_and_tims!(
(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),
(PB5, AltFunc2, pac::Tim12, cfg(not(feature = "va41628"))),
(PB6, AltFunc2, pac::Tim11, cfg(not(feature = "va41628"))),
(PB7, AltFunc2, pac::Tim10, cfg(not(feature = "va41628"))),
(PB8, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
(PB9, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
(PB10, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
(PB11, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
(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),
(PD0, AltFunc2, pac::Tim0, cfg(not(feature = "va41628"))),
(PD1, AltFunc2, pac::Tim1, cfg(not(feature = "va41628"))),
(PD2, AltFunc2, pac::Tim2, cfg(not(feature = "va41628"))),
(PD3, AltFunc2, pac::Tim3, cfg(not(feature = "va41628"))),
(PD4, AltFunc2, pac::Tim4, cfg(not(feature = "va41628"))),
(PD5, AltFunc2, pac::Tim5, cfg(not(feature = "va41628"))),
(PD6, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
(PD7, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
(PD8, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
(PD9, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
(PD10, AltFunc2, pac::Tim10),
(PD11, AltFunc2, pac::Tim11),
(PD12, AltFunc2, pac::Tim12),
@ -281,23 +336,23 @@ valid_pin_and_tims!(
(PE7, AltFunc2, pac::Tim23),
(PE8, AltFunc3, pac::Tim16),
(PE9, AltFunc3, pac::Tim17),
(PE10, AltFunc3, pac::Tim18),
(PE11, AltFunc3, pac::Tim19),
(PE10, AltFunc3, pac::Tim18, cfg(not(feature = "va41628"))),
(PE11, AltFunc3, pac::Tim19, cfg(not(feature = "va41628"))),
(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),
(PF2, AltFunc3, pac::Tim2, cfg(not(feature = "va41628"))),
(PF3, AltFunc3, pac::Tim3, cfg(not(feature = "va41628"))),
(PF4, AltFunc3, pac::Tim4, cfg(not(feature = "va41628"))),
(PF5, AltFunc3, pac::Tim5, cfg(not(feature = "va41628"))),
(PF6, AltFunc3, pac::Tim6, cfg(not(feature = "va41628"))),
(PF7, AltFunc3, pac::Tim7, cfg(not(feature = "va41628"))),
(PF8, AltFunc3, pac::Tim8, cfg(not(feature = "va41628"))),
(PF9, AltFunc3, pac::Tim9),
(PF10, AltFunc3, pac::Tim10),
(PF10, AltFunc3, pac::Tim10, cfg(not(feature = "va41628"))),
(PF11, AltFunc3, pac::Tim11),
(PF12, AltFunc3, pac::Tim12),
(PF13, AltFunc2, pac::Tim19),
@ -320,17 +375,25 @@ valid_pin_and_tims!(
///
/// Only the bit related to the corresponding TIM peripheral is modified
#[inline]
fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
pub 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)) })
.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) {
pub 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)) })
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) });
}
#[inline]
pub fn assert_tim_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, tim_id: u8) {
assert_tim_reset(syscfg, tim_id);
asm::nop();
asm::nop();
deassert_tim_reset(syscfg, tim_id);
}
pub type TimRegBlock = pac::tim0::RegisterBlock;
@ -342,11 +405,11 @@ pub type TimRegBlock = pac::tim0::RegisterBlock;
///
/// # Safety
///
/// Users should only implement the [`tim_id`] function. No default function
/// Users should only implement the [Self::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 {
pub unsafe trait TimRegInterface {
fn tim_id(&self) -> u8;
const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
@ -354,7 +417,7 @@ pub(super) unsafe trait TimRegInterface {
/// 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 {
fn reg_block(&self) -> &TimRegBlock {
unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
}
@ -381,6 +444,12 @@ pub(super) unsafe trait TimRegInterface {
}
}
unsafe impl<Tim: ValidTim> TimRegInterface for Tim {
fn tim_id(&self) -> u8 {
Tim::ID
}
}
/// Provide a safe register interface for [`ValidTimAndPin`]s
///
/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to
@ -408,7 +477,7 @@ impl<TIM: ValidTim> TimRegister<TIM> {
unsafe impl<Tim: ValidTim> TimRegInterface for TimRegister<Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::TIM_ID
Tim::ID
}
}
@ -429,20 +498,20 @@ where
unsafe impl<Pin: TimPin, Tim: ValidTim> TimRegInterface for TimAndPinRegister<Pin, Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::TIM_ID
Tim::ID
}
}
pub(super) struct TimDynRegister {
tim_id: u8,
pub(crate) struct TimDynRegister {
pub(crate) tim_id: u8,
#[allow(dead_code)]
pin_id: DynPinId,
pub(crate) 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,
tim_id: Tim::ID,
pin_id: Pin::DYN,
}
}
@ -459,7 +528,10 @@ unsafe impl TimRegInterface for TimDynRegister {
// Timers
//==================================================================================================
/// Hardware timers
/// Hardware timers.
///
/// These timers also implement the [embedded_hal::delay::DelayNs] trait and can be used to delay
/// with a higher resolution compared to the Cortex-M systick delays.
pub struct CountdownTimer<TIM: ValidTim> {
tim: TimRegister<TIM>,
curr_freq: Hertz,
@ -470,16 +542,16 @@ pub struct CountdownTimer<TIM: ValidTim> {
}
#[inline]
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
pub 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> {
unsafe impl<Tim: ValidTim> TimRegInterface for CountdownTimer<Tim> {
#[inline]
fn tim_id(&self) -> u8 {
TIM::TIM_ID
Tim::ID
}
}
@ -489,11 +561,11 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
/// 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);
enable_tim_clk(syscfg, Tim::ID);
assert_tim_reset(syscfg, Tim::ID);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_tim_reset(syscfg, Tim::TIM_ID);
deassert_tim_reset(syscfg, Tim::ID);
CountdownTimer {
tim: unsafe { TimRegister::new(tim) },
@ -517,13 +589,13 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn listen(&mut self) {
self.listening = true;
self.enable_interrupt();
unsafe { enable_interrupt(Tim::IRQ) }
unsafe { enable_nvic_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();
let cnt = self.tim.reg_block().cnt_value().read().bits();
if (cnt > self.last_cnt) || cnt == 0 {
self.last_cnt = self.rst_val;
Ok(())
@ -535,67 +607,95 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
#[inline]
pub fn stop(&mut self) {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
}
#[inline]
pub fn unlisten(&mut self) {
self.listening = true;
self.disable_interrupt();
disable_interrupt(Tim::IRQ);
disable_nvic_interrupt(Tim::IRQ);
}
#[inline(always)]
pub fn enable_interrupt(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().set_bit());
self.tim
.reg_block()
.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());
self.tim
.reg_block()
.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());
self.tim
.reg_block()
.ctrl()
.write(|w| w.enable().clear_bit());
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) });
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << 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.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self.curr_freq = timeout.into();
self.rst_val = self.clock.raw() / self.curr_freq.raw();
self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1;
self.set_reload(self.rst_val);
self.set_count(0);
// Decrementing counter, to set the reset value.
self.set_count(self.rst_val);
}
#[inline(always)]
pub fn set_reload(&mut self, val: u32) {
self.tim.reg().rst_value().write(|w| unsafe { w.bits(val) });
self.tim
.reg_block()
.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) });
self.tim
.reg_block()
.cnt_value()
.write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn count(&self) -> u32 {
self.tim.reg().cnt_value().read().bits()
self.tim.reg_block().cnt_value().read().bits()
}
#[inline(always)]
pub fn enable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit());
self.tim
.reg_block()
.enable()
.write(|w| unsafe { w.bits(1) });
}
#[inline(always)]
pub fn disable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
self.tim
.reg_block()
.ctrl()
.modify(|_, w| w.enable().clear_bit());
}
/// Disable the counter, setting both enable and active bit to 0
@ -603,12 +703,12 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn auto_disable(self, enable: bool) -> Self {
if enable {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().set_bit());
} else {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_disable().clear_bit());
}
@ -623,12 +723,12 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn auto_deactivate(self, enable: bool) -> Self {
if enable {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().set_bit());
} else {
self.tim
.reg()
.reg_block()
.ctrl()
.modify(|_, w| w.auto_deactivate().clear_bit());
}
@ -638,7 +738,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Configure the cascade parameters
#[inline]
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
self.tim.reg().csd_ctrl().write(|w| {
self.tim.reg_block().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);
@ -656,7 +756,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.reg_block()
.cascade0()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
@ -666,7 +766,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.reg_block()
.cascade1()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
@ -676,7 +776,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.reg_block()
.cascade2()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
@ -767,7 +867,7 @@ pub fn set_up_ms_tick<Tim: ValidTim>(
/// 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| {
critical_section::with(|cs| {
let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1;
MS_COUNTER.borrow(cs).set(ms);
@ -776,7 +876,7 @@ pub fn default_ms_irq_handler() {
/// Get the current MS tick count
pub fn get_ms_ticks() -> u32 {
cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get())
critical_section::with(|cs| MS_COUNTER.borrow(cs).get())
}
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);

File diff suppressed because it is too large Load Diff

1327
va416xx-hal/src/uart/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,448 @@
//! # Async UART reception functionality for the VA416xx family.
//!
//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
//! [embedded_io_async::Read] trait.
//! This trait allows for asynchronous reception of data streams. Please note that this module does
//! not specify/declare the interrupt handlers which must be provided for async support to work.
//! However, it provides two interrupt handlers:
//!
//! - [on_interrupt_rx]
//! - [on_interrupt_rx_overwriting]
//!
//! The first two are used for the [RxAsync] struct, while the latter two are used with the
//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer.
//!
//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors]
//! structure returned by the interrupt handlers.
//!
//! # Example
//!
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io::ErrorType;
use portable_atomic::AtomicBool;
use va416xx::uart0 as uart_base;
use crate::enable_nvic_interrupt;
use super::{Bank, Instance, Rx, UartErrors};
static UART_RX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
static RX_READ_ACTIVE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
static RX_HAS_DATA: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
struct RxFuture {
uart_idx: usize,
}
impl RxFuture {
pub fn new<Uart: Instance>(_rx: &mut Rx<Uart>) -> Self {
RX_READ_ACTIVE[Uart::IDX as usize].store(true, Ordering::Relaxed);
Self {
uart_idx: Uart::IDX as usize,
}
}
}
impl Future for RxFuture {
type Output = Result<(), Infallible>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_RX_WAKERS[self.uart_idx].register(cx.waker());
if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) {
return core::task::Poll::Ready(Ok(()));
}
core::task::Poll::Pending
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AsyncUartErrors {
/// Queue has overflowed, data might have been lost.
pub queue_overflow: bool,
/// UART errors.
pub uart_errors: UartErrors,
}
fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option<UartErrors> {
let rx_status = uart.rxstatus().read();
if rx_status.rxovr().bit_is_set()
|| rx_status.rxfrm().bit_is_set()
|| rx_status.rxpar().bit_is_set()
{
let mut errors_val = UartErrors::default();
if rx_status.rxovr().bit_is_set() {
errors_val.overflow = true;
}
if rx_status.rxfrm().bit_is_set() {
errors_val.framing = true;
}
if rx_status.rxpar().bit_is_set() {
errors_val.parity = true;
}
return Some(errors_val);
}
None
}
fn on_interrupt_rx_common_post_processing(
bank: Bank,
rx_enabled: bool,
read_some_data: bool,
irq_end: u32,
) -> Option<UartErrors> {
let idx = bank as usize;
if read_some_data {
RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
UART_RX_WAKERS[idx].wake();
}
}
let mut errors = None;
let uart_regs = unsafe { bank.reg_block() };
// Check for RX errors
if rx_enabled {
errors = on_interrupt_handle_rx_errors(uart_regs);
}
// Clear the interrupt status bits
uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
errors
}
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
///
/// Should be called in the user interrupt handler to enable
/// asynchronous reception. This variant will overwrite old data in the ring buffer in case
/// the ring buffer is full.
pub fn on_interrupt_rx_overwriting<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer)
}
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<u8, N>,
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Result<(), AsyncUartErrors> {
let uart_regs = unsafe { bank.reg_block() };
let irq_end = uart_regs.irq_end().read();
let enb_status = uart_regs.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let mut read_some_data = false;
let mut queue_overflow = false;
// Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() {
let available_bytes = uart_regs.rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible
for _ in 0..available_bytes {
let byte = uart_regs.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
cons_ref.as_mut().unwrap().dequeue();
});
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart_regs.rxstatus().read().rdavl().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart_regs.data().read().bits();
if !prod.ready() {
queue_overflow = true;
critical_section::with(|cs| {
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
cons_ref.as_mut().unwrap().dequeue();
});
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
uart_errors: uart_errors.unwrap_or_default(),
});
}
Ok(())
}
/// Interrupt handler for asynchronous RX operations.
///
/// Should be called in the user interrupt handler to enable asynchronous reception.
pub fn on_interrupt_rx<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
on_interrupt_rx_async_heapless_queue(bank, prod)
}
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
bank: Bank,
prod: &mut heapless::spsc::Producer<'_, u8, N>,
) -> Result<(), AsyncUartErrors> {
let uart = unsafe { bank.reg_block() };
let irq_end = uart.irq_end().read();
let enb_status = uart.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set();
let mut read_some_data = false;
let mut queue_overflow = false;
// Half-Full interrupt. We have a guaranteed amount of data we can read.
if irq_end.irq_rx().bit_is_set() {
let available_bytes = uart.rxfifoirqtrg().read().bits() as usize;
// If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible
for _ in 0..available_bytes {
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
while uart.rxstatus().read().rdavl().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart.data().read().bits();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte as u8).ok();
}
read_some_data = true;
}
let uart_errors =
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
if uart_errors.is_some() || queue_overflow {
return Err(AsyncUartErrors {
queue_overflow,
uart_errors: uart_errors.unwrap_or_default(),
});
}
Ok(())
}
struct ActiveReadGuard(usize);
impl Drop for ActiveReadGuard {
fn drop(&mut self) {
RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed);
}
}
struct RxAsyncInner<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub queue: heapless::spsc::Consumer<'static, u8, N>,
}
/// Core data structure to allow asynchronous UART reception.
///
/// If the ring buffer becomes full, data will be lost.
pub struct RxAsync<Uart: Instance, const N: usize>(Option<RxAsyncInner<Uart, N>>);
impl<Uart: Instance, const N: usize> ErrorType for RxAsync<Uart, N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
fn stop_async_rx<Uart: Instance>(rx: &mut Rx<Uart>) {
rx.disable_interrupts();
rx.disable();
unsafe {
enable_nvic_interrupt(Uart::IRQ_RX);
}
rx.clear_fifo();
}
impl<Uart: Instance, const N: usize> RxAsync<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which
/// is filled by the interrupt handler [on_interrupt_rx].
pub fn new(mut rx: Rx<Uart>, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
unsafe {
enable_nvic_interrupt(Uart::IRQ_RX);
}
rx.enable_interrupts();
rx.enable();
});
Self(Some(RxAsyncInner { rx, queue }))
}
pub fn stop(&mut self) {
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
}
pub fn release(mut self) -> (Rx<Uart>, heapless::spsc::Consumer<'static, u8, N>) {
self.stop();
let inner = self.0.take().unwrap();
(inner.rx, inner.queue)
}
}
impl<Uart: Instance, const N: usize> Drop for RxAsync<Uart, N> {
fn drop(&mut self) {
self.stop();
}
}
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsync<Uart, N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// Need to wait for the IRQ to read data and set this flag. If the queue is not
// empty, we can read data immediately.
if self.0.as_ref().unwrap().queue.len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
let _guard = ActiveReadGuard(Uart::IDX as usize);
let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| {
let data_to_read = consumer.len().min(buf.len());
for byte in buf.iter_mut().take(data_to_read) {
// We own the consumer and we checked that the amount of data is guaranteed to be available.
*byte = unsafe { consumer.dequeue_unchecked() };
}
data_to_read
};
let mut_ref = self.0.as_mut().unwrap();
let fut = RxFuture::new(&mut mut_ref.rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(&mut mut_ref.queue);
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
Ok(handle_data_in_queue(&mut mut_ref.queue))
}
}
struct RxAsyncOverwritingInner<Uart: Instance, const N: usize> {
rx: Rx<Uart>,
pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
}
/// Core data structure to allow asynchronous UART reception.
///
/// If the ring buffer becomes full, the oldest data will be overwritten when using the
/// [on_interrupt_rx_overwriting] interrupt handlers.
pub struct RxAsyncOverwriting<Uart: Instance, const N: usize>(
Option<RxAsyncOverwritingInner<Uart, N>>,
);
impl<Uart: Instance, const N: usize> ErrorType for RxAsyncOverwriting<Uart, N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
impl<Uart: Instance, const N: usize> RxAsyncOverwriting<Uart, N> {
/// Create a new asynchronous receiver.
///
/// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
/// which is filled by the interrupt handler. The shared property allows using it in the
/// interrupt handler to overwrite old data.
pub fn new(
mut rx: Rx<Uart>,
shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
rx.enable_interrupts();
rx.enable();
});
Self(Some(RxAsyncOverwritingInner {
rx,
shared_consumer,
}))
}
pub fn stop(&mut self) {
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
}
pub fn release(mut self) -> Rx<Uart> {
self.stop();
let inner = self.0.take().unwrap();
inner.rx
}
}
impl<Uart: Instance, const N: usize> Drop for RxAsyncOverwriting<Uart, N> {
fn drop(&mut self) {
self.stop();
}
}
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncOverwriting<Uart, N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// Need to wait for the IRQ to read data and set this flag. If the queue is not
// empty, we can read data immediately.
critical_section::with(|cs| {
let queue = self.0.as_ref().unwrap().shared_consumer.borrow(cs);
if queue.borrow().as_ref().unwrap().len() == 0 {
RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
}
});
let _guard = ActiveReadGuard(Uart::IDX as usize);
let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<Uart, N>| {
critical_section::with(|cs| {
let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
let consumer = consumer_ref.as_mut().unwrap();
let data_to_read = consumer.len().min(buf.len());
for byte in buf.iter_mut().take(data_to_read) {
// We own the consumer and we checked that the amount of data is guaranteed to be available.
*byte = unsafe { consumer.dequeue_unchecked() };
}
data_to_read
})
};
let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
// Data is available, so read that data immediately.
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
if read_data > 0 {
return Ok(read_data);
}
// Await data.
let _ = fut.await;
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
Ok(read_data)
}
}

View File

@ -0,0 +1,263 @@
//! # Async UART transmission functionality for the VA416xx family.
//!
//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
//! This trait allows for asynchronous sending of data streams. Please note that this module does
//! not specify/declare the interrupt handlers which must be provided for async support to work.
//! However, it the [on_interrupt_tx] interrupt handler.
//!
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
//! for a given UART bank.
//!
//! # Example
//!
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
use core::{cell::RefCell, future::Future};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io_async::Write;
use portable_atomic::AtomicBool;
use super::*;
static UART_TX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 3] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; 3];
// Completion flag. Kept outside of the context structure as an atomic to avoid
// critical section.
static TX_DONE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
/// UART bank.
///
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
/// the given UART bank.
pub fn on_interrupt_tx(bank: Bank) {
let uart = unsafe { bank.reg_block() };
let idx = bank as usize;
let irq_enb = uart.irq_enb().read();
// IRQ is not related to TX.
if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() {
return;
}
let tx_status = uart.txstatus().read();
let unexpected_overrun = tx_status.wrlost().bit_is_set();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
});
context.tx_overrun = unexpected_overrun;
if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
uart.irq_enb().modify(|_, w| {
w.irq_tx().clear_bit();
w.irq_tx_empty().clear_bit();
w.irq_tx_status().clear_bit()
});
uart.enable().modify(|_, w| w.txenable().clear_bit());
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
// Transfer is done.
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
UART_TX_WAKERS[idx].wake();
return;
}
// Safety: We documented that the user provided slice must outlive the future, so we convert
// the raw pointer back to the slice here.
let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) };
while context.progress < context.slice.len {
let wrrdy = uart.txstatus().read().wrrdy().bit_is_set();
if !wrrdy {
break;
}
// Safety: TX structure is owned by the future which does not write into the the data
// register, so we can assume we are the only one writing to the data register.
uart.data()
.write(|w| unsafe { w.bits(slice[context.progress] as u32) });
context.progress += 1;
}
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
}
#[derive(Debug, Copy, Clone)]
pub struct TxContext {
progress: usize,
tx_overrun: bool,
slice: RawBufSlice,
}
#[allow(clippy::new_without_default)]
impl TxContext {
pub const fn new() -> Self {
Self {
progress: 0,
tx_overrun: false,
slice: RawBufSlice::new_empty(),
}
}
}
#[derive(Debug, Copy, Clone)]
struct RawBufSlice {
data: *const u8,
len: usize,
}
/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
unsafe impl Send for RawBufSlice {}
impl RawBufSlice {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
#[allow(dead_code)]
const unsafe fn new(data: &[u8]) -> Self {
Self {
data: data.as_ptr(),
len: data.len(),
}
}
const fn new_empty() -> Self {
Self {
data: core::ptr::null(),
len: 0,
}
}
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn set(&mut self, data: &[u8]) {
self.data = data.as_ptr();
self.len = data.len();
}
}
pub struct TxFuture {
uart_idx: usize,
}
impl TxFuture {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn new<Uart: Instance>(tx: &mut Tx<Uart>, data: &[u8]) -> Self {
TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed);
tx.disable_interrupts();
tx.disable();
tx.clear_fifo();
let uart_tx = unsafe { tx.uart() };
let init_fill_count = core::cmp::min(data.len(), 16);
// We fill the FIFO.
for data in data.iter().take(init_fill_count) {
uart_tx.data().write(|w| unsafe { w.bits(*data as u32) });
}
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.slice.set(data);
context.progress = init_fill_count;
// Ensure those are enabled inside a critical section at the same time. Can lead to
// weird glitches otherwise.
tx.enable_interrupts();
tx.enable();
});
Self {
uart_idx: Uart::IDX as usize,
}
}
}
impl Future for TxFuture {
type Output = Result<usize, TxOverrunError>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_TX_WAKERS[self.uart_idx].register(cx.waker());
if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
let progress = critical_section::with(|cs| {
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
});
return core::task::Poll::Ready(Ok(progress));
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture {
fn drop(&mut self) {
let reg_block = match self.uart_idx {
0 => unsafe { pac::Uart0::reg_block() },
1 => unsafe { pac::Uart1::reg_block() },
2 => unsafe { pac::Uart2::reg_block() },
_ => unreachable!(),
};
disable_tx_interrupts(reg_block);
disable_tx(reg_block);
}
}
pub struct TxAsync<Uart: Instance> {
tx: Tx<Uart>,
}
impl<Uart: Instance> TxAsync<Uart> {
/// Create a new asynchronous TX object.
///
/// This function also enable the NVIC interrupt, but does not enable the peripheral specific
/// interrupts.
pub fn new(tx: Tx<Uart>) -> Self {
// Safety: We own TX now.
unsafe { enable_nvic_interrupt(Uart::IRQ_TX) };
Self { tx }
}
/// This function also disables the NVIC interrupt.
pub fn release(self) -> Tx<Uart> {
disable_nvic_interrupt(Uart::IRQ_TX);
self.tx
}
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("TX overrun error")]
pub struct TxOverrunError;
impl embedded_io_async::Error for TxOverrunError {
fn kind(&self) -> embedded_io_async::ErrorKind {
embedded_io_async::ErrorKind::Other
}
}
impl<Uart: Instance> embedded_io::ErrorType for TxAsync<Uart> {
type Error = TxOverrunError;
}
impl<Uart: Instance> Write for TxAsync<Uart> {
/// Write a buffer asynchronously.
///
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
fut.await
}
}

View File

@ -4,20 +4,21 @@
//!
//! - [Watchdog simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/wdt.rs)
use crate::time::Hertz;
use crate::{
clock::{Clocks, PeripheralSelect},
pac,
prelude::SyscfgExt,
};
use crate::{disable_interrupt, enable_interrupt};
use crate::{clock::Clocks, pac};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt, PeripheralSelect, SyscfgExt as _};
pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
pub struct WdtController {
/// Watchdog peripheral driver.
pub struct Wdt {
clock_freq: Hertz,
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
///
/// # Safety
@ -25,17 +26,16 @@ pub struct WdtController {
/// This function is `unsafe` because it can break mask-based critical sections.
#[inline]
pub unsafe fn enable_wdt_interrupts() {
enable_interrupt(pac::Interrupt::WATCHDOG)
enable_nvic_interrupt(pac::Interrupt::WATCHDOG)
}
#[inline]
pub fn disable_wdt_interrupts() {
disable_interrupt(pac::Interrupt::WATCHDOG)
disable_nvic_interrupt(pac::Interrupt::WATCHDOG)
}
impl WdtController {
impl Wdt {
pub fn new(
&self,
syscfg: &mut pac::Sysconfig,
wdt: pac::WatchDog,
clocks: &Clocks,
@ -76,12 +76,12 @@ impl WdtController {
#[inline]
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]
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]

View File

@ -1,64 +1,44 @@
on: [push]
name: build
name: ci
on: [push, pull_request]
jobs:
check:
name: Check
name: Check build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: stable
target: thumbv7em-none-eabihf
override: true
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: check
args: --target thumbv7em-none-eabihf
targets: "thumbv7em-none-eabihf"
- run: cargo check --target thumbv7em-none-eabihf
- run: cargo check --target thumbv7em-none-eabihf --examples
- run: cargo check -p va416xx --target thumbv7em-none-eabihf --all-features
- run: cargo check -p va416xx-hal --target thumbv7em-none-eabihf --features "defmt"
fmt:
name: Rustfmt
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo fmt --all -- --check
docs:
name: Check Documentation Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va416xx --all-features
- 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:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
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
targets: "thumbv7em-none-eabihf"
- run: cargo clippy --target thumbv7em-none-eabihf -- -D warnings

View File

@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [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
- Re-Generated PAC with `svd2rust` v0.33.3

View File

@ -1,6 +1,6 @@
[package]
name = "va416xx"
version = "0.2.0"
version = "0.4.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "PAC for the Vorago VA416xx family of MCUs"
@ -15,6 +15,8 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-m = "0.7"
vcell = "0.1.3"
defmt = { version = "1", optional = true }
critical-section = { version = "1", optional = true }
[dependencies.cortex-m-rt]
@ -23,6 +25,8 @@ version = ">=0.6.15,<0.8"
[features]
rt = ["cortex-m-rt/device"]
# Adds Debug implementation
debug = []
[package.metadata.docs.rs]
all-features = true

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# Use installed tool by default
svd2rust_bin="svd2rust"
@ -29,7 +29,7 @@ then
fi
svdtools patch svd/va416xx-patch.yml
${svd2rust_bin} --reexport-interrupt -i svd/va416xx.svd.patched
${svd2rust_bin} --reexport-interrupt --impl-defmt defmt --impl-debug-feature debug -i svd/va416xx.svd.patched
result=$?
if [ $result -ne 0 ]; then

View File

@ -65,61 +65,61 @@ impl RegisterBlock {
&self.perid
}
}
#[doc = "CTRL (rw) register accessor: Control Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`ctrl::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`ctrl::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@ctrl`]
#[doc = "CTRL (rw) register accessor: Control Register\n\nYou can [`read`](crate::Reg::read) this register and get [`ctrl::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`ctrl::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@ctrl`]
module"]
#[doc(alias = "CTRL")]
pub type Ctrl = crate::Reg<ctrl::CtrlSpec>;
#[doc = "Control Register"]
pub mod ctrl;
#[doc = "FIFO_DATA (r) register accessor: FIFO data\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`fifo_data::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@fifo_data`]
#[doc = "FIFO_DATA (r) register accessor: FIFO data\n\nYou can [`read`](crate::Reg::read) this register and get [`fifo_data::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@fifo_data`]
module"]
#[doc(alias = "FIFO_DATA")]
pub type FifoData = crate::Reg<fifo_data::FifoDataSpec>;
#[doc = "FIFO data"]
pub mod fifo_data;
#[doc = "STATUS (r) register accessor: Status\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`status::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@status`]
#[doc = "STATUS (r) register accessor: Status\n\nYou can [`read`](crate::Reg::read) this register and get [`status::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@status`]
module"]
#[doc(alias = "STATUS")]
pub type Status = crate::Reg<status::StatusSpec>;
#[doc = "Status"]
pub mod status;
#[doc = "IRQ_ENB (rw) register accessor: Interrupt Enable\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_enb::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`irq_enb::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_enb`]
#[doc = "IRQ_ENB (rw) register accessor: Interrupt Enable\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_enb::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`irq_enb::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_enb`]
module"]
#[doc(alias = "IRQ_ENB")]
pub type IrqEnb = crate::Reg<irq_enb::IrqEnbSpec>;
#[doc = "Interrupt Enable"]
pub mod irq_enb;
#[doc = "IRQ_RAW (r) register accessor: Raw Interrupt Status\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_raw::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_raw`]
#[doc = "IRQ_RAW (r) register accessor: Raw Interrupt Status\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_raw::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_raw`]
module"]
#[doc(alias = "IRQ_RAW")]
pub type IrqRaw = crate::Reg<irq_raw::IrqRawSpec>;
#[doc = "Raw Interrupt Status"]
pub mod irq_raw;
#[doc = "IRQ_END (r) register accessor: Enabled Interrupt Status\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_end::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_end`]
#[doc = "IRQ_END (r) register accessor: Enabled Interrupt Status\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_end::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_end`]
module"]
#[doc(alias = "IRQ_END")]
pub type IrqEnd = crate::Reg<irq_end::IrqEndSpec>;
#[doc = "Enabled Interrupt Status"]
pub mod irq_end;
#[doc = "IRQ_CLR (w) register accessor: Clear Interrupt\n\nYou can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`irq_clr::W`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_clr`]
#[doc = "IRQ_CLR (w) register accessor: Clear Interrupt\n\nYou can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`irq_clr::W`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_clr`]
module"]
#[doc(alias = "IRQ_CLR")]
pub type IrqClr = crate::Reg<irq_clr::IrqClrSpec>;
#[doc = "Clear Interrupt"]
pub mod irq_clr;
#[doc = "RXFIFOIRQTRG (rw) register accessor: Receive FIFO Interrupt Trigger Value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`rxfifoirqtrg::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`rxfifoirqtrg::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@rxfifoirqtrg`]
#[doc = "RXFIFOIRQTRG (rw) register accessor: Receive FIFO Interrupt Trigger Value\n\nYou can [`read`](crate::Reg::read) this register and get [`rxfifoirqtrg::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`rxfifoirqtrg::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@rxfifoirqtrg`]
module"]
#[doc(alias = "RXFIFOIRQTRG")]
pub type Rxfifoirqtrg = crate::Reg<rxfifoirqtrg::RxfifoirqtrgSpec>;
#[doc = "Receive FIFO Interrupt Trigger Value"]
pub mod rxfifoirqtrg;
#[doc = "FIFO_CLR (rw) register accessor: FIFO Clear\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`fifo_clr::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`fifo_clr::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@fifo_clr`]
#[doc = "FIFO_CLR (rw) register accessor: FIFO Clear\n\nYou can [`read`](crate::Reg::read) this register and get [`fifo_clr::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`fifo_clr::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@fifo_clr`]
module"]
#[doc(alias = "FIFO_CLR")]
pub type FifoClr = crate::Reg<fifo_clr::FifoClrSpec>;
#[doc = "FIFO Clear"]
pub mod fifo_clr;
#[doc = "PERID (r) register accessor: Peripheral ID Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`perid::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@perid`]
#[doc = "PERID (r) register accessor: Peripheral ID Register\n\nYou can [`read`](crate::Reg::read) this register and get [`perid::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@perid`]
module"]
#[doc(alias = "PERID")]
pub type Perid = crate::Reg<perid::PeridSpec>;

View File

@ -61,42 +61,36 @@ impl R {
impl W {
#[doc = "Bits 0:15 - Enables the channel for data collection"]
#[inline(always)]
#[must_use]
pub fn chan_en(&mut self) -> ChanEnW<CtrlSpec> {
ChanEnW::new(self, 0)
}
#[doc = "Bit 16 - Enables the channel tag to be saved with the ADC data"]
#[inline(always)]
#[must_use]
pub fn chan_tag_en(&mut self) -> ChanTagEnW<CtrlSpec> {
ChanTagEnW::new(self, 16)
}
#[doc = "Bit 17 - ADC data acquisition for all enabled channel"]
#[inline(always)]
#[must_use]
pub fn sweep_en(&mut self) -> SweepEnW<CtrlSpec> {
SweepEnW::new(self, 17)
}
#[doc = "Bit 18 - Allows the external trigger to start analog acquisition"]
#[inline(always)]
#[must_use]
pub fn ext_trig_en(&mut self) -> ExtTrigEnW<CtrlSpec> {
ExtTrigEnW::new(self, 18)
}
#[doc = "Bit 19 - Starts analog acquisition"]
#[inline(always)]
#[must_use]
pub fn manual_trig(&mut self) -> ManualTrigW<CtrlSpec> {
ManualTrigW::new(self, 19)
}
#[doc = "Bits 20:23 - Conversion count describes the number of conversions to be applied for triggers/sweeps. (N+1 conversions)"]
#[inline(always)]
#[must_use]
pub fn conv_cnt(&mut self) -> ConvCntW<CtrlSpec> {
ConvCntW::new(self, 20)
}
}
#[doc = "Control Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`ctrl::R`](R). You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`ctrl::W`](W). You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Control Register\n\nYou can [`read`](crate::Reg::read) this register and get [`ctrl::R`](R). You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`ctrl::W`](W). You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct CtrlSpec;
impl crate::RegisterSpec for CtrlSpec {
type Ux = u32;

View File

@ -7,12 +7,11 @@ pub type FifoClrW<'a, REG> = crate::BitWriter<'a, REG>;
impl W {
#[doc = "Bit 0 - Clears the ADC FIFO. Always reads 0"]
#[inline(always)]
#[must_use]
pub fn fifo_clr(&mut self) -> FifoClrW<FifoClrSpec> {
FifoClrW::new(self, 0)
}
}
#[doc = "FIFO Clear\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`fifo_clr::R`](R). You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`fifo_clr::W`](W). You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "FIFO Clear\n\nYou can [`read`](crate::Reg::read) this register and get [`fifo_clr::R`](R). You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`fifo_clr::W`](W). You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct FifoClrSpec;
impl crate::RegisterSpec for FifoClrSpec {
type Ux = u32;

View File

@ -16,7 +16,7 @@ impl R {
ChanTagR::new(((self.bits >> 12) & 0x0f) as u8)
}
}
#[doc = "FIFO data\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`fifo_data::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "FIFO data\n\nYou can [`read`](crate::Reg::read) this register and get [`fifo_data::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct FifoDataSpec;
impl crate::RegisterSpec for FifoDataSpec {
type Ux = u32;

View File

@ -11,30 +11,26 @@ pub type TrigErrorW<'a, REG> = crate::BitWriter<'a, REG>;
impl W {
#[doc = "Bit 0 - Clears the FIFO overflow interrupt status. Always reads 0"]
#[inline(always)]
#[must_use]
pub fn fifo_oflow(&mut self) -> FifoOflowW<IrqClrSpec> {
FifoOflowW::new(self, 0)
}
#[doc = "Bit 1 - Clears the FIFO underflow interrupt status. Always reads 0"]
#[inline(always)]
#[must_use]
pub fn fifo_uflow(&mut self) -> FifoUflowW<IrqClrSpec> {
FifoUflowW::new(self, 1)
}
#[doc = "Bit 2 - Clears the ADC done interrupt status. Always reads 0"]
#[inline(always)]
#[must_use]
pub fn adc_done(&mut self) -> AdcDoneW<IrqClrSpec> {
AdcDoneW::new(self, 2)
}
#[doc = "Bit 3 - Clears the trigger error interrupt status. Always reads 0"]
#[inline(always)]
#[must_use]
pub fn trig_error(&mut self) -> TrigErrorW<IrqClrSpec> {
TrigErrorW::new(self, 3)
}
}
#[doc = "Clear Interrupt\n\nYou can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`irq_clr::W`](W). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Clear Interrupt\n\nYou can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`irq_clr::W`](W). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct IrqClrSpec;
impl crate::RegisterSpec for IrqClrSpec {
type Ux = u32;

View File

@ -70,48 +70,41 @@ impl R {
impl W {
#[doc = "Bit 0 - Enables the interrupt for FIFO empty"]
#[inline(always)]
#[must_use]
pub fn fifo_empty(&mut self) -> FifoEmptyW<IrqEnbSpec> {
FifoEmptyW::new(self, 0)
}
#[doc = "Bit 1 - Enables the interrupt for FIFO full"]
#[inline(always)]
#[must_use]
pub fn fifo_full(&mut self) -> FifoFullW<IrqEnbSpec> {
FifoFullW::new(self, 1)
}
#[doc = "Bit 2 - Enables the interrupt for a FIFO overflow"]
#[inline(always)]
#[must_use]
pub fn fifo_oflow(&mut self) -> FifoOflowW<IrqEnbSpec> {
FifoOflowW::new(self, 2)
}
#[doc = "Bit 3 - Enables the interrupt for a FIFO underflow"]
#[inline(always)]
#[must_use]
pub fn fifo_uflow(&mut self) -> FifoUflowW<IrqEnbSpec> {
FifoUflowW::new(self, 3)
}
#[doc = "Bit 4 - Enables the interrupt for an ADC data acquisition completion"]
#[inline(always)]
#[must_use]
pub fn adc_done(&mut self) -> AdcDoneW<IrqEnbSpec> {
AdcDoneW::new(self, 4)
}
#[doc = "Bit 5 - Enables the interrupt for a trigger error"]
#[inline(always)]
#[must_use]
pub fn trig_error(&mut self) -> TrigErrorW<IrqEnbSpec> {
TrigErrorW::new(self, 5)
}
#[doc = "Bit 6 - Enables the interrupt for the FIFO entry count meets or exceeds the trigger level"]
#[inline(always)]
#[must_use]
pub fn fifo_depth_trig(&mut self) -> FifoDepthTrigW<IrqEnbSpec> {
FifoDepthTrigW::new(self, 6)
}
}
#[doc = "Interrupt Enable\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_enb::R`](R). You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`irq_enb::W`](W). You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Interrupt Enable\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_enb::R`](R). You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`irq_enb::W`](W). You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct IrqEnbSpec;
impl crate::RegisterSpec for IrqEnbSpec {
type Ux = u32;

View File

@ -51,7 +51,7 @@ impl R {
FifoDepthTrigR::new(((self.bits >> 6) & 1) != 0)
}
}
#[doc = "Enabled Interrupt Status\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_end::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Enabled Interrupt Status\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_end::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct IrqEndSpec;
impl crate::RegisterSpec for IrqEndSpec {
type Ux = u32;

View File

@ -51,7 +51,7 @@ impl R {
FifoDepthTrigR::new(((self.bits >> 6) & 1) != 0)
}
}
#[doc = "Raw Interrupt Status\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_raw::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Raw Interrupt Status\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_raw::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct IrqRawSpec;
impl crate::RegisterSpec for IrqRawSpec {
type Ux = u32;

View File

@ -1,11 +1,12 @@
#[doc = "Register `PERID` reader"]
pub type R = crate::R<PeridSpec>;
#[cfg(feature = "debug")]
impl core::fmt::Debug for R {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.bits())
}
}
#[doc = "Peripheral ID Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`perid::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Peripheral ID Register\n\nYou can [`read`](crate::Reg::read) this register and get [`perid::R`](R). See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct PeridSpec;
impl crate::RegisterSpec for PeridSpec {
type Ux = u32;

View File

@ -16,12 +16,11 @@ impl R {
impl W {
#[doc = "Bits 0:4 - Sets the FIFO_ENTRY_CNT value that asserts the FIFO_DEPTH_TRIG interrupt"]
#[inline(always)]
#[must_use]
pub fn level(&mut self) -> LevelW<RxfifoirqtrgSpec> {
LevelW::new(self, 0)
}
}
#[doc = "Receive FIFO Interrupt Trigger Value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`rxfifoirqtrg::R`](R). You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`rxfifoirqtrg::W`](W). You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
#[doc = "Receive FIFO Interrupt Trigger Value\n\nYou can [`read`](crate::Reg::read) this register and get [`rxfifoirqtrg::R`](R). You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`rxfifoirqtrg::W`](W). You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api)."]
pub struct RxfifoirqtrgSpec;
impl crate::RegisterSpec for RxfifoirqtrgSpec {
type Ux = u32;

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