112 Commits

Author SHA1 Message Date
cf56cdd993 small improvement 2025-05-13 16:50:15 +02:00
efe62ea39e improve docs 2025-05-13 16:49:32 +02:00
72aae605d8 CAN peripheral support 2025-05-13 16:35:23 +02:00
c46d210ef9 note about shared crate 2025-04-24 22:46:00 +02:00
47596415d3 Merge pull request 'Rework library structure' (#68) from shared-peripherals into main
Reviewed-on: #68
2025-04-24 17:04:18 +02:00
935ee9dbb1 Rework library structure
Changed:

- Move most library components to new [`vorago-shared-periphs`](https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs)
  which is mostly re-exported in this crate.
- Overhaul and simplification of several HAL APIs. The system configuration and IRQ router
  peripheral instance generally does not need to be passed to HAL API anymore.
- All HAL drivers are now type erased. The constructors will still expect and consume the PAC
  singleton component for resource management purposes, but are not cached anymore.
- Refactoring of GPIO library to be more inline with embassy GPIO API.

Added:

- I2C clock timeout feature support.
2025-04-24 16:54:03 +02:00
d641f3943f Merge pull request 'start switching to defmt' (#67) from add-defmt-support into main
Reviewed-on: #67
2025-04-24 14:29:02 +02:00
3b5e7a9af3 start switching to defmt 2025-04-22 18:21:19 +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
621 changed files with 8745 additions and 9275 deletions

View File

@ -1,21 +1,16 @@
[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",
"link-arg=-Tlink.x",
# "-C",
# "linker=flip-link",
# "-C",
# "link-arg=-Tdefmt.x",
"-C",
"linker=flip-link",
"-C",
"link-arg=-Tdefmt.x",
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
"-C",
@ -35,6 +30,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"
DEFMT_LOG = "debug"

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

151
README.md
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,26 @@ 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.
Some parts of the HAL implementation and the Embassy-rs support are contained in the external
[`vorago-shared-periphs`](https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs) crate.
## Using the `.cargo/config.toml` file
@ -47,6 +61,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 is available in the `.cargo/def-config.toml` file to use `probe-rs` for
convenience. `probe-rs` is also able to process and display `defmt` strings directly.
### Pre-Requisites
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
@ -55,43 +88,13 @@ care of installing the pre-requisites first.
### Using CLI
You can build the blinky example application with the following command
```sh
cargo build --example blinky
```
Start the GDB server first. The server needs to be started with a certain configuration and with
a JLink script to disable ROM protection.
For example, on Debian based system the following command can be used to do this (this command
is also run when running the `jlink-gdb.sh` script)
```sh
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
```
After this, you can flash and debug the application with the following command
```sh
gdb-mutliarch -q -x jlink/jlink.gdb target/thumbv7em-none-eabihf/debug/examples/blinky
```
Please note that you can automate all steps except starting the GDB server by using a cargo
runner configuration, for example with the following lines in your `.cargo/config.toml` file:
```toml
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "gdb-multiarch -q -x jlink/jlink.gdb"
```
After that, you can simply use `cargo run --example blinky` to flash the blinky
example.
### Using VS Code
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
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`
@ -106,4 +109,80 @@ configuration variables in your `settings.json`:
- `"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`.
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
cargo build --example blinky
```
Start the GDB server first. Depending on whether the application is flashed to RAM or the NVM
flash memory, the server needs to be started with a different configuration
For example, on Debian based system the following command can be used to do this (this command
is also run when running the `jlink-gdb.sh` script)
**RAM Flash**
```sh
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
```
**NVM Flash**
```sh
JLinkGDBServer -select USB -device VA416xx -endian little -if SWD -speed 2000 \
-LocalhostOnly -vd
```
After this, you can flash and debug the application with the following command
```sh
gdb-mutliarch -q -x jlink/jlink.gdb target/thumbv7em-none-eabihf/debug/examples/blinky
```
Please note that you can automate all steps except starting the GDB server by using a cargo
runner configuration, for example with the following lines in your `.cargo/config.toml` file:
```toml
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "gdb-multiarch -q -x jlink/jlink.gdb -tui"
```
After that, you can simply use `cargo run --example blinky` to flash the blinky
example.
### Using the RTT Viewer
The Segger RTT viewer can be used to display log messages received from the target. The base
address for the RTT block placement is 0x1fff8000. It is recommended to use a search range of
0x1000 around that base address when using the RTT viewer.
The RTT viewer will not be able to process `defmt` printouts. However, you can view the defmt
logs by [installing defmt-print](https://crates.io/crates/defmt-print) first and then running
```sh
defmt-print -e <pathToElfFile> tcp
```
The path of the ELF file which is being debugged needs to be specified for this to work.
## Learning (Embedded) Rust
If you are unfamiliar with Rust on Embedded Systems or Rust in general, the following resources
are recommended:
- [Rust Book](https://doc.rust-lang.org/book/)
- [Embedded Rust Book](https://docs.rust-embedded.org/book/)
- [Embedded Rust Discovery](https://docs.rust-embedded.org/discovery/microbit/)
- [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust)

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
"""
}
}
}

18
bootloader/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "bootloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
crc = "3"
static_assertions = "1"
[dependencies.va416xx-hal]
version = "0.5"
features = ["va41630", "defmt"]
path = "../va416xx-hal"

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.

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

@ -0,0 +1,347 @@
//! Vorago bootloader which can boot from two images.
//!
//! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
//! but does not scale that clock up.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use crc::{Crc, CRC_32_ISO_HDLC};
use defmt_rtt as _;
use panic_probe as _;
use va416xx_hal::{
clock::{pll_setup_delay, ClkDivSel, ClkselSys, ClockConfigurator},
edac,
nvm::Nvm,
pac::{self, interrupt},
time::Hertz,
wdt::Wdt,
};
const EXTCLK_FREQ: u32 = 40_000_000;
const WITH_WDT: bool = false;
const WDT_FREQ_MS: u32 = 50;
const DEBUG_PRINTOUTS: bool = true;
// Dangerous option! An image with this option set to true will flash itself from RAM directly
// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM
// boot process. Please note that this will flash an image which will also always perform the
// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash
// loader to boot a bootloader without this feature.
const FLASH_SELF: bool = false;
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
// the binary stays small enough.
const DEFMT_PRINTOUTS: bool = true;
// Important bootloader addresses and offsets, vector table information.
const NVM_SIZE: u32 = 0x40000;
const BOOTLOADER_START_ADDR: u32 = 0x0;
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 4;
const BOOTLOADER_END_ADDR: u32 = 0x4000;
// 0x4000
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
// The actual size of the image which is relevant for CRC calculation will be store at this
// address.
// 0x21FF8
const APP_A_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// 0x21FFC
const APP_A_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
pub const APP_A_END_ADDR: u32 = BOOTLOADER_END_ADDR + APP_IMG_SZ;
// 0x22000
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
// The actual size of the image which is relevant for CRC calculation will be stored at this
// address.
// 0x3FFF8
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// 0x3FFFC
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
// 0x40000
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
pub const APP_IMG_SZ: u32 = APP_B_END_ADDR - APP_A_START_ADDR / 2;
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
pub const VECTOR_TABLE_LEN: u32 = 0x350;
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
const CRC_ALGO: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
#[derive(Debug, Copy, Clone, PartialEq, Eq, defmt::Format)]
enum AppSel {
A,
B,
}
pub trait WdtInterface {
fn feed(&self);
}
pub struct OptWdt(Option<Wdt>);
impl WdtInterface for OptWdt {
fn feed(&self) {
if self.0.is_some() {
self.0.as_ref().unwrap().feed();
}
}
}
#[entry]
fn main() -> ! {
if DEFMT_PRINTOUTS {
defmt::println!("-- VA416xx bootloader --");
}
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// Disable ROM protection.
dp.sysconfig.rom_prot().write(|w| unsafe { w.bits(1) });
setup_edac(&mut dp.sysconfig);
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
let mut opt_wdt = OptWdt(None);
if WITH_WDT {
opt_wdt.0 = Some(Wdt::start(dp.watch_dog, &clocks, WDT_FREQ_MS));
}
let nvm = Nvm::new(dp.spi3, &clocks);
if FLASH_SELF {
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let bootloader_data = {
unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
)
}
};
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(bootloader_data);
let bootloader_crc = digest.finalize();
nvm.write_data(0x0, &first_four_bytes);
nvm.write_data(0x4, bootloader_data);
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
if DEFMT_PRINTOUTS {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
if DEFMT_PRINTOUTS {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes());
if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
if DEFMT_PRINTOUTS {
defmt::error!(
"error: CRC verification for bootloader self-flash failed: {:?}",
e
);
}
}
}
// Check bootloader's CRC (and write it if blank)
check_own_crc(&opt_wdt, &nvm, &cp);
if check_app_crc(AppSel::A, &opt_wdt) {
boot_app(AppSel::A, &cp)
} else if check_app_crc(AppSel::B, &opt_wdt) {
boot_app(AppSel::B, &cp)
} else {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
defmt::println!("both images corrupt! booting image A");
}
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
// Both images seem to be corrupt. Boot default image A.
boot_app(AppSel::A, &cp)
}
}
fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) {
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u32).read_unaligned().to_be() };
wdt.feed();
// I'd prefer to use [core::slice::from_raw_parts], but that is problematic
// because the address of the bootloader is 0x0, so the NULL check fails and the functions
// panics.
let mut first_four_bytes: [u8; 4] = [0; 4];
read_four_bytes_at_addr_zero(&mut first_four_bytes);
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(unsafe {
&*core::ptr::slice_from_raw_parts(
(BOOTLOADER_START_ADDR + 4) as *const u8,
(BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 8) as usize,
)
});
let crc_calc = digest.finalize();
wdt.feed();
if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
defmt::info!("BL CRC blank - prog new CRC");
}
// Blank CRC, write it to NVM.
nvm.write_data(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes());
// The Vorago bootloader resets here. I am not sure why this is done but I think it is
// necessary because somehow the boot will not work if we just continue as usual.
// cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
defmt::info!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc,
crc_exp
);
}
// TODO: Shift out minimal CCSDS frame to notify about bootloader corruption.
boot_app(AppSel::A, cp);
}
}
fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) {
unsafe {
core::arch::asm!(
"ldr r0, [{0}]", // Load 4 bytes from src into r0 register
"str r0, [{1}]", // Store r0 register into first_four_bytes
in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0)
in(reg) buf as *mut [u8; 4], // Input: destination pointer
);
}
}
fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
defmt::info!("Checking image {:?}", app_sel);
}
if app_sel == AppSel::A {
check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR, wdt)
} else {
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR, wdt)
}
}
fn check_app_given_addr(
crc_addr: u32,
start_addr: u32,
image_size_addr: u32,
wdt: &OptWdt,
) -> bool {
let crc_exp = unsafe { (crc_addr as *const u32).read_unaligned().to_be() };
let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
if DEFMT_PRINTOUTS {
defmt::info!("detected invalid app size {}", image_size);
}
return false;
}
wdt.feed();
let crc_calc = CRC_ALGO.checksum(unsafe {
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
});
wdt.feed();
if crc_calc == crc_exp {
return true;
}
false
}
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUTS {
defmt::info!("booting app {:?}", app_sel);
}
let clkgen = unsafe { pac::Clkgen::steal() };
clkgen
.ctrl0()
.modify(|_, w| unsafe { w.clksel_sys().bits(ClkselSys::Hbo as u8) });
pll_setup_delay();
clkgen
.ctrl0()
.modify(|_, w| unsafe { w.clk_div_sel().bits(ClkDivSel::Div1 as u8) });
// Clear all interrupts set.
unsafe {
cp.NVIC.icer[0].write(0xFFFFFFFF);
cp.NVIC.icpr[0].write(0xFFFFFFFF);
}
cortex_m::asm::dsb();
cortex_m::asm::isb();
unsafe {
if app_sel == AppSel::A {
cp.SCB.vtor.write(APP_A_START_ADDR);
} else {
cp.SCB.vtor.write(APP_B_START_ADDR);
}
}
cortex_m::asm::dsb();
cortex_m::asm::isb();
vector_reset();
}
pub fn vector_reset() -> ! {
unsafe {
// Set R0 to VTOR address (0xE000ED08)
let vtor_address: u32 = 0xE000ED08;
// Load VTOR
let vtor: u32 = *(vtor_address as *const u32);
// Load initial MSP value
let initial_msp: u32 = *(vtor as *const u32);
// Set SP value (assume MSP is selected)
core::arch::asm!("mov sp, {0}", in(reg) initial_msp);
// Load reset vector
let reset_vector: u32 = *((vtor + 4) as *const u32);
// Branch to reset handler
core::arch::asm!("bx {0}", in(reg) reset_vector);
}
unreachable!();
}
fn setup_edac(syscfg: &mut pac::Sysconfig) {
// The scrub values are based on the Vorago provided bootloader.
edac::enable_rom_scrub(syscfg, 125);
edac::enable_ram0_scrub(syscfg, 1000);
edac::enable_ram1_scrub(syscfg, 1000);
edac::enable_sbe_irq();
edac::enable_mbe_irq();
}
#[interrupt]
#[allow(non_snake_case)]
fn WATCHDOG() {
let wdt = unsafe { pac::WatchDog::steal() };
// Clear interrupt.
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
}
#[interrupt]
#[allow(non_snake_case)]
fn EDAC_SBE() {
// TODO: Send some command via UART for notification purposes. Also identify the problematic
// memory.
edac::clear_sbe_irq();
}
#[interrupt]
#[allow(non_snake_case)]
fn EDAC_MBE() {
// TODO: Send some command via UART for notification purposes.
edac::clear_mbe_irq();
// TODO: Reset like the vorago example?
}

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,42 @@
[package]
name = "embassy-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
cfg-if = "1"
embedded-io = "0.6"
embedded-can = "0.4"
embedded-hal-async = "1"
embedded-io-async = "0.6"
heapless = "0.8"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["print-defmt"] }
static_cell = "2"
critical-section = "1"
ringbuf = { version = "0.4", default-features = false }
nb = "1"
embassy-sync = "0.6"
embassy-time = "0.4"
embassy-executor = { version = "0.7", features = [
"arch-cortex-m",
"executor-thread",
"executor-interrupt"
]}
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["defmt"] }
va416xx-embassy = { version = "0.1", path = "../../va416xx-embassy", default-features = false }
[features]
default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"]
custom-irqs = []
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]
[package.metadata.cargo-machete]
ignored = ["cortex-m-rt"]

View File

@ -0,0 +1,353 @@
//! This example demonstrates the usage of async GPIO operations on VA416xx.
//!
//! You need to tie the PA0 to the PA1 pin for this example to work. You can optionally also tie
//! more pin combinations together and test other ports by setting the appropriate
//! [CHECK_XXX_TO_XXX] constants to true.
#![no_std]
#![no_main]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_sync::channel::{Receiver, Sender};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_time::{Duration, Instant, Timer};
use embedded_hal_async::digital::Wait;
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::gpio::asynch::{on_interrupt_for_async_gpio_for_port, InputPinAsync};
use va416xx_hal::gpio::{Input, Output, PinState, Port};
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::pins::{PinsA, PinsB, PinsC, PinsD, PinsE, PinsF, PinsG};
use va416xx_hal::time::Hertz;
const CHECK_PA0_TO_PA1: bool = true;
const CHECK_PB0_TO_PB1: bool = false;
const CHECK_PC14_TO_PC15: bool = false;
const CHECK_PD2_TO_PD3: bool = false;
const CHECK_PE0_TO_PE1: bool = false;
const CHECK_PF0_TO_PF1: bool = false;
#[derive(Clone, Copy)]
pub struct GpioCmd {
cmd_type: GpioCmdType,
after_delay: u32,
}
impl GpioCmd {
pub fn new(cmd_type: GpioCmdType, after_delay: u32) -> Self {
Self {
cmd_type,
after_delay,
}
}
}
#[derive(Clone, Copy)]
pub enum GpioCmdType {
SetHigh,
SetLow,
RisingEdge,
FallingEdge,
CloseTask,
}
// Declare a bounded channel of 3 u32s.
static CHANNEL_PA0_TO_PA1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PB0_TO_PB1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PC14_TO_PC15: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PD2_TO_PD3: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PE0_TO_PE1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PF0_TO_PF1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
#[embassy_executor::main]
async fn main(spawner: Spawner) {
defmt::println!("-- VA416xx Async GPIO Demo --");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let porta = PinsA::new(dp.porta);
let portb = PinsB::new(dp.portb);
let portc = PinsC::new(dp.portc);
let portd = PinsD::new(dp.portd);
let porte = PinsE::new(dp.porte);
let portf = PinsF::new(dp.portf);
let portg = PinsG::new(dp.portg);
let mut led = Output::new(portg.pg5, PinState::Low);
if CHECK_PA0_TO_PA1 {
let out_pin = Output::new(porta.pa0, PinState::Low);
let in_pin = Input::new_floating(porta.pa1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PA0 to PA1",
out_pin,
CHANNEL_PA0_TO_PA1.receiver(),
))
.unwrap();
check_pin_to_pin_async_ops("PA0 to PA1", CHANNEL_PA0_TO_PA1.sender(), in_pin).await;
defmt::info!("Example PA0 to PA1 done");
}
if CHECK_PB0_TO_PB1 {
let out_pin = Output::new(portb.pb0, PinState::Low);
let in_pin = Input::new_floating(portb.pb1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PB0 to PB1",
out_pin,
CHANNEL_PB0_TO_PB1.receiver(),
))
.unwrap();
check_pin_to_pin_async_ops("PB0 to PB1", CHANNEL_PB0_TO_PB1.sender(), in_pin).await;
defmt::info!("Example PB0 to PB1 done");
}
if CHECK_PC14_TO_PC15 {
let out_pin = Output::new(portc.pc14, PinState::Low);
let in_pin = Input::new_floating(portc.pc15);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PC14 to PC15",
out_pin,
CHANNEL_PC14_TO_PC15.receiver(),
))
.unwrap();
check_pin_to_pin_async_ops("PC14 to PC15", CHANNEL_PC14_TO_PC15.sender(), in_pin).await;
defmt::info!("Example PC14 to PC15 done");
}
if CHECK_PD2_TO_PD3 {
let out_pin = Output::new(portd.pd2, PinState::Low);
let in_pin = Input::new_floating(portd.pd3);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PD2 to PD3",
out_pin,
CHANNEL_PD2_TO_PD3.receiver(),
))
.unwrap();
check_pin_to_pin_async_ops("PD2 to PD3", CHANNEL_PD2_TO_PD3.sender(), in_pin).await;
defmt::info!("Example PD2 to PD3 done");
}
if CHECK_PE0_TO_PE1 {
let out_pin = Output::new(porte.pe0, PinState::Low);
let in_pin = Input::new_floating(porte.pe1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PE0 to PE1",
out_pin,
CHANNEL_PE0_TO_PE1.receiver(),
))
.unwrap();
check_pin_to_pin_async_ops("PE0 to PE1", CHANNEL_PE0_TO_PE1.sender(), in_pin).await;
defmt::info!("Example PE0 to PE1 done");
}
if CHECK_PF0_TO_PF1 {
let out_pin = Output::new(portf.pf0, PinState::Low);
let in_pin = Input::new_floating(portf.pf1);
let in_pin = InputPinAsync::new(in_pin).unwrap();
spawner
.spawn(output_task(
"PF0 to PF1",
out_pin,
CHANNEL_PF0_TO_PF1.receiver(),
))
.unwrap();
check_pin_to_pin_async_ops("PF0 to PF1", CHANNEL_PF0_TO_PF1.sender(), in_pin).await;
defmt::info!("Example PF0 to PF1 done");
}
defmt::info!("Example done, toggling LED0");
loop {
led.toggle();
Timer::after(Duration::from_millis(500)).await;
}
}
async fn check_pin_to_pin_async_ops(
ctx: &'static str,
sender: Sender<'static, ThreadModeRawMutex, GpioCmd, 3>,
mut async_input: impl Wait,
) {
defmt::info!(
"{}: sending SetHigh command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::SetHigh, 20)).await;
async_input.wait_for_high().await.unwrap();
defmt::info!(
"{}: Input pin is high now ({} ms)",
ctx,
Instant::now().as_millis()
);
defmt::info!(
"{}: sending SetLow command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::SetLow, 20)).await;
async_input.wait_for_low().await.unwrap();
defmt::info!(
"{}: Input pin is low now ({} ms)",
ctx,
Instant::now().as_millis()
);
defmt::info!(
"{}: sending RisingEdge command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
async_input.wait_for_rising_edge().await.unwrap();
defmt::info!(
"{}: input pin had rising edge ({} ms)",
ctx,
Instant::now().as_millis()
);
defmt::info!(
"{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
.await;
async_input.wait_for_falling_edge().await.unwrap();
defmt::info!(
"{}: input pin had a falling edge ({} ms)",
ctx,
Instant::now().as_millis()
);
defmt::info!(
"{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
.await;
async_input.wait_for_any_edge().await.unwrap();
defmt::info!(
"{}: input pin had a falling (any) edge ({} ms)",
ctx,
Instant::now().as_millis()
);
defmt::info!(
"{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
async_input.wait_for_any_edge().await.unwrap();
defmt::info!(
"{}: input pin had a rising (any) edge ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::CloseTask, 0)).await;
}
#[embassy_executor::task(pool_size = 8)]
async fn output_task(
ctx: &'static str,
mut out: Output,
receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
) {
loop {
let next_cmd = receiver.receive().await;
Timer::after(Duration::from_millis(next_cmd.after_delay.into())).await;
match next_cmd.cmd_type {
GpioCmdType::SetHigh => {
defmt::info!("{}: Set output high", ctx);
out.set_high();
}
GpioCmdType::SetLow => {
defmt::info!("{}: Set output low", ctx);
out.set_low();
}
GpioCmdType::RisingEdge => {
defmt::info!("{}: Rising edge", ctx);
if !out.is_set_low() {
out.set_low();
}
out.set_high();
}
GpioCmdType::FallingEdge => {
defmt::info!("{}: Falling edge", ctx);
if !out.is_set_high() {
out.set_high();
}
out.set_low();
}
GpioCmdType::CloseTask => {
defmt::info!("{}: Closing task", ctx);
break;
}
}
}
}
#[interrupt]
#[allow(non_snake_case)]
fn PORTA1() {
on_interrupt_for_async_gpio_for_port(Port::A).unwrap();
}
#[interrupt]
#[allow(non_snake_case)]
fn PORTB1() {
on_interrupt_for_async_gpio_for_port(Port::B).unwrap();
}
#[interrupt]
#[allow(non_snake_case)]
fn PORTC15() {
on_interrupt_for_async_gpio_for_port(Port::C).unwrap();
}
#[interrupt]
#[allow(non_snake_case)]
fn PORTD3() {
on_interrupt_for_async_gpio_for_port(Port::D).unwrap();
}
#[interrupt]
#[allow(non_snake_case)]
fn PORTE1() {
on_interrupt_for_async_gpio_for_port(Port::E).unwrap();
}
#[interrupt]
#[allow(non_snake_case)]
fn PORTF1() {
on_interrupt_for_async_gpio_for_port(Port::F).unwrap();
}

View File

@ -0,0 +1,103 @@
//! Asynchronous UART reception example application.
//!
//! This application receives data on two UARTs permanently using a ring buffer.
//! The ring buffer are read them asynchronously.
//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board.
//!
//! Instructions:
//!
//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1.
//! 2. Connect to the serial interface by using an application like Putty or picocom. You can
//! type something in the terminal and check if the data is echoed back. You can also check the
//! RTT logs to see received data.
#![no_std]
#![no_main]
// Import panic provider.
use panic_probe as _;
// Import logger.
use core::cell::RefCell;
use defmt_rtt as _;
use defmt_rtt as _;
use critical_section::Mutex;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::Instant;
use embedded_io::Write;
use embedded_io_async::Read;
use heapless::spsc::{Producer, Queue};
use va416xx_hal::{
clock::ClockConfigurator,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsG,
prelude::*,
time::Hertz,
uart::{
self,
rx_asynch::{on_interrupt_rx, RxAsync},
Bank,
},
};
static QUEUE_UART_A: static_cell::ConstStaticCell<Queue<u8, 256>> =
static_cell::ConstStaticCell::new(Queue::new());
static PRODUCER_UART_A: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(RefCell::new(None));
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
defmt::println!("-- VA108xx Async UART RX Demo --");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let portg = PinsG::new(dp.portg);
let mut led = Output::new(portg.pg5, PinState::Low);
let uarta =
uart::Uart::new(dp.uart0, portg.pg0, portg.pg1, &clocks, 115200.Hz().into()).unwrap();
let (mut tx_uart_a, rx_uart_a) = uarta.split();
let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();
// Pass the producer to the interrupt handler.
critical_section::with(|cs| {
*PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod_uart_a);
});
// TODO: Add example for RxAsyncOverwriting using another UART.
let mut async_uart_rx = RxAsync::new(rx_uart_a, cons_uart_a);
let mut buf = [0u8; 256];
loop {
defmt::info!("Current time UART A: {}", Instant::now().as_secs());
led.toggle();
let read_bytes = async_uart_rx.read(&mut buf).await.unwrap();
let read_str = core::str::from_utf8(&buf[..read_bytes]).unwrap();
defmt::info!(
"Read {} bytes asynchronously on UART A: {:?}",
read_bytes,
read_str
);
tx_uart_a.write_all(read_str.as_bytes()).unwrap();
}
}
#[interrupt]
#[allow(non_snake_case)]
fn UART0_RX() {
let mut prod =
critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap());
let errors = on_interrupt_rx(Bank::Uart0, &mut prod);
critical_section::with(|cs| *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod));
// In a production app, we could use a channel to send the errors to the main task.
if let Err(errors) = errors {
defmt::info!("UART A errors: {:?}", errors);
}
}

View File

@ -0,0 +1,88 @@
//! Asynchronous UART transmission example application.
//!
//! This application receives sends 4 strings with different sizes permanently.
//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board.
//!
//! Instructions:
//!
//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1.
//! 2. Connect to the serial interface by using an application like Putty or picocom. You can
//! type something in the terminal and check if the data is echoed back. You can also check the
//! RTT logs to see received data.
#![no_std]
#![no_main]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_io_async::Write;
use va416xx_hal::{
clock::ClockConfigurator,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsG,
prelude::*,
time::Hertz,
uart::{
self,
tx_asynch::{on_interrupt_tx, TxAsync},
Bank,
},
};
const STR_LIST: &[&str] = &[
"Hello World\r\n",
"Smoll\r\n",
"A string which is larger than the FIFO size\r\n",
"A really large string which is significantly larger than the FIFO size\r\n",
];
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
defmt::println!("-- VA108xx Async UART TX Demo --");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let pinsg = PinsG::new(dp.portg);
let mut led = Output::new(pinsg.pg5, PinState::Low);
let uarta =
uart::Uart::new(dp.uart0, pinsg.pg0, pinsg.pg1, &clocks, 115200.Hz().into()).unwrap();
let (tx, _rx) = uarta.split();
let mut async_tx = TxAsync::new(tx);
let mut ticker = Ticker::every(Duration::from_secs(1));
let mut idx = 0;
loop {
defmt::println!("Current time: {}", Instant::now().as_secs());
led.toggle();
let _written = async_tx
.write(STR_LIST[idx].as_bytes())
.await
.expect("writing failed");
idx += 1;
if idx == STR_LIST.len() {
idx = 0;
}
ticker.next().await;
}
}
#[interrupt]
#[allow(non_snake_case)]
fn UART0_TX() {
on_interrupt_tx(Bank::Uart0);
}

View File

@ -0,0 +1,239 @@
#![no_std]
#![no_main]
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use va416xx_hal::can::asynch::{on_interrupt_can, CanTxAsync};
use va416xx_hal::can::{
Can, CanFrame, CanFrameNormal, CanFrameRtr, CanId, CanRx, CanTx, ClockConfig,
};
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::time::Hertz;
use va416xx_hal::{can, prelude::*};
const STANDARD_ID_0: can::StandardId = can::StandardId::new(0x42).unwrap();
const STANDARD_ID_1: can::StandardId = can::StandardId::new(0x5).unwrap();
const EXTENDED_ID_0: can::ExtendedId = can::ExtendedId::new(0x10).unwrap();
// Declare a bounded channel of 3 u32s.
static CAN_RX_CHANNEL: embassy_sync::channel::Channel<
CriticalSectionRawMutex,
(usize, CanFrame),
3,
> = embassy_sync::channel::Channel::<CriticalSectionRawMutex, (usize, CanFrame), 3>::new();
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
defmt::println!("-- VA416xx CAN Demo --");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
defmt::info!("creating CAN peripheral driver");
defmt::info!("clocks: {}", clocks);
let clk_config = ClockConfig::from_bitrate_and_segments(&clocks, 250.kHz(), 14, 5, 4)
.expect("CAN clock config error");
let mut can = Can::new(dp.can0, clk_config);
can.modify_control(|mut val| {
val.set_loopback(true);
val.set_ignore_ack(true);
val.set_internal(true);
val.set_bufflock(true);
val.set_diag_enable(true);
val
});
can.set_global_mask_for_exact_id_match_with_rtr_masked();
can.set_base_mask_for_all_match();
can.enable();
let mut channels = can.take_channels().unwrap();
// Transmit channel.
let mut tx = CanTx::new(channels.take(0).unwrap(), None);
// Base channel which has dedicated mask.
let mut rx_dedicated = CanRx::new(channels.take(1).unwrap());
// Base channel which has dedicated mask.
let mut rx_base = CanRx::new(channels.take(14).unwrap());
rx_base.configure_for_reception();
defmt::info!("Running blocking examples");
send_and_receive_on_dedicated_channel(&mut can, &mut tx, &mut rx_dedicated);
send_and_receive_rtr_on_dedicated_channel(&mut can, &mut tx, &mut rx_dedicated);
send_extended_on_base_channel(&mut can, &mut tx, &mut rx_base);
defmt::info!("Running non-blocking (asycnhronous) examples");
non_blocking_example(&mut can, &mut rx_dedicated, &mut rx_base).await;
defmt::info!("Non-blocking (asycnhronous) examples done");
loop {
cortex_m::asm::nop();
}
}
fn send_and_receive_on_dedicated_channel(can: &mut Can, tx: &mut CanTx, rx_dedicated: &mut CanRx) {
let send_data = &[1, 2, 3, 4];
let sent_frame =
CanFrame::Normal(CanFrameNormal::new(can::Id::Standard(STANDARD_ID_0), send_data).unwrap());
defmt::info!(
"sending CAN frame with ID {:#X} and data {}",
STANDARD_ID_0.as_raw(),
send_data
);
rx_dedicated.configure_for_reception_with_standard_id(STANDARD_ID_0, false);
tx.transmit_frame(sent_frame).unwrap();
// Await frame transmission completion.
nb::block!(tx.transfer_done()).unwrap();
check_and_handle_errors(can);
let received_frame = nb::block!(rx_dedicated.receive(true)).expect("invalid CAN rx state");
check_and_handle_errors(can);
assert_eq!(received_frame, sent_frame);
if let CanFrame::Normal(can_frame_normal) = received_frame {
if let can::Id::Standard(standard_id) = can_frame_normal.id() {
defmt::info!(
"received CAN frame with ID {:#X} and data {}",
standard_id.as_raw(),
can_frame_normal.data()
);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN remote frame");
}
}
fn send_and_receive_rtr_on_dedicated_channel(
can: &mut Can,
tx: &mut CanTx,
rx_dedicated: &mut CanRx,
) {
let rtr_frame = CanFrame::Rtr(CanFrameRtr::new(can::Id::Standard(STANDARD_ID_1), 0));
// RTR bit is masked, so the setting should not matter.
rx_dedicated.configure_for_reception_with_standard_id(STANDARD_ID_1, false);
tx.transmit_frame(rtr_frame).unwrap();
// Await frame transmission completion.
nb::block!(tx.remote_transfer_done_with_tx_reconfig()).unwrap();
check_and_handle_errors(can);
let received_frame = nb::block!(rx_dedicated.receive(true)).expect("invalid CAN rx state");
check_and_handle_errors(can);
assert_eq!(received_frame, rtr_frame);
if let CanFrame::Rtr(can_frame_rtr) = received_frame {
if let can::Id::Standard(standard_id) = can_frame_rtr.id() {
defmt::info!("received CAN RTR frame with ID {:#X}", standard_id.as_raw(),);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN data frame");
}
}
fn check_and_handle_errors(can: &mut Can) {
let err_counter = can.read_error_counters();
if err_counter.transmit() > 0 || err_counter.receive() > 0 {
defmt::warn!(
"error count tx {}, error count rx {}",
err_counter.transmit(),
err_counter.receive()
);
let diag = can.read_error_diagnostics();
defmt::warn!("EFID: {}, EBID: {}", diag.efid(), diag.ebid());
}
}
fn send_extended_on_base_channel(can: &mut Can, tx: &mut CanTx, rx: &mut CanRx) {
let send_data = &[4, 3, 2, 1];
let sent_frame =
CanFrame::Normal(CanFrameNormal::new(can::Id::Extended(EXTENDED_ID_0), send_data).unwrap());
tx.transmit_frame(sent_frame).unwrap();
// Await frame transmission completion.
nb::block!(tx.transfer_done()).unwrap();
check_and_handle_errors(can);
let received_frame = nb::block!(rx.receive(true)).expect("invalid CAN rx state");
check_and_handle_errors(can);
assert_eq!(sent_frame, received_frame);
if let CanFrame::Normal(can_frame_normal) = received_frame {
if let can::Id::Extended(extended_id) = can_frame_normal.id() {
defmt::info!(
"received CAN frame with ID {:#X} and data {}",
extended_id.as_raw(),
can_frame_normal.data()
);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN data frame");
}
}
async fn non_blocking_example(can: &mut Can, rx_dedicated: &mut CanRx, rx_base: &mut CanRx) {
let mut tx_async = CanTxAsync::new(can);
// Enable interrupts for RX channels.
rx_dedicated.enable_interrupt(true);
rx_base.enable_interrupt(true);
// For asynchronous mode, all TX channels needs to be configured explicitely. Configuring more
// channels allows multiple active transfers when using the async API.
tx_async.configure_channel(0).unwrap();
let send_data = &[1, 2, 3, 4];
let send_frame =
CanFrame::Normal(CanFrameNormal::new(can::Id::Standard(STANDARD_ID_0), send_data).unwrap());
let fut = tx_async.start_transmit(send_frame).unwrap();
fut.await;
let (ch_idx, frame) = CAN_RX_CHANNEL.receive().await;
assert_eq!(send_frame, frame);
// Received on base channel.
assert_eq!(ch_idx, 14);
if let CanFrame::Normal(can_frame_normal) = frame {
if let can::Id::Standard(standard_id) = can_frame_normal.id() {
defmt::info!(
"received CAN frame with ID {:#X} and data {}",
standard_id.as_raw(),
can_frame_normal.data()
);
} else {
panic!("unexpected CAN extended frame ID");
}
} else {
defmt::error!("received unexpected CAN remote frame");
}
}
#[interrupt]
#[allow(non_snake_case)]
fn CAN0() {
match on_interrupt_can(CanId::Can0, false).unwrap() {
can::asynch::InterruptResult::NoInterrupt => {
defmt::warn!("unexpected interrupt on CAN0");
}
can::asynch::InterruptResult::ReceivedFrame {
channel_index,
frame,
} => {
CAN_RX_CHANNEL.try_send((channel_index, frame)).unwrap();
}
can::asynch::InterruptResult::TransmissionEvent { channel_index, id } => {
defmt::info!(
"transmission event on channel {} with event ID {}",
channel_index,
id
);
}
}
}

View File

@ -0,0 +1,151 @@
//! This is an example of using the UART HAL abstraction with the IRQ support and embassy.
//!
//! It uses the UART0 for communication with another MCU or a host computer (recommended).
//! You can connect a USB-to-Serial converter to the UART0 pins and then use a serial terminal
//! application like picocom to send data to the microcontroller, which should be echoed
//! back to the sender.
//!
//! This application uses the interrupt support of the VA416xx to read the data arriving
//! on the UART without requiring polling.
#![no_std]
#![no_main]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use core::cell::RefCell;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::{Duration, Ticker};
use embedded_io::Write;
use ringbuf::{
traits::{Consumer, Observer, Producer},
StaticRb,
};
use va416xx_hal::{
clock::ClockConfigurator,
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsG,
time::Hertz,
uart,
};
pub type SharedUart = Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt>>>;
static RX: SharedUart = Mutex::new(RefCell::new(None));
const BAUDRATE: u32 = 115200;
// Ring buffer size.
const RING_BUF_SIZE: usize = 2048;
pub type SharedRingBuf =
Mutex<CriticalSectionRawMutex, RefCell<Option<StaticRb<u8, RING_BUF_SIZE>>>>;
// Ring buffers to handling variable sized telemetry
static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None));
// See https://embassy.dev/book/#_sharing_using_a_mutex for background information about sharing
// a peripheral with embassy.
#[embassy_executor::main]
async fn main(spawner: Spawner) {
defmt::println!("VA416xx UART-Embassy Example");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
let portg = PinsG::new(dp.portg);
let uart0 = uart::Uart::new(
dp.uart0,
portg.pg0,
portg.pg1,
&clocks,
Hertz::from_raw(BAUDRATE).into(),
)
.unwrap();
let (mut tx, rx) = uart0.split();
let mut rx = rx.into_rx_with_irq();
rx.start();
RX.lock(|static_rx| {
static_rx.borrow_mut().replace(rx);
});
RINGBUF.lock(|static_rb| {
static_rb.borrow_mut().replace(StaticRb::default());
});
let led = Output::new(portg.pg5, PinState::Low);
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE];
let mut read_bytes = 0;
spawner.spawn(blinky(led)).expect("failed to spawn blinky");
loop {
RINGBUF.lock(|static_rb| {
let mut rb_borrow = static_rb.borrow_mut();
let rb_mut = rb_borrow.as_mut().unwrap();
read_bytes = rb_mut.occupied_len();
rb_mut.pop_slice(&mut processing_buf[0..read_bytes]);
});
// Simply send back all received data.
tx.write_all(&processing_buf[0..read_bytes])
.expect("sending back read data failed");
ticker.next().await;
}
}
#[embassy_executor::task]
async fn blinky(mut led: Output) {
let mut ticker = Ticker::every(Duration::from_millis(500));
loop {
led.toggle();
ticker.next().await;
}
}
#[interrupt]
#[allow(non_snake_case)]
fn UART0_RX() {
let mut buf: [u8; 16] = [0; 16];
let mut read_len: usize = 0;
let mut errors = None;
RX.lock(|static_rx| {
let mut rx_borrow = static_rx.borrow_mut();
let rx_mut_ref = rx_borrow.as_mut().unwrap();
let result = rx_mut_ref.on_interrupt(&mut buf);
read_len = result.bytes_read;
if result.errors.is_some() {
errors = result.errors;
}
});
let mut ringbuf_full = false;
if read_len > 0 {
// Send the received buffer to the main thread for processing via a ring buffer.
RINGBUF.lock(|static_rb| {
let mut rb_borrow = static_rb.borrow_mut();
let rb_mut_ref = rb_borrow.as_mut().unwrap();
if rb_mut_ref.vacant_len() < read_len {
ringbuf_full = true;
for _ in rb_mut_ref.pop_iter() {}
}
rb_mut_ref.push_slice(&buf[0..read_len]);
});
}
if errors.is_some() {
defmt::info!("UART error: {:?}", errors);
}
if ringbuf_full {
defmt::info!("ringbuffer is full, deleted oldest data");
}
}

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]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use va416xx_hal::{
clock::ClockConfigurator,
gpio::{Output, PinState},
pac,
pins::PinsG,
time::Hertz,
};
cfg_if::cfg_if! {
if #[cfg(feature = "custom-irqs")] {
use va416xx_hal::pac::interrupt;
va416xx_embassy::embassy_time_driver_irqs!(timekeeper_irq = TIM12, alarm_irq = TIM11);
}
}
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
defmt::println!("VA416xx Embassy Demo");
let dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
// Safety: Only called once here.
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va416xx_embassy::init(
dp.tim15,
dp.tim14,
&clocks
);
} else {
va416xx_embassy::init(
dp.tim12,
dp.tim11,
&clocks
);
}
}
let pinsg = PinsG::new(dp.portg);
let mut led = Output::new(pinsg.pg5, PinState::Low);
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;
defmt::info!("Current time: {}", Instant::now().as_secs());
led.toggle();
}
}

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

@ -0,0 +1,20 @@
[package]
name = "rtic-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630"] }
[dependencies.rtic]
version = "2"
features = ["thumbv7-backend"]
[dependencies.rtic-monotonics]
version = "2"
features = ["cortex-m-systick"]

View File

@ -0,0 +1,31 @@
//! Empty RTIC project template
#![no_main]
#![no_std]
#[rtic::app(device = pac)]
mod app {
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va416xx_hal::pac;
#[local]
struct Local {}
#[shared]
struct Shared {}
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago RTIC template --");
(Shared {}, Local {})
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
#[allow(clippy::empty_loop)]
loop {}
}
}

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

@ -0,0 +1,69 @@
//! RTIC minimal blinky
#![no_main]
#![no_std]
use va416xx_hal::time::Hertz;
const EXTCLK_FREQ: Hertz = Hertz::from_raw(40_000_000);
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app {
use super::*;
// Import panic provider.
use panic_probe as _;
// Import logger.
use cortex_m::asm;
use defmt_rtt as _;
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
use va416xx_hal::{
clock::ClockConfigurator,
gpio::{Output, PinState},
pac,
pins::PinsG,
};
#[local]
struct Local {
led: Output,
}
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago RTIC example application --");
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(cx.device.clkgen)
.xtal_n_clk_with_src_freq(EXTCLK_FREQ)
.freeze()
.unwrap();
Mono::start(cx.core.SYST, clocks.sysclk().raw());
let pinsg = PinsG::new(cx.device.portg);
let led = Output::new(pinsg.pg5, PinState::Low);
blinky::spawn().ok();
(Shared {}, Local { led })
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 3,
local=[led],
)]
async fn blinky(cx: blinky::Context) {
loop {
cx.local.led.toggle();
Mono::delay(200.millis()).await;
}
}
}

View File

@ -4,15 +4,40 @@ 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"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
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", path = "../../va416xx-hal", features = ["va41630", "defmt"] }
[dependencies.vorago-peb1]
path = "../../vorago-peb1"
optional = true
[features]
default = ["va41630"]
va41630 = ["va416xx-hal/va41630", "has-adc-dac"]
va41629 = ["va416xx-hal/va41629", "has-adc-dac"]
va41628 = ["va416xx-hal/va41628"]
has-adc-dac = []
[[example]]
name = "peb1-accelerometer"
required-features = ["vorago-peb1"]
[[example]]
name = "dac-adc"
required-features = ["has-adc-dac"]
[[example]]
name = "adc"
required-features = ["has-adc-dac"]

View File

@ -2,15 +2,18 @@
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{
adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect},
clock::ClockConfigurator,
pac,
prelude::*,
timer::CountdownTimer,
};
@ -19,32 +22,32 @@ const ENABLE_BUF_PRINTOUT: bool = false;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx ADC example");
defmt::println!("VA416xx ADC example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let adc = Adc::new_with_channel_tag(&mut dp.sysconfig, dp.adc, &clocks);
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let adc = Adc::new_with_channel_tag(dp.adc, &clocks);
let mut delay = CountdownTimer::new(dp.tim0, &clocks);
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);
defmt::info!(
"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");
if ENABLE_BUF_PRINTOUT {
rprintln!("ADC Range Read (0-8) read {} values", read_num);
rprintln!("ADC Range Read (0-8): {:?}", read_buf);
defmt::info!("ADC Range Read (0-8) read {} values", read_num);
defmt::info!("ADC Range Read (0-8): {:?}", read_buf);
}
assert_eq!(read_num, 8);
for (idx, ch_val) in read_buf.iter().enumerate() {
@ -60,11 +63,11 @@ fn main() -> ! {
)
.expect("ADC multiselect read failed");
if ENABLE_BUF_PRINTOUT {
rprintln!("ADC Multiselect Read(0, 2 and 10): {:?}", &read_buf[0..3]);
defmt::info!("ADC Multiselect Read(0, 2 and 10): {:?}", &read_buf[0..3]);
}
assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0);
assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2);
assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor);
delay_provider.delay_ms(500);
delay.delay_ms(500);
}
}

View File

@ -2,22 +2,27 @@
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
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};
use va416xx_hal::{
gpio::{Output, PinState},
pac,
pins::PinsG,
};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx HAL blinky example");
defmt::println!("VA416xx HAL blinky example");
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();
let dp = pac::Peripherals::take().unwrap();
let portg = PinsG::new(dp.portg);
let mut led = Output::new(portg.pg5, PinState::Low);
loop {
cortex_m::asm::delay(2_000_000);
led.toggle().ok();
led.toggle();
}
}

View File

@ -2,12 +2,15 @@
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{adc::Adc, dac::Dac, pac, prelude::*, timer::CountdownTimer};
use va416xx_hal::{adc::Adc, clock::ClockConfigurator, dac::Dac, pac, timer::CountdownTimer};
const DAC_INCREMENT: u16 = 256;
@ -25,21 +28,17 @@ const APP_MODE: AppMode = AppMode::DacAndAdc;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA416xx DAC/ADC example");
defmt::println!("VA416xx DAC/ADC example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut dac = None;
if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc {
dac = Some(Dac::new(
&mut dp.sysconfig,
dp.dac0,
va416xx_hal::dac::DacSettling::Apb2Times100,
&clocks,
@ -47,13 +46,13 @@ fn main() -> ! {
}
let mut adc = None;
if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc {
adc = Some(Adc::new(&mut dp.sysconfig, dp.adc, &clocks));
adc = Some(Adc::new(dp.adc, &clocks));
}
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let mut delay_provider = CountdownTimer::new(dp.tim0, &clocks);
let mut current_val = 0;
loop {
if let Some(dac) = &dac {
rprintln!("loading DAC with value {}", current_val);
if let Some(dac) = &mut dac {
defmt::info!("loading DAC with value {}", current_val);
dac.load_and_trigger_manually(current_val)
.expect("loading DAC value failed");
if current_val + DAC_INCREMENT >= 4096 {
@ -70,7 +69,7 @@ fn main() -> ! {
let ch_value = adc
.trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0)
.expect("reading ADC channel 0 failed");
rprintln!("Received channel value {:?}", ch_value);
defmt::info!("Received channel value {:?}", ch_value);
}
delay_provider.delay_ms(500);

View File

@ -2,20 +2,22 @@
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va416xx_hal::clock::ClockConfigurator;
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
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::pwm::CountdownTimer;
use va416xx_hal::{
pac::{self, interrupt},
prelude::*,
};
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::pac::{self, interrupt};
use va416xx_hal::timer::CountdownTimer;
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
@ -25,34 +27,40 @@ static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
#[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");
defmt::println!("VA416xx DMA example");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
enable_and_init_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(), unsafe {
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK)
})
let dma = Dma::new(
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 delay_ms = CountdownTimer::new(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_16_bit: [u16; 33] = [0; 33];
let mut dest_buf_16_bit: [u16; 33] = [0; 33];
let mut src_buf_32_bit: [u32; 17] = [0; 17];
let mut 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,
@ -60,12 +68,8 @@ fn main() -> ! {
&mut delay_ms,
);
delay_ms.delay_ms(500);
transfer_example_16_bit(
&mut src_buf_16_bit,
&mut dest_buf_16_bit,
&mut dma0,
&mut delay_ms,
);
// 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,
@ -81,20 +85,22 @@ fn transfer_example_8_bit(
src_buf: &mut [u8; 65],
dest_buf: &mut [u8; 65],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
delay: &mut CountdownTimer,
) {
(0..64).for_each(|i| {
src_buf[i] = i as u8;
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
let dma_transfer = dma0
.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
.expect("error preparing transfer");
// 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 {
@ -105,13 +111,12 @@ fn transfer_example_8_bit(
dma0.enable();
// We still need to manually trigger the DMA request.
dma0.trigger_with_sw_request();
let dest_buf;
// Use polling for completion status.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 8 bit transfer");
defmt::info!("DMA0 is active with 8 bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
@ -119,12 +124,10 @@ fn transfer_example_8_bit(
}
});
if dma_done {
rprintln!("8-bit transfer done");
// Safety: We checked for transfer completion.
dest_buf = unsafe { dma_transfer.release() };
defmt::info!("8-bit transfer done");
break;
}
delay_ms.delay_ms(1);
delay.delay_ms(1);
}
(0..64).for_each(|i| {
assert_eq!(dest_buf[i], i as u8);
@ -134,26 +137,28 @@ fn transfer_example_8_bit(
dest_buf.fill(0);
}
fn transfer_example_16_bit(
src_buf: &mut [u16; 33],
dest_buf: &mut [u16; 33],
dma0: &mut DmaChannel,
delay_ms: &mut CountdownTimer<pac::Tim0>,
) {
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
(0..32).for_each(|i| {
src_buf[i] = (i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16;
});
cortex_m::interrupt::free(|cs| {
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer) {
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);
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
let dma_transfer = dma0
.prepare_mem_to_mem_transfer_16_bit(src_buf, dest_buf)
// 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");
dest_buf[5] = 2;
}
// Enable all interrupts.
// Safety: Not using mask based critical sections.
unsafe {
@ -167,9 +172,9 @@ fn transfer_example_16_bit(
// Use polling for completion status.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 16-bit transfer");
defmt::info!("DMA0 is active with 16-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
@ -177,40 +182,43 @@ fn transfer_example_16_bit(
}
});
if dma_done {
rprintln!("16-bit transfer done");
defmt::info!("16-bit transfer done");
break;
}
delay_ms.delay_ms(1);
}
(0..32).for_each(|i| {
assert_eq!(
dest_buf[i],
(i as u32 * u16::MAX as u32 / (src_buf.len() - 1) as u32) as u16
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[32], 0);
dest_buf.fill(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>,
delay_ms: &mut CountdownTimer,
) {
// 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;
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(false);
});
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(false);
});
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
.expect("error preparing transfer");
// 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 {
@ -224,9 +232,9 @@ fn transfer_example_32_bit(
// Use polling for completion status.
loop {
let mut dma_done = false;
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
if DMA_ACTIVE_FLAG.borrow(cs).get() {
rprintln!("DMA0 is active with 32-bit transfer");
defmt::info!("DMA0 is active with 32-bit transfer");
DMA_ACTIVE_FLAG.borrow(cs).set(false);
}
if DMA_DONE_FLAG.borrow(cs).get() {
@ -234,7 +242,7 @@ fn transfer_example_32_bit(
}
});
if dma_done {
rprintln!("32-bit transfer done");
defmt::info!("32-bit transfer done");
break;
}
delay_ms.delay_ms(1);
@ -254,7 +262,7 @@ fn transfer_example_32_bit(
#[allow(non_snake_case)]
fn DMA_DONE0() {
// Notify the main loop that the DMA transfer is finished.
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_DONE_FLAG.borrow(cs).set(true);
});
}
@ -263,7 +271,7 @@ fn DMA_DONE0() {
#[allow(non_snake_case)]
fn DMA_ACTIVE0() {
// Notify the main loop that the DMA 0 is active now.
cortex_m::interrupt::free(|cs| {
critical_section::with(|cs| {
DMA_ACTIVE_FLAG.borrow(cs).set(true);
});
}

View File

@ -2,17 +2,21 @@
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use accelerometer::{Accelerometer, RawAccelerometer};
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{
clock::ClockConfigurator,
i2c,
pac::{self},
prelude::*,
pwm::CountdownTimer,
timer::CountdownTimer,
};
use vorago_peb1::lis2dh12::{self, detect_i2c_addr, FullScale, Odr};
@ -25,25 +29,21 @@ const DISPLAY_MODE: DisplayMode = DisplayMode::Normalized;
#[entry]
fn main() -> ! {
rtt_init_print!();
let mut dp = pac::Peripherals::take().unwrap();
rprintln!("-- Vorago PEB1 accelerometer example --");
defmt::println!("-- Vorago PEB1 accelerometer example --");
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut i2c_master = i2c::I2cMaster::new(
dp.i2c0,
&mut dp.sysconfig,
i2c::MasterConfig::default(),
&clocks,
i2c::MasterConfig::default(),
i2c::I2cSpeed::Regular100khz,
)
.expect("creating I2C master failed");
let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
let mut delay_provider = CountdownTimer::new(dp.tim1, &clocks);
// Detect the I2C address of the accelerometer by scanning all possible values.
let slave_addr = detect_i2c_addr(&mut i2c_master).expect("detecting I2C address failed");
// Create the accelerometer driver using the PEB1 BSP.
@ -51,7 +51,7 @@ fn main() -> ! {
.expect("creating accelerometer driver failed");
let device_id = accelerometer.get_device_id().unwrap();
accelerometer
.set_mode(lis2dh12::reg::Mode::Normal)
.set_mode(lis2dh12::Mode::Normal)
.expect("setting mode failed");
accelerometer
.set_odr(Odr::Hz100)
@ -63,7 +63,7 @@ fn main() -> ! {
accelerometer
.enable_temp(true)
.expect("enabling temperature sensor failed");
rprintln!("Device ID: 0x{:02X}", device_id);
defmt::info!("Device ID: 0x{:02X}", device_id);
// Start reading the accelerometer periodically.
loop {
let temperature = accelerometer
@ -74,13 +74,25 @@ fn main() -> ! {
let value = accelerometer
.accel_norm()
.expect("reading normalized accelerometer data failed");
rprintln!("Accel Norm F32x3: {:.06?} | Temp {} °C", value, temperature);
defmt::info!(
"Accel Norm F32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
value.x,
value.y,
value.z,
temperature
);
}
DisplayMode::Raw => {
let value_raw = accelerometer
.accel_raw()
.expect("reading raw accelerometer data failed");
rprintln!("Accel Raw F32x3: {:?} | Temp {} °C", value_raw, temperature);
defmt::info!(
"Accel Raw I32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
value_raw.x,
value_raw.y,
value_raw.z,
temperature
);
}
}
delay_provider.delay_ms(100);

View File

@ -1,48 +1,45 @@
//! Simple PWM example
//!
//! Outputs a PWM waveform on pin PG2.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::{delay::DelayNs, pwm::SetDutyCycle};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use simple_examples::peb1;
use va416xx_hal::{
gpio::PinsA,
clock::ClockConfigurator,
pac,
pins::PinsG,
prelude::*,
pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin},
pwm::{get_duty_from_percent, PwmA, PwmB, PwmPin},
timer::CountdownTimer,
};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx PWM example application--");
let mut dp = pac::Peripherals::take().unwrap();
defmt::println!("-- VA108xx PWM example application--");
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut pwm = pwm::PwmPin::new(
(pinsa.pa3.into_funsel_1(), dp.tim3),
&mut dp.sysconfig,
&clocks,
10.Hz(),
);
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
let pinsg = PinsG::new(dp.portg);
let mut pwm = PwmPin::new(pinsg.pg2, dp.tim9, &clocks, 10.Hz()).unwrap();
let mut delay_timer = CountdownTimer::new(dp.tim0, &clocks);
let mut current_duty_cycle = 0.0;
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
.unwrap();
pwm.enable();
// Delete type information, increased code readibility for the rest of the code
let mut reduced_pin = ReducedPwmPin::from(pwm);
loop {
let mut counter = 0;
// Increase duty cycle continuously
@ -51,11 +48,10 @@ fn main() -> ! {
current_duty_cycle += 0.02;
counter += 1;
if counter % 10 == 0 {
rprintln!("current duty cycle: {}", current_duty_cycle);
defmt::info!("current duty cycle: {}", current_duty_cycle);
}
reduced_pin
.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
.unwrap();
}
@ -64,7 +60,7 @@ fn main() -> ! {
current_duty_cycle = 0.0;
let mut upper_limit = 1.0;
let mut lower_limit = 0.0;
let mut pwmb: ReducedPwmPin<PwmB> = ReducedPwmPin::from(reduced_pin);
let mut pwmb: PwmPin<PwmB> = PwmPin::from(pwm);
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
while lower_limit < 0.5 {
@ -73,9 +69,9 @@ fn main() -> ! {
upper_limit -= 0.01;
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
rprintln!("Lower limit: {}", pwmb.pwmb_lower_limit());
rprintln!("Upper limit: {}", pwmb.pwmb_upper_limit());
defmt::info!("Lower limit: {}", pwmb.pwmb_lower_limit());
defmt::info!("Upper limit: {}", pwmb.pwmb_upper_limit());
}
reduced_pin = ReducedPwmPin::<PwmA>::from(pwmb);
pwm = PwmPin::<PwmA>::from(pwmb);
}
}

View File

@ -1,36 +0,0 @@
// Code to test RTT logger functionality.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::pac;
// Mask for the LED
const LED_PG5: u32 = 1 << 5;
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
// Enable all peripheral clocks
dp.sysconfig
.peripheral_clk_enable()
.modify(|_, w| unsafe { w.bits(0xffffffff) });
dp.portg.dir().modify(|_, w| unsafe { w.bits(LED_PG5) });
dp.portg
.datamask()
.modify(|_, w| unsafe { w.bits(LED_PG5) });
rtt_init_print!();
rprintln!("VA416xx RTT Demo");
let mut counter = 0;
loop {
rprintln!("{}: Hello, world!", counter);
// Still toggle LED. If there are issues with the RTT log, the LED
// blinking ensures that the application is actually running.
dp.portg.togout().write(|w| unsafe { w.bits(LED_PG5) });
counter += 1;
cortex_m::asm::delay(10_000_000);
}
}

View File

@ -3,27 +3,31 @@
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
#![no_main]
#![no_std]
use embedded_hal::delay::DelayNs;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use cortex_m_rt::entry;
use 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::clock::ClockConfigurator;
use va416xx_hal::spi::{Spi, SpiClkConfig};
use va416xx_hal::timer::CountdownTimer;
use va416xx_hal::{
gpio::{PinsB, PinsC},
pac,
prelude::*,
pins::{PinsB, PinsC},
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;
@ -34,63 +38,50 @@ const FILL_WORD: u8 = 0x0f;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx SPI example application--");
let cp = cortex_m::Peripherals::take().unwrap();
let mut dp = pac::Peripherals::take().unwrap();
defmt::println!("-- VA108xx SPI example application--");
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
let mut delay = CountdownTimer::new(dp.tim1, &clocks);
let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
// Configure SPI1 pins.
let (sck, miso, mosi) = (
pins_b.pb15.into_funsel_1(),
pins_c.pc0.into_funsel_1(),
pins_c.pc1.into_funsel_1(),
);
let pins_b = PinsB::new(dp.portb);
let pins_c = PinsC::new(dp.portc);
let mut spi_cfg = SpiConfig::default();
let mut spi_cfg = SpiConfig::default()
.clk_cfg(
SpiClkConfig::from_clks(&clocks, Hertz::from_raw(SPI_SPEED_KHZ))
.expect("invalid target clock"),
)
.mode(SPI_MODE)
.blockmode(BLOCKMODE);
if EXAMPLE_SEL == ExampleSelect::Loopback {
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(
dp.spi0,
(sck, miso, mosi),
&clocks,
spi_cfg,
&mut dp.sysconfig,
Some(&transfer_cfg.downgrade()),
);
let mut spi0 = Spi::new(dp.spi0, (pins_b.pb15, pins_c.pc0, pins_c.pc1), spi_cfg).unwrap();
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);
delay_sysclk.delay_ms(500);
assert_eq!(rx_buf, [1, 2, 3, 0]);
delay.delay_ms(500);
}
}

View File

@ -1,17 +1,21 @@
//! MS and Second counter implemented using the TIM0 and TIM1 peripheral
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use core::sync::atomic::{AtomicU32, Ordering};
use cortex_m::asm;
use cortex_m_rt::entry;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::{
clock::ClockConfigurator,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
prelude::*,
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
timer::CountdownTimer,
};
#[allow(dead_code)]
@ -20,49 +24,47 @@ enum LibType {
Hal,
}
static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
static MS_COUNTER: AtomicU32 = AtomicU32::new(0);
static SEC_COUNTER: AtomicU32 = AtomicU32::new(0);
#[entry]
fn main() -> ! {
rtt_init_print!();
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let mut last_ms = 0;
rprintln!("-- Vorago system ticks using timers --");
defmt::println!("-- Vorago system ticks using timers --");
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
enable_and_init_irq_router();
let mut ms_timer = CountdownTimer::new(dp.tim0, &clocks);
ms_timer.enable_interrupt(true);
ms_timer.start(1.Hz());
let mut second_timer = CountdownTimer::new(dp.tim1, &clocks);
second_timer.enable_interrupt(true);
second_timer.start(1.Hz());
second_timer.listen();
loop {
let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get());
if current_ms - last_ms >= 1000 {
last_ms = current_ms;
rprintln!("MS counter: {}", current_ms);
let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get());
rprintln!("Second counter: {}", second);
let current_ms = MS_COUNTER.load(Ordering::Relaxed);
if current_ms >= last_ms + 1000 {
// To prevent drift.
last_ms += 1000;
defmt::info!("MS counter: {}", current_ms);
let second = SEC_COUNTER.load(Ordering::Relaxed);
defmt::info!("Second counter: {}", second);
}
cortex_m::asm::delay(10000);
asm::delay(1000);
}
}
#[interrupt]
#[allow(non_snake_case)]
fn TIM0() {
default_ms_irq_handler()
MS_COUNTER.fetch_add(1, Ordering::Relaxed);
}
#[interrupt]
#[allow(non_snake_case)]
fn TIM1() {
cortex_m::interrupt::free(|cs| {
let mut sec = SEC_COUNTER.borrow(cs).get();
sec += 1;
SEC_COUNTER.borrow(cs).set(sec);
});
SEC_COUNTER.fetch_add(1, Ordering::Relaxed);
}

View File

@ -2,55 +2,53 @@
// echo mode
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use cortex_m_rt::entry;
use embedded_hal_nb::serial::Read;
use embedded_io::Write;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1;
use va416xx_hal::clock::ClkgenExt;
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::pins::PinsG;
use va416xx_hal::time::Hertz;
use va416xx_hal::{gpio::PinsG, pac, uart};
use va416xx_hal::{pac, uart};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA416xx UART example application--");
defmt::println!("-- VA416xx UART example application--");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let gpiob = PinsG::new(&mut dp.sysconfig, dp.portg);
let tx = gpiob.pg0.into_funsel_1();
let rx = gpiob.pg1.into_funsel_1();
let gpiog = PinsG::new(dp.portg);
let uart0 = uart::Uart::new(
dp.uart0,
(tx, rx),
Hertz::from_raw(115200),
&mut dp.sysconfig,
gpiog.pg0,
gpiog.pg1,
&clocks,
);
Hertz::from_raw(115200).into(),
)
.unwrap();
let (mut tx, mut rx) = uart0.split();
writeln!(tx, "Hello World\n\r").unwrap();
loop {
// Echo what is received on the serial link.
match nb::block!(rx.read()) {
Ok(recvd) => {
if let Err(e) = embedded_hal_nb::serial::Write::write(&mut tx, recvd) {
rprintln!("UART TX error: {:?}", e);
}
// Infallible operation.
embedded_hal_nb::serial::Write::write(&mut tx, recvd).unwrap();
}
Err(e) => {
rprintln!("UART RX error {:?}", e);
defmt::info!("UART RX error {:?}", e);
}
}
}

View File

@ -1,18 +1,20 @@
// Code to test the watchdog timer.
#![no_main]
#![no_std]
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va416xx_hal::clock::ClockConfigurator;
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use core::sync::atomic::{AtomicU32, Ordering};
use cortex_m_rt::entry;
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));
static WDT_INTRPT_COUNT: AtomicU32 = AtomicU32::new(0);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
@ -28,36 +30,39 @@ const WDT_ROLLOVER_MS: u32 = 100;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA416xx WDT example application--");
defmt::println!("-- VA416xx WDT example application--");
let cp = cortex_m::Peripherals::take().unwrap();
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
let clocks = ClockConfigurator::new(dp.clkgen)
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
.freeze(&mut dp.sysconfig)
.freeze()
.unwrap();
let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
enable_and_init_irq_router();
let mut delay = 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(dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
wdt_ctrl.enable_reset();
let log_divisor = 25;
let mut counter: u32 = 0;
loop {
counter = counter.wrapping_add(1);
if counter % log_divisor == 0 {
defmt::info!("wdt example main loop alive");
}
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 = WDT_INTRPT_COUNT.load(Ordering::Relaxed);
if interrupt_counter > last_interrupt_counter {
rprintln!("interrupt counter has increased to {}", interrupt_counter);
defmt::info!("interrupt counter has increased to {}", interrupt_counter);
last_interrupt_counter = interrupt_counter;
}
match TEST_MODE {
TestMode::FedByMain => delay_sysclk.delay_ms(WDT_ROLLOVER_MS / 5),
TestMode::FedByIrq => delay_sysclk.delay_ms(WDT_ROLLOVER_MS),
TestMode::FedByMain => delay.delay_ms(WDT_ROLLOVER_MS / 5),
TestMode::FedByIrq => delay.delay_ms(WDT_ROLLOVER_MS),
_ => (),
}
}
@ -66,11 +71,7 @@ fn main() -> ! {
#[interrupt]
#[allow(non_snake_case)]
fn WATCHDOG() {
cortex_m::interrupt::free(|cs| {
WDT_INTRPT_COUNT
.borrow(cs)
.set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);
});
WDT_INTRPT_COUNT.fetch_add(1, Ordering::Relaxed);
let wdt = unsafe { pac::WatchDog::steal() };
// Clear interrupt.
if TEST_MODE != TestMode::AllowReset {

View File

@ -3,7 +3,7 @@
#![no_std]
use cortex_m_rt::entry;
use panic_rtt_target as _;
use panic_halt as _;
#[entry]
fn main() -> ! {

1
flashloader/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/venv

22
flashloader/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "flashloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
embedded-io = "0.6"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
static_cell = "2"
satrs = { version = "0.3.0-alpha.0", default-features = false }
ringbuf = { version = "0.4", default-features = false }
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
spacepackets = { version = "0.13", default-features = false, features = ["defmt"] }
cobs = { version = "0.3", default-features = false }
va416xx-hal = { version = "0.5", features = ["va41630", "defmt"], path = "../va416xx-hal" }
rtic = { version = "2", features = ["thumbv7-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }

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.

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

@ -0,0 +1,457 @@
#!/usr/bin/env python3
from typing import List, Tuple
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
from com_interface import ComInterface
import toml
import struct
import logging
import argparse
import time
import enum
from com_interface.serial_base import SerialCfg
from com_interface.serial_cobs import SerialCobsComIF
from crcmod.predefined import PredefinedCrc
from spacepackets.ecss.tc import PusTc
from spacepackets.ecss.pus_verificator import PusVerificator, StatusField
from spacepackets.ecss.pus_1_verification import Service1Tm, UnpackParams
from spacepackets.seqcount import SeqCountProvider
from pathlib import Path
import dataclasses
from elftools.elf.elffile import ELFFile
BAUD_RATE = 115200
BOOTLOADER_START_ADDR = 0x0
BOOTLOADER_END_ADDR = 0x4000
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 4
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4
APP_A_START_ADDR = 0x4000
APP_A_END_ADDR = 0x22000
# The actual size of the image which is relevant for CRC calculation.
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8
APP_A_CRC_ADDR = APP_A_END_ADDR - 4
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_B_START_ADDR = 0x22000
APP_B_END_ADDR = 0x40000
# The actual size of the image which is relevant for CRC calculation.
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8
APP_B_CRC_ADDR = APP_B_END_ADDR - 4
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
CHUNK_SIZE = 896
MEMORY_SERVICE = 6
ACTION_SERVICE = 8
RAW_MEMORY_WRITE_SUBSERVICE = 2
BOOT_NVM_MEMORY_ID = 1
PING_PAYLOAD_SIZE = 0
class ActionId(enum.IntEnum):
CORRUPT_APP_A = 128
CORRUPT_APP_B = 129
SET_BOOT_SLOT = 130
_LOGGER = logging.getLogger(__name__)
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
@dataclasses.dataclass
class LoadableSegment:
name: str
offset: int
size: int
data: bytes
class Target(enum.Enum):
BOOTLOADER = 0
APP_A = 1
APP_B = 2
class AppSel(enum.IntEnum):
APP_A = 0
APP_B = 1
class ImageLoader:
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
self.com_if = com_if
self.verificator = verificator
def handle_boot_sel_cmd(self, target: AppSel):
_LOGGER.info("Sending ping command")
action_tc = PusTc(
apid=0x00,
service=PusService.S8_FUNC_CMD,
subservice=ActionId.SET_BOOT_SLOT,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes([target]),
)
self.verificator.add_tc(action_tc)
self.com_if.send(bytes(action_tc.pack()))
self.await_for_command_completion("boot image selection command")
def handle_ping_cmd(self):
_LOGGER.info("Sending ping command")
ping_tc = PusTc(
apid=0x00,
service=PusService.S17_TEST,
subservice=1,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes(PING_PAYLOAD_SIZE),
)
self.verificator.add_tc(ping_tc)
self.com_if.send(bytes(ping_tc.pack()))
self.await_for_command_completion("ping command")
def handle_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER:
_LOGGER.error("can not corrupt bootloader")
if target == Target.APP_A:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_A,
),
)
if target == Target.APP_B:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_B,
),
)
def await_for_command_completion(self, context: str):
done = False
now = time.time()
while time.time() - now < 2.0:
if self.com_if.data_available() == 0:
time.sleep(0.2)
continue
for reply in self.com_if.receive():
result = self.verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info(f"received {context} reply")
done = True
if done:
break
if not done:
_LOGGER.warning(f"no {context} reply received")
def handle_flash_cmd(self, target: Target, file_path: Path) -> int:
loadable_segments = []
_LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
loadable_segments, total_size = create_loadable_segments(target, file_path)
segments_info_str(target, loadable_segments, total_size, file_path)
result = self._perform_flashing_algorithm(loadable_segments)
if result != 0:
return result
self._crc_and_app_size_postprocessing(target, total_size, loadable_segments)
return 0
def _perform_flashing_algorithm(
self,
loadable_segments: List[LoadableSegment],
) -> int:
# Perform the flashing algorithm.
for segment in loadable_segments:
segment_end = segment.offset + segment.size
current_addr = segment.offset
pos_in_segment = 0
while pos_in_segment < segment.size:
next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
data = segment.data[pos_in_segment : pos_in_segment + next_chunk_size]
next_packet = pack_memory_write_command(current_addr, data)
_LOGGER.info(
f"Sending memory write command for address {current_addr:#08x} and data with "
f"length {len(data)}"
)
self.verificator.add_tc(next_packet)
self.com_if.send(bytes(next_packet.pack()))
current_addr += next_chunk_size
pos_in_segment += next_chunk_size
start_time = time.time()
while True:
if time.time() - start_time > 1.0:
_LOGGER.error("Timeout while waiting for reply")
return -1
data_available = self.com_if.data_available(0.1)
done = False
if not data_available:
continue
replies = self.com_if.receive()
for reply in replies:
tm = PusTm.unpack(reply, 0)
if tm.service != 1:
continue
service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
check_result = self.verificator.add_tm(service_1_tm)
# We could send after we have received the step reply, but that can
# somehow lead to overrun errors. I think it's okay to do it like
# this as long as the flash loader only uses polling..
if (
check_result is not None
and check_result.status.completed == StatusField.SUCCESS
):
done = True
# This is an optimized variant, but I think the small delay is not an issue.
"""
if (
check_result is not None
and check_result.status.step == StatusField.SUCCESS
and len(check_result.status.step_list) == 1
):
done = True
"""
self.verificator.remove_completed_entries()
if done:
break
return 0
def _crc_and_app_size_postprocessing(
self,
target: Target,
total_size: int,
loadable_segments: List[LoadableSegment],
):
if target == Target.BOOTLOADER:
_LOGGER.info("Blanking the bootloader checksum")
# Blank the checksum. For the bootloader, the bootloader will calculate the
# checksum itself on the initial run.
checksum_write_packet = pack_memory_write_command(
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
)
self.send_tc(checksum_write_packet)
else:
crc_addr = None
size_addr = None
if target == Target.APP_A:
crc_addr = APP_A_CRC_ADDR
size_addr = APP_A_SIZE_ADDR
elif target == Target.APP_B:
crc_addr = APP_B_CRC_ADDR
size_addr = APP_B_SIZE_ADDR
assert crc_addr is not None
assert size_addr is not None
_LOGGER.info(f"Writing app size {total_size} at address {size_addr:#08x}")
size_write_packet = pack_memory_write_command(
size_addr, struct.pack("!I", total_size)
)
self.com_if.send(bytes(size_write_packet.pack()))
time.sleep(0.2)
crc_calc = PredefinedCrc("crc-32")
for segment in loadable_segments:
crc_calc.update(segment.data)
checksum = crc_calc.digest()
_LOGGER.info(
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
)
self.send_tc(pack_memory_write_command(crc_addr, checksum))
def send_tc(self, tc: PusTc):
self.com_if.send(bytes(tc.pack()))
def main() -> int:
print("Python VA416XX Image Loader Application")
logging.basicConfig(
format="[%(asctime)s] [%(levelname)s] %(message)s", level=logging.DEBUG
)
parser = argparse.ArgumentParser(
prog="image-loader", description="Python VA416XX Image Loader Application"
)
parser.add_argument("-p", "--ping", action="store_true", help="Send ping command")
parser.add_argument("-c", "--corrupt", action="store_true", help="Corrupt a target")
parser.add_argument(
"-t",
"--target",
choices=["bl", "a", "b"],
help="Target (Bootloader or slot A or B)",
)
parser.add_argument(
"path", nargs="?", default=None, help="Path to the App to flash"
)
args = parser.parse_args()
serial_port = None
if Path("loader.toml").exists():
with open("loader.toml", "r") as toml_file:
parsed_toml = toml.loads(toml_file.read())
if "serial_port" in parsed_toml:
serial_port = parsed_toml["serial_port"]
if serial_port is None:
serial_port = input("Please specify the serial port manually: ")
serial_cfg = SerialCfg(
com_if_id="ser_cobs",
serial_port=serial_port,
baud_rate=BAUD_RATE,
polling_frequency=0.1,
)
verificator = PusVerificator()
com_if = SerialCobsComIF(serial_cfg)
com_if.open()
target = None
if args.target == "bl":
target = Target.BOOTLOADER
elif args.target == "a":
target = Target.APP_A
elif args.target == "b":
target = Target.APP_B
image_loader = ImageLoader(com_if, verificator)
file_path = None
result = -1
if args.ping:
image_loader.handle_ping_cmd()
com_if.close()
return 0
if target:
if not args.corrupt:
if not args.path:
_LOGGER.error("App Path needs to be specified for the flash process")
file_path = Path(args.path)
if not file_path.exists():
_LOGGER.error("File does not exist")
if args.corrupt:
if not target:
_LOGGER.error("target for corruption command required")
com_if.close()
return -1
image_loader.handle_corruption_cmd(target)
else:
assert file_path is not None
assert target is not None
result = image_loader.handle_flash_cmd(target, file_path)
com_if.close()
return result
def create_loadable_segments(
target: Target, file_path: Path
) -> Tuple[List[LoadableSegment], int]:
loadable_segments = []
total_size = 0
with open(file_path, "rb") as app_file:
elf_file = ELFFile(app_file)
for idx, segment in enumerate(elf_file.iter_segments("PT_LOAD")):
if segment.header.p_filesz == 0:
continue
# Basic validity checks of the base addresses.
if idx == 0:
if (
target == Target.BOOTLOADER
and segment.header.p_paddr != BOOTLOADER_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"bootloader, expected {BOOTLOADER_START_ADDR}"
)
if (
target == Target.APP_A
and segment.header.p_paddr != APP_A_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App A, expected {APP_A_START_ADDR}"
)
if (
target == Target.APP_B
and segment.header.p_paddr != APP_B_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App B, expected {APP_B_START_ADDR}"
)
name = None
for section in elf_file.iter_sections():
if (
section.header.sh_offset == segment.header.p_offset
and section.header.sh_size > 0
):
name = section.name
if name is None:
_LOGGER.warning("no fitting section found for segment")
continue
# print(f"Segment Addr: {segment.header.p_paddr}")
# print(f"Segment Offset: {segment.header.p_offset}")
# print(f"Segment Filesize: {segment.header.p_filesz}")
loadable_segments.append(
LoadableSegment(
name=name,
offset=segment.header.p_paddr,
size=segment.header.p_filesz,
data=segment.data(),
)
)
total_size += segment.header.p_filesz
return loadable_segments, total_size
def segments_info_str(
target: Target,
loadable_segments: List[LoadableSegment],
total_size: int,
file_path: Path,
):
# Set context string and perform basic sanity checks.
if target == Target.BOOTLOADER:
if total_size > BOOTLOADER_MAX_SIZE:
_LOGGER.error(
f"provided bootloader app larger than allowed {total_size} bytes"
)
return -1
context_str = "Bootloader"
elif target == Target.APP_A:
if total_size > APP_A_MAX_SIZE:
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes")
return -1
context_str = "App Slot A"
elif target == Target.APP_B:
if total_size > APP_B_MAX_SIZE:
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes")
return -1
context_str = "App Slot B"
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})")
for idx, segment in enumerate(loadable_segments):
_LOGGER.info(
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and "
f"size {segment.size}"
)
def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
app_data = bytearray()
app_data.append(BOOT_NVM_MEMORY_ID)
# N parameter is always 1 here.
app_data.append(1)
app_data.extend(struct.pack("!I", addr))
app_data.extend(struct.pack("!I", len(data)))
app_data.extend(data)
return PusTc(
apid=0,
service=MEMORY_SERVICE,
subservice=RAW_MEMORY_WRITE_SUBSERVICE,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes(app_data),
)
if __name__ == "__main__":
main()

1
flashloader/loader.toml Normal file
View File

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

View File

@ -0,0 +1,5 @@
spacepackets == 0.28
com-interface == 0.1.0
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();
}
}

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

@ -0,0 +1,537 @@
//! Vorago flashloader which can be used to flash image A and image B via a simple
//! low-level CCSDS memory interface via a UART wire.
//!
//! This flash loader can be used after the bootloader was flashed to flash the images.
//! You can also use this as an starting application for a software update mechanism.
//!
//! Bootloader memory map
//!
//! * <0x0> Bootloader start <code up to 0x3FFE bytes>
//! * <0x3FFE> Bootloader CRC <halfword>
//! * <0x4000> App image A start <code up to 0x1DFFC (~120K) bytes>
//! * <0x21FFC> App image A CRC check length <halfword>
//! * <0x21FFE> App image A CRC check value <halfword>
//! * <0x22000> App image B start <code up to 0x1DFFC (~120K) bytes>
//! * <0x3FFFC> App image B CRC check length <halfword>
//! * <0x3FFFE> App image B CRC check value <halfword>
//! * <0x40000> <end>
#![no_main]
#![no_std]
use once_cell::sync::OnceCell;
use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
const EXTCLK_FREQ: u32 = 40_000_000;
const MAX_TC_SIZE: usize = 1024;
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
const MAX_TM_SIZE: usize = 128;
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200;
const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
const TX_DEBUGGING: bool = false;
pub enum ActionId {
CorruptImageA = 128,
CorruptImageB = 129,
}
pub trait WdtInterface {
fn feed(&self);
}
pub struct OptWdt(Option<Wdt>);
impl WdtInterface for OptWdt {
fn feed(&self) {
if self.0.is_some() {
self.0.as_ref().unwrap().feed();
}
}
}
use ringbuf::{
traits::{Consumer, Observer, Producer, SplitRef},
CachingCons, StaticProd, StaticRb,
};
use static_cell::StaticCell;
// Larger buffer for TC to be able to hold the possibly large memory write packets.
const BUF_RB_SIZE_TC: usize = 2048;
const SIZES_RB_SIZE_TC: usize = 16;
const BUF_RB_SIZE_TM: usize = 512;
const SIZES_RB_SIZE_TM: usize = 16;
// Ring buffers to handling variable sized telemetry
static BUF_RB_TM: StaticCell<StaticRb<u8, BUF_RB_SIZE_TM>> = StaticCell::new();
static SIZES_RB_TM: StaticCell<StaticRb<usize, SIZES_RB_SIZE_TM>> = StaticCell::new();
// Ring buffers to handling variable sized telecommands
static BUF_RB_TC: StaticCell<StaticRb<u8, BUF_RB_SIZE_TC>> = StaticCell::new();
static SIZES_RB_TC: StaticCell<StaticRb<usize, SIZES_RB_SIZE_TC>> = StaticCell::new();
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
pub sizes_prod: StaticProd<'static, usize, SIZES_LEN>,
}
pub struct DataConsumer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf_cons: CachingCons<&'static StaticRb<u8, BUF_SIZE>>,
pub sizes_cons: CachingCons<&'static StaticRb<usize, SIZES_LEN>>,
}
static CLOCKS: OnceCell<Clocks> = OnceCell::new();
pub const APP_A_START_ADDR: u32 = 0x4000;
pub const APP_A_END_ADDR: u32 = 0x22000;
pub const APP_B_START_ADDR: u32 = 0x22000;
pub const APP_B_END_ADDR: u32 = 0x40000;
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app {
use super::*;
use cortex_m::asm;
use embedded_io::Write;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use rtic::Mutex;
use rtic_monotonics::systick::prelude::*;
use satrs::pus::verification::VerificationReportCreator;
use spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va416xx_hal::clock::ClockConfigurator;
use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::uart::IrqContextTimeoutOrMaxSize;
use va416xx_hal::{
edac,
nvm::Nvm,
pac,
pins::PinsG,
uart::{self, Uart},
};
use crate::{setup_edac, EXTCLK_FREQ};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum CobsReaderStates {
#[default]
WaitingForStart,
WatingForEnd,
FrameOverflow,
}
#[local]
struct Local {
uart_rx: uart::RxWithInterrupt,
uart_tx: uart::Tx,
rx_context: IrqContextTimeoutOrMaxSize,
rom_spi: Option<pac::Spi3>,
// We handle all TM in one task.
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
// We consume all TC in one task.
tc_cons: DataConsumer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
// We produce all TC in one task.
tc_prod: DataProducer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
verif_reporter: VerificationReportCreator,
}
#[shared]
struct Shared {
// Having this shared allows multiple tasks to generate telemetry.
tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
}
rtic_monotonics::systick_monotonic!(Mono, 10_000);
#[init]
fn init(mut cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago flashloader --");
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = ClockConfigurator::new(cx.device.clkgen)
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze()
.unwrap();
enable_and_init_irq_router();
setup_edac(&mut cx.device.sysconfig);
let gpiog = PinsG::new(cx.device.portg);
let uart0 = Uart::new(
cx.device.uart0,
gpiog.pg0,
gpiog.pg1,
&clocks,
Hertz::from_raw(UART_BAUDRATE).into(),
)
.unwrap();
let (tx, rx) = uart0.split();
let verif_reporter = VerificationReportCreator::new(0).unwrap();
let (buf_prod_tm, buf_cons_tm) = BUF_RB_TM
.init(StaticRb::<u8, BUF_RB_SIZE_TM>::default())
.split_ref();
let (sizes_prod_tm, sizes_cons_tm) = SIZES_RB_TM
.init(StaticRb::<usize, SIZES_RB_SIZE_TM>::default())
.split_ref();
let (buf_prod_tc, buf_cons_tc) = BUF_RB_TC
.init(StaticRb::<u8, BUF_RB_SIZE_TC>::default())
.split_ref();
let (sizes_prod_tc, sizes_cons_tc) = SIZES_RB_TC
.init(StaticRb::<usize, SIZES_RB_SIZE_TC>::default())
.split_ref();
Mono::start(cx.core.SYST, clocks.sysclk().raw());
CLOCKS.set(clocks).unwrap();
let mut rx = rx.into_rx_with_irq();
let mut rx_context = IrqContextTimeoutOrMaxSize::new(MAX_TC_FRAME_SIZE);
rx.read_fixed_len_or_timeout_based_using_irq(&mut rx_context)
.expect("initiating UART RX failed");
pus_tc_handler::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap();
(
Shared {
tm_prod: DataProducer {
buf_prod: buf_prod_tm,
sizes_prod: sizes_prod_tm,
},
},
Local {
uart_rx: rx,
uart_tx: tx,
rx_context,
rom_spi: Some(cx.device.spi3),
tm_cons: DataConsumer {
buf_cons: buf_cons_tm,
sizes_cons: sizes_cons_tm,
},
tc_cons: DataConsumer {
buf_cons: buf_cons_tc,
sizes_cons: sizes_cons_tc,
},
tc_prod: DataProducer {
buf_prod: buf_prod_tc,
sizes_prod: sizes_prod_tc,
},
verif_reporter,
},
)
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
// This is the interrupt handler to read all bytes received on the UART0.
#[task(
binds = UART0_RX,
local = [
cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
rx_context,
uart_rx,
tc_prod
],
)]
fn uart_rx_irq(cx: uart_rx_irq::Context) {
match cx
.local
.uart_rx
.on_interrupt_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
{
Ok(result) => {
if RX_DEBUGGING {
defmt::info!("RX Info: {:?}", cx.local.rx_context);
defmt::info!("RX Result: {:?}", result);
}
if result.complete() {
// Check frame validity (must have COBS format) and decode the frame.
// Currently, we expect a full frame or a frame received through a timeout
// to be one COBS frame. We could parse for multiple COBS packets in one
// frame, but the additional complexity is not necessary here..
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
let decoded_size =
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
if decoded_size.is_err() {
defmt::warn!("COBS decoding failed");
} else {
let decoded_size = decoded_size.unwrap();
if cx.local.tc_prod.sizes_prod.vacant_len() >= 1
&& cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size
{
// Should never fail, we checked there is enough space.
cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap();
cx.local
.tc_prod
.buf_prod
.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
} else {
defmt::warn!("COBS TC queue full");
}
}
} else {
defmt::warn!(
"COBS frame with invalid format, start and end bytes are not 0"
);
}
// Initiate next transfer.
cx.local
.uart_rx
.read_fixed_len_or_timeout_based_using_irq(cx.local.rx_context)
.expect("read operation failed");
}
if result.has_errors() {
defmt::warn!("UART error: {:?}", result.errors.unwrap());
}
}
Err(e) => {
defmt::warn!("UART error: {:?}", e);
}
}
}
#[task(
priority = 2,
local=[
tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
src_data_buf: [u8; 16] = [0; 16],
verif_buf: [u8; 32] = [0; 32],
tc_cons,
rom_spi,
verif_reporter
],
shared=[tm_prod]
)]
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
loop {
// Try to read a TC from the ring buffer.
let packet_len = cx.local.tc_cons.sizes_cons.try_pop();
if packet_len.is_none() {
// Small delay, TCs might arrive very quickly.
Mono::delay(20.millis()).await;
continue;
}
let packet_len = packet_len.unwrap();
defmt::info!("received packet with length {}", packet_len);
assert_eq!(
cx.local
.tc_cons
.buf_cons
.pop_slice(&mut cx.local.tc_buf[0..packet_len]),
packet_len
);
// Read a telecommand, now handle it.
handle_valid_pus_tc(&mut cx);
}
}
fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
let pus_tc = PusTcReader::new(cx.local.tc_buf);
if pus_tc.is_err() {
defmt::warn!("PUS TC error: {}", pus_tc.unwrap_err());
return;
}
let (pus_tc, _) = pus_tc.unwrap();
let mut write_and_send = |tm: &PusTmCreator| {
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
cx.shared.tm_prod.lock(|prod| {
prod.sizes_prod.try_push(tm.len_written()).unwrap();
prod.buf_prod
.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let token = cx.local.verif_reporter.add_tc(&pus_tc);
let (tm, accepted_token) = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
let (tm, started_token) = cx
.local
.verif_reporter
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
if pus_tc.service() == PusServiceId::Action as u8 {
let mut corrupt_image = |base_addr: u32| {
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let nvm = Nvm::new(
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
let mut buf = [0u8; 4];
nvm.read_data(base_addr + 32, &mut buf);
buf[0] += 1;
nvm.write_data(base_addr + 32, &buf);
*cx.local.rom_spi = Some(nvm.release());
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
};
if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
defmt::info!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR);
}
if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
defmt::info!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR);
}
}
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
defmt::info!("received ping TC");
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
let tm = cx
.local
.verif_reporter
.step_success(
cx.local.src_data_buf,
&started_token,
0,
0,
&[],
EcssEnumU8::new(0),
)
.expect("step success failed");
write_and_send(&tm);
// Raw memory write TC
if pus_tc.subservice() == 2 {
let app_data = pus_tc.app_data();
if app_data.len() < 10 {
defmt::warn!(
"app data for raw memory write is too short: {}",
app_data.len()
);
}
let memory_id = app_data[0];
if memory_id != BOOT_NVM_MEMORY_ID {
defmt::warn!("memory ID {} not supported", memory_id);
// TODO: Error reporting
return;
}
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
if 10 + data_len as usize > app_data.len() {
defmt::warn!(
"invalid data length {} for raw mem write detected",
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
defmt::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let nvm = Nvm::new(
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release());
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
defmt::info!("NVM operation done");
}
}
}
#[task(
priority = 1,
local=[
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
uart_tx,
tm_cons
],
shared=[]
)]
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
loop {
while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
cx.local
.tm_cons
.buf_cons
.pop_slice(&mut cx.local.read_buf[0..next_size]);
cx.local.encoded_buf[0] = 0;
let send_size = cobs::encode(
&cx.local.read_buf[0..next_size],
&mut cx.local.encoded_buf[1..],
);
cx.local.encoded_buf[send_size + 1] = 0;
if TX_DEBUGGING {
defmt::debug!("UART TX: Sending data with size {}", send_size + 2);
}
cx.local
.uart_tx
.write_all(&cx.local.encoded_buf[0..send_size + 2])
.unwrap();
Mono::delay(2.millis()).await;
}
Mono::delay(50.millis()).await;
}
}
#[task(binds = EDAC_SBE, priority = 1)]
fn edac_sbe_isr(_cx: edac_sbe_isr::Context) {
// TODO: Send some command via UART for notification purposes. Also identify the problematic
// memory.
edac::clear_sbe_irq();
}
#[task(binds = EDAC_MBE, priority = 1)]
fn edac_mbe_isr(_cx: edac_mbe_isr::Context) {
// TODO: Send some command via UART for notification purposes.
edac::clear_mbe_irq();
// TODO: Reset like the vorago example?
}
#[task(binds = WATCHDOG, priority = 1)]
fn watchdog_isr(_cx: watchdog_isr::Context) {
let wdt = unsafe { pac::WatchDog::steal() };
// Clear interrupt.
wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
}
}
fn setup_edac(syscfg: &mut pac::Sysconfig) {
// The scrub values are based on the Vorago provided bootloader.
edac::enable_rom_scrub(syscfg, 125);
edac::enable_ram0_scrub(syscfg, 1000);
edac::enable_ram1_scrub(syscfg, 1000);
edac::enable_sbe_irq();
edac::enable_mbe_irq();
}

5
jlink-gdb-ram.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# Start the JLinkGDBServer while also specifying the JLinkScript file. The JLinkScript is necessary
# to disable ROM protection to allow flashing
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript

View File

@ -1,5 +1,5 @@
#!/bin/bash
# Start the JLinkGDBServer while also specifying the JLinkScript file. The JLinkScript is necessary
# to disable ROM protection to allow flashing
JLinkGDBServer -select USB -device Cortex-M4 -endian little -if SWD -speed 2000 \
-LocalhostOnly -vd -jlinkscriptfile ./jlink/JLinkSettings.JLinkScript
JLinkGDBServer -select USB -device VA416xx -endian little -if SWD -speed 2000 \
-LocalhostOnly -vd

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

@ -18,6 +18,5 @@ _stack_start = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
} > SRAM_1
};

1
scripts/can-clk-calc/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

View File

@ -0,0 +1,9 @@
[workspace]
[package]
name = "can-clk-calc"
version = "0.1.0"
edition = "2024"
[dependencies]
va416xx-hal = { path = "../../va416xx-hal", features = ["alloc", "revb"], default-features = false }

View File

@ -0,0 +1,14 @@
use va416xx_hal::can::calculate_all_viable_clock_configs;
use va416xx_hal::time::Hertz;
fn main() {
let cfgs = calculate_all_viable_clock_configs(
Hertz::from_raw(20_000_000),
Hertz::from_raw(250_000),
0.75,
)
.unwrap();
for cfg in &cfgs {
println!("Config: {:#?}", cfg);
}
}

18
scripts/defmt-telnet.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# Check if binary path was provided
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <path-to-binary>"
exit 1
fi
BINARY="$1"
# Check if file exists
if [ ! -f "$BINARY" ]; then
echo "Error: File '$BINARY' not found."
exit 1
fi
# Run the command
defmt-print -e "$BINARY" tcp

23
scripts/memory.x Normal file
View File

@ -0,0 +1,23 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
/* SRAM_0 can be used for all busses: Instruction, Data and System */
/* SRAM_1 only supports the system bus */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/* Define sections for placing symbols into the extra memory regions above. */
/* This makes them accessible from code. */
SECTIONS {
.sram1 (NOLOAD) : ALIGN(8) {
*(.sram1 .sram1.*);
. = ALIGN(4);
} > SRAM_1
};

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
};

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,31 @@
[package]
name = "va416xx-embassy"
version = "0.1.1"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Embassy-rs support for the Vorago VA416xx family of microcontrollers"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs"
license = "Apache-2.0"
keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
[dependencies]
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] }
va416xx-hal = { path = "../va416xx-hal" }
[features]
default = ["irq-tim14-tim15"]
# This determines the reserved interrupt functions for the embassy time drivers. Only one
# is allowed to be selected!
irq-tim14-tim15 = ["_irqs-in-lib"]
irq-tim13-tim14 = ["_irqs-in-lib"]
# These TIMs are clocked slower!
irq-tim22-tim23 = ["_irqs-in-lib"]
# Private feature.
_irqs-in-lib = []
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

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

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

@ -0,0 +1,117 @@
//! # Embassy-rs support for the Vorago VA416xx MCU family
//!
//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
//! VA416xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
//! peripherals provided by the VA416xx family for this purpose.
//!
//! ## Usage
//!
//! This library only exposes the [embassy::init] method which sets up the time driver. This
//! function must be called once at the start of the application.
//!
//! This implementation requires two TIM peripherals provided by the VA108xx device.
//! The user can freely specify the two used TIM peripheral by passing the concrete TIM instances
//! into the [init] method. If the interrupt handlers are provided by the library, the ID of the
//! used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
//! can only be checked at run-time, and a run-time assertion will panic on the embassy
//! initialization in case of a missmatch.
//!
//! The application also requires two interrupt handlers to handle the timekeeper and alarm
//! interrupts. By default, this library will define the interrupt handler inside the library
//! itself by using the `irq-tim14-tim15` feature flag. This library exposes three combinations:
//!
//! - `irq-tim14-tim15`: Uses [pac::Interrupt::TIM14] for alarm and [pac::Interrupt::TIM15]
//! for timekeeper
//! - `irq-tim13-tim14`: Uses [pac::Interrupt::TIM13] for alarm and [pac::Interrupt::TIM14]
//! for timekeeper
//! - `irq-tim22-tim23`: Uses [pac::Interrupt::TIM22] for alarm and [pac::Interrupt::TIM23]
//! for timekeeper
//!
//! You can disable the default features and then specify one of the features above to use the
//! documented combination of IRQs. It is also possible to specify custom IRQs by importing and
//! using the [embassy_time_driver_irqs] macro to declare the IRQ handlers in the
//! application code. If this is done, [embassy::init_with_custom_irqs] must be used
//! method to pass the IRQ numbers to the library.
//!
//! ## Examples
//!
//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy)
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use va416xx_hal::{
clock::Clocks,
irq_router::enable_and_init_irq_router,
pac::{self, interrupt},
timer::{TimMarker, TIM_IRQ_OFFSET},
};
use vorago_shared_periphs::embassy::time_driver;
/// Macro to define the IRQ handlers for the time driver.
///
/// By default, the code generated by this macro will be defined inside the library depending on
/// the feature flags specified. However, the macro is exported to allow users to specify the
/// interrupt handlers themselves.
///
/// Please note that you have to explicitely import the [macro@va108xx_hal::pac::interrupt]
/// macro in the application code in case this macro is used there.
#[macro_export]
macro_rules! embassy_time_driver_irqs {
(
timekeeper_irq = $timekeeper_irq:ident,
alarm_irq = $alarm_irq:ident
) => {
const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::$timekeeper_irq;
#[interrupt]
#[allow(non_snake_case)]
fn $timekeeper_irq() {
// Safety: We call it once here.
unsafe { $crate::time_driver().on_interrupt_timekeeping() }
}
const ALARM_IRQ: pac::Interrupt = pac::Interrupt::$alarm_irq;
#[interrupt]
#[allow(non_snake_case)]
fn $alarm_irq() {
// Safety: We call it once here.
unsafe { $crate::time_driver().on_interrupt_alarm() }
}
};
}
// Provide three combinations of IRQs for the time driver by default.
#[cfg(feature = "irq-tim14-tim15")]
embassy_time_driver_irqs!(timekeeper_irq = TIM15, alarm_irq = TIM14);
#[cfg(feature = "irq-tim13-tim14")]
embassy_time_driver_irqs!(timekeeper_irq = TIM14, alarm_irq = TIM13);
#[cfg(feature = "irq-tim22-tim23")]
embassy_time_driver_irqs!(timekeeper_irq = TIM23, alarm_irq = TIM22);
/// Initialization method for embassy
///
/// If the interrupt handlers are provided by the library, the ID of the
/// used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
/// can only be checked at run-time, and a run-time assertion will panic on the embassy
/// initialization in case of a missmatch.
pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
timekeeper: TimekeeperTim,
alarm: AlarmTim,
clocks: &Clocks,
) {
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
TimekeeperTim::ID.value(),
TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Timekeeper TIM and IRQ missmatch"
);
#[cfg(feature = "_irqs-in-lib")]
assert_eq!(
AlarmTim::ID.value(),
ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,
"Alarm TIM and IRQ missmatch"
);
enable_and_init_irq_router();
time_driver().__init(timekeeper, alarm, clocks)
}

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,35 +12,40 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
derive-mmio = { version = "0.4", git = "https://github.com/knurling-rs/derive-mmio.git" }
static_assertions = "1.1"
vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] }
libm = "0.2"
nb = "1"
paste = "1"
embedded-hal-nb = "1"
embedded-hal = "1"
embedded-io = "0.6"
embedded-dma = "0.2"
num_enum = { version = "0.7", default-features = false }
typenum = "1"
bitflags = "2"
bitfield = "0.15"
defmt = { version = "0.3", optional = true }
bitbybit = "1.3"
arbitrary-int = "1.3"
fugit = "0.3"
delegate = "0.12"
embedded-can = "0.4"
embassy-sync = "0.6"
thiserror = { version = "2", default-features = false }
[dependencies.void]
version = "1"
default-features = false
[dependencies.va416xx]
default-features = false
version = "0.2"
features = ["critical-section"]
defmt = { version = "0.3", optional = true }
[features]
default = ["rt", "revb"]
rt = ["va416xx/rt"]
defmt = ["dep:defmt", "fugit/defmt"]
alloc = []
defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"]
va41630 = ["device-selected"]
va41620 = ["device-selected"]
va41629 = ["device-selected"]
va41628 = ["device-selected", "vorago-shared-periphs/va41628"]
device-selected = []
revb = []
[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,10 +1,16 @@
//! 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;
use crate::pac;
use crate::prelude::*;
use crate::time::Hertz;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000);
pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000);
@ -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>,
@ -137,8 +151,8 @@ pub struct Adc<TagEnabled = ChannelTagDisabled> {
impl Adc<ChannelTagEnabled> {}
impl Adc<ChannelTagDisabled> {
pub fn new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, clocks: &Clocks) -> Self {
Self::generic_new(syscfg, adc, clocks)
pub fn new(adc: pac::Adc, clocks: &Clocks) -> Self {
Self::generic_new(adc, clocks)
}
pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result<u16, AdcEmptyError> {
@ -154,43 +168,49 @@ 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> {
pub fn new_with_channel_tag(
syscfg: &mut pac::Sysconfig,
adc: pac::Adc,
clocks: &Clocks,
) -> Self {
let mut adc = Self::generic_new(syscfg, adc, clocks);
pub fn new_with_channel_tag(adc: pac::Adc, clocks: &Clocks) -> Self {
let mut adc = Self::generic_new(adc, clocks);
adc.enable_channel_tag();
adc
}
@ -230,17 +250,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 +274,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);
fn generic_new(adc: pac::Adc, _clocks: &Clocks) -> Self {
enable_peripheral_clock(PeripheralSelect::Adc);
adc.ctrl().write(|w| unsafe { w.bits(0) });
let adc = Self {
adc,
@ -274,11 +303,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 +350,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,311 @@
use core::{
future::Future,
sync::atomic::{AtomicU8, Ordering},
};
use crate::can::regs::BufferState;
use super::{
regs::{DiagnosticRegister, InterruptClear, MmioCan, StatusPending},
CanChannelLowLevel, CanFrame, CanId, InvalidBufferIndexError,
};
#[derive(Debug)]
pub enum TxChannelState {
Unconfigured = 0,
Idle = 1,
TxDataFrame = 2,
TxRtrTransmission = 3,
TxRtrReception = 4,
Finished = 5,
}
static TX_STATES: [AtomicU8; 15] = [const { AtomicU8::new(0) }; 15];
static TX_WAKERS: [embassy_sync::waitqueue::AtomicWaker; 15] =
[const { embassy_sync::waitqueue::AtomicWaker::new() }; 15];
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TxEventId {
/// Buffer state went from [BufferState::TxOnce] to [BufferState::TxNotActive].
TxDataFrame,
/// Buffer state went from [BufferState::TxOnce] to [BufferState::TxNotActive] for a remote
/// frame (RTR bit set). Channel might be in reception mode [BufferState::RxReady] now.
TxRemoteFrame,
/// A response to a remote frame was performed successfully, and the buffer state went from
/// [BufferState::TxOnceRtr] to [BufferState::TxRtr].
RtrResponse,
/// A remote frame was received and the transmission of a response frame was scheduled. The
/// buffer state went from [BufferState::TxRtr] to [BufferState::TxOnceRtr].
TransmitScheduling,
}
#[derive(Debug)]
pub enum InterruptResult {
NoInterrupt,
ReceivedFrame {
channel_index: usize,
frame: CanFrame,
},
TransmissionEvent {
channel_index: usize,
id: TxEventId,
},
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptError {
UnexpectedError,
InvalidInterruptId(StatusPending),
InvalidStatus(u8),
UnexpectedState(BufferState),
CanError(DiagnosticRegister),
}
/// This interrupt handler allow asynchronous transmission and reception of CAN frames.
///
/// This handler will re-configure a channel to [BufferState::RxReady] after successfull reception
/// of a frame without disabling the interrupts, assuming that the user wants to immediately
/// receive the next frame on the channel.
/// The user should re-configure the buffer state to [BufferState::RxNotActive] if the reception
/// should be disabled.
///
/// The handler will re-configure a channel to [BufferState::TxNotActive] instead of
/// [BufferState::RxReady] if the completed frame transmission was a remote frame and after
/// successfully having received a response to that remote frame. The assumption is that this
/// channel is used to request more frames. If the argument `reconfigure_tx_rtr_to_tx` is set to
/// true, the channel will automatically be configured back to [BufferState::TxNotActive] with
/// interrupts for the respective channel disabled after transmission of a remote frame.
///
/// The handler will not disable the interrupts realted to the TX RTR and TX RTR ONCE auto-response
/// functionality of the CAN peripheral. It will report the event type to the caller via the
/// [TxEventId] enumeration.
pub fn on_interrupt_can(
id: CanId,
reconfigure_tx_rtr_to_tx: bool,
) -> Result<InterruptResult, InterruptError> {
let mut regs = unsafe { id.steal_regs() };
// Check if any interrupts are enabled.
let ie = regs.read_ien();
if ie.raw_value() == 0 {
return Ok(InterruptResult::NoInterrupt);
}
let pending_id = regs.read_status_pending();
if pending_id.interrupt_id().is_none() {
regs.write_iclr(InterruptClear::new_with_raw_value(0xFFFF_FFFF));
return Err(InterruptError::InvalidInterruptId(pending_id));
}
match pending_id.interrupt_id().unwrap() {
super::regs::CanInterruptId::None => Ok(InterruptResult::NoInterrupt),
super::regs::CanInterruptId::Error => Err(InterruptError::CanError(regs.read_diag())),
super::regs::CanInterruptId::Buffer(idx) => {
let mut channel = unsafe { CanChannelLowLevel::steal_unchecked(id, idx) };
let status = channel.read_state();
if status.is_err() {
let mut clr = InterruptClear::new_with_raw_value(0);
clr.set_buffer(idx, true);
regs.write_iclr(clr);
regs.modify_ien(|mut val| {
val.set_buffer(idx, false);
val
});
return Err(InterruptError::InvalidStatus(status.unwrap_err()));
}
let buf_state = status.unwrap();
if buf_state == BufferState::TxNotActive {
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
clear_and_disable_interrupt(&mut regs, idx);
// Handle reading frames, updating states etc.
if tx_state == TxChannelState::TxDataFrame as u8 {
// Transmission complete.
TX_STATES[idx].store(TxChannelState::Finished as u8, Ordering::Relaxed);
TX_WAKERS[idx].wake();
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TxDataFrame,
});
}
}
if buf_state == BufferState::RxReady {
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
if tx_state == TxChannelState::TxRtrTransmission as u8 {
if reconfigure_tx_rtr_to_tx {
channel.write_state(BufferState::TxNotActive);
clear_and_disable_interrupt(&mut regs, idx);
// Transmission complete.
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
} else {
// Do not disable interrupt, channel is now used to receive the frame.
clear_interrupt(&mut regs, idx);
// Transmission complete.
TX_STATES[idx]
.store(TxChannelState::TxRtrReception as u8, Ordering::Relaxed);
}
TX_WAKERS[idx].wake();
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TxRemoteFrame,
});
}
}
if buf_state == BufferState::RxOverrun || buf_state == BufferState::RxFull {
let tx_state = TX_STATES[idx].load(Ordering::Relaxed);
// Do not disable interrupt and assume continuous reception.
clear_interrupt(&mut regs, idx);
let frame = channel.read_frame_unchecked();
if tx_state == TxChannelState::TxRtrReception as u8 {
// Reception of response complete. We can release the channel for TX (or RX)
// usage again.
TX_STATES[idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
channel.write_state(BufferState::TxNotActive);
} else {
// Assume continous reception of frames.
channel.write_state(BufferState::RxReady);
}
return Ok(InterruptResult::ReceivedFrame {
channel_index: idx,
frame,
});
}
if buf_state == BufferState::TxRtr {
// Do not disable interrupt and assume continuous transmission.
clear_interrupt(&mut regs, idx);
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::RtrResponse,
});
}
if buf_state == BufferState::TxOnceRtr {
// Do not disable interrupt and assume continuous transmission.
clear_interrupt(&mut regs, idx);
return Ok(InterruptResult::TransmissionEvent {
channel_index: idx,
id: TxEventId::TransmitScheduling,
});
}
Err(InterruptError::UnexpectedState(buf_state))
}
}
}
#[inline(always)]
fn clear_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
let mut clr = InterruptClear::new_with_raw_value(0);
clr.set_buffer(idx, true);
regs.write_iclr(clr);
}
#[inline(always)]
fn clear_and_disable_interrupt(regs: &mut MmioCan<'static>, idx: usize) {
clear_interrupt(regs, idx);
regs.modify_ien(|mut val| {
val.set_buffer(idx, false);
val
});
}
#[derive(Debug, thiserror::Error)]
#[error("all channels are unconfigured, none available for TX")]
pub struct AllTxChannelsUnconfiguredError;
pub struct CanTxFuture(usize);
impl Future for CanTxFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
TX_WAKERS[self.0].register(cx.waker());
if TX_STATES[self.0].load(Ordering::Relaxed) == TxChannelState::Finished as u8 {
TX_STATES[self.0].store(TxChannelState::Idle as u8, Ordering::Relaxed);
return core::task::Poll::Ready(());
}
core::task::Poll::Pending
}
}
impl CanTxFuture {
pub fn new(frame: CanFrame) -> nb::Result<Self, AllTxChannelsUnconfiguredError> {
let mut channel_is_free = [false; 15];
let mut all_channels_unused = true;
for (idx, state) in TX_STATES.iter().enumerate() {
let state = state.load(Ordering::Relaxed);
if state == TxChannelState::Idle as u8 {
channel_is_free[idx] = true;
}
if state != TxChannelState::Unconfigured as u8 {
all_channels_unused = false;
}
}
if channel_is_free.iter().all(|&x| !x) {
return Err(nb::Error::WouldBlock);
}
if all_channels_unused {
return Err(nb::Error::Other(AllTxChannelsUnconfiguredError));
}
let free_channel_id = channel_is_free.iter().position(|&x| x).unwrap();
let mut channel =
unsafe { CanChannelLowLevel::steal_unchecked(CanId::Can0, free_channel_id) };
TX_STATES[free_channel_id].store(TxChannelState::TxDataFrame as u8, Ordering::Relaxed);
channel.write_state(BufferState::TxNotActive);
channel.transmit_frame_unchecked(frame);
channel.clear_interrupt();
channel.enable_interrupt(true);
channel.enable_error_interrupt(true);
Ok(CanTxFuture(free_channel_id))
}
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ChannelConfigError {
#[error("channel is busy")]
Busy,
#[error("invalid offset: {0}")]
Offset(#[from] InvalidBufferIndexError),
}
pub struct CanTxAsync;
impl CanTxAsync {
pub fn new(can: &mut super::Can) -> Self {
can.clear_interrupts();
can.enable_nvic_interrupt();
CanTxAsync
}
pub fn configure_channel(&mut self, channel_idx: usize) -> Result<(), ChannelConfigError> {
if channel_idx >= TX_STATES.len() {
return Err(ChannelConfigError::Offset(InvalidBufferIndexError(
channel_idx,
)));
}
let state = TX_STATES[channel_idx].load(Ordering::Relaxed);
if state != TxChannelState::Idle as u8 && state != TxChannelState::Unconfigured as u8 {
return Err(ChannelConfigError::Busy);
}
TX_STATES[channel_idx].store(TxChannelState::Idle as u8, Ordering::Relaxed);
Ok(())
}
/// Start a transmission and returns the future which can be polled to completion.
pub fn start_transmit(
&mut self,
frame: CanFrame,
) -> nb::Result<CanTxFuture, AllTxChannelsUnconfiguredError> {
CanTxFuture::new(frame)
}
/// Calls [Self::start_transmit] and awaits the returned future to completion immediately.
pub async fn transmit(
&mut self,
frame: CanFrame,
) -> nb::Result<(), AllTxChannelsUnconfiguredError> {
self.start_transmit(frame)?.await;
Ok(())
}
}

View File

@ -0,0 +1,131 @@
pub use embedded_can::{ExtendedId, Id, StandardId};
#[derive(Debug, thiserror::Error)]
#[error("invalid data size error {0}")]
pub struct InvalidDataSizeError(usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CanFrameNormal {
id: embedded_can::Id,
size: usize,
data: [u8; 8],
}
impl CanFrameNormal {
pub fn new(id: embedded_can::Id, data: &[u8]) -> Result<Self, InvalidDataSizeError> {
if data.len() > 8 {
return Err(InvalidDataSizeError(data.len()));
}
let size = data.len();
let mut data_array = [0; 8];
data_array[0..size].copy_from_slice(data);
Ok(Self {
id,
size,
data: data_array,
})
}
#[inline]
pub fn id(&self) -> embedded_can::Id {
self.id
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data[0..self.dlc()]
}
#[inline]
pub fn dlc(&self) -> usize {
self.size
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CanFrameRtr {
id: embedded_can::Id,
dlc: usize,
}
impl CanFrameRtr {
pub fn new(id: embedded_can::Id, dlc: usize) -> Self {
Self { id, dlc }
}
pub fn id(&self) -> embedded_can::Id {
self.id
}
pub fn dlc(&self) -> usize {
self.dlc
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CanFrame {
Normal(CanFrameNormal),
Rtr(CanFrameRtr),
}
impl From<CanFrameNormal> for CanFrame {
fn from(value: CanFrameNormal) -> Self {
Self::Normal(value)
}
}
impl From<CanFrameRtr> for CanFrame {
fn from(value: CanFrameRtr) -> Self {
Self::Rtr(value)
}
}
impl embedded_can::Frame for CanFrame {
fn new(id: impl Into<embedded_can::Id>, data: &[u8]) -> Option<Self> {
if data.len() > 8 {
return None;
}
let id: embedded_can::Id = id.into();
Some(Self::Normal(CanFrameNormal::new(id, data).unwrap()))
}
fn new_remote(id: impl Into<embedded_can::Id>, dlc: usize) -> Option<Self> {
let id: embedded_can::Id = id.into();
Some(Self::Rtr(CanFrameRtr::new(id, dlc)))
}
fn is_extended(&self) -> bool {
match self.id() {
embedded_can::Id::Extended(_) => true,
embedded_can::Id::Standard(_) => false,
}
}
fn is_remote_frame(&self) -> bool {
match self {
CanFrame::Normal(_) => false,
CanFrame::Rtr(_) => true,
}
}
fn id(&self) -> embedded_can::Id {
match self {
CanFrame::Normal(can_frame_normal) => can_frame_normal.id(),
CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.id(),
}
}
fn dlc(&self) -> usize {
match self {
CanFrame::Normal(can_frame_normal) => can_frame_normal.dlc(),
CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.dlc(),
}
}
fn data(&self) -> &[u8] {
match self {
CanFrame::Normal(can_frame_normal) => can_frame_normal.data(),
CanFrame::Rtr(_) => &[],
}
}
}

317
va416xx-hal/src/can/ll.rs Normal file
View File

@ -0,0 +1,317 @@
use arbitrary_int::{u11, u15, u3, u4, Number};
use embedded_can::Frame;
use super::{
regs::{
self, BaseId, BufStatusAndControl, BufferState, ExtendedId, MmioCanMsgBuf, TwoBytesData,
},
CanFrame, CanFrameNormal, CanFrameRtr, CanId, InvalidBufferIndexError,
};
pub struct CanChannelLowLevel {
id: CanId,
/// Message buffer index.
idx: usize,
msg_buf: MmioCanMsgBuf<'static>,
}
impl core::fmt::Debug for CanChannelLowLevel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CanChannel")
.field("can_id", &self.id)
.field("idx", &self.idx)
.finish()
}
}
impl CanChannelLowLevel {
/// Steal a low level instance of a CAN channel.
///
/// # Safety
///
/// Circumvents ownership and safety guarantees of the HAL.
#[inline]
pub unsafe fn steal(can: CanId, idx: usize) -> Result<Self, InvalidBufferIndexError> {
if idx > 14 {
return Err(InvalidBufferIndexError(idx));
}
let msg_buf = unsafe { can.steal_regs().steal_cmbs_unchecked(idx) };
Ok(Self {
id: can,
idx,
msg_buf,
})
}
/// Steal a low level instance of a CAN channel without and index checks.
///
/// # Safety
///
/// Does not perform any bound checks. Passing an invalid index of 15 or higher leads to
/// undefined behaviour.
#[inline]
pub const unsafe fn steal_unchecked(can: CanId, idx: usize) -> Self {
if idx > 14 {
panic!("invalid buffer index for CAN low level channel");
}
let msg_buf = unsafe { can.steal_regs().steal_cmbs_unchecked(idx) };
Self {
id: can,
idx,
msg_buf,
}
}
/// # Safety
///
/// Allows to create an aribtrary amoutn of driver handles to the same message block, which
/// might lead to data races on invalid usage.
#[inline]
pub const unsafe fn clone(&self) -> Self {
Self {
id: self.id,
idx: self.idx,
msg_buf: unsafe { self.msg_buf.clone() },
}
}
pub fn reset(&mut self) {
self.msg_buf.reset();
}
#[inline]
pub fn read_state(&self) -> Result<BufferState, u8> {
self.msg_buf.read_stat_ctrl().state()
}
#[inline]
pub fn write_state(&mut self, buffer_state: BufferState) {
self.msg_buf.modify_stat_ctrl(|mut val| {
val.set_state(buffer_state);
val
});
}
pub fn configure_for_transmission(&mut self, tx_priority: Option<u4>) {
self.msg_buf.modify_stat_ctrl(|mut val| {
val.set_dlc(u4::new(0));
if let Some(tx_priority) = tx_priority {
val.set_priority(tx_priority);
}
val.set_state(BufferState::TxNotActive);
val
});
}
pub fn set_standard_id(&mut self, standard_id: embedded_can::StandardId, set_rtr: bool) {
let mut id1_reg = standard_id.as_raw() << 5;
if set_rtr {
id1_reg |= 1 << 4;
}
self.msg_buf
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
}
pub fn set_extended_id(&mut self, extended_id: embedded_can::ExtendedId, set_rtr: bool) {
let id_raw = extended_id.as_raw();
let id1_reg = (((id_raw >> 18) & 0x7FF) << 4) as u16 | ((id_raw >> 15) & 0b111) as u16;
self.msg_buf
.write_id1(BaseId::new_with_raw_value(id1_reg as u32));
let id0_reg = ((id_raw & 0x7FFF) << 1) as u16 | set_rtr as u16;
self.msg_buf
.write_id0(ExtendedId::new_with_raw_value(id0_reg as u32));
}
pub fn configure_for_reception(&mut self) {
self.msg_buf.write_stat_ctrl(
BufStatusAndControl::builder()
.with_dlc(u4::new(0))
.with_priority(u4::new(0))
.with_state(BufferState::RxReady)
.build(),
);
}
pub fn transmit_frame_unchecked(&mut self, frame: CanFrame) {
let is_remote = frame.is_remote_frame();
self.write_id(frame.id(), is_remote);
let dlc = frame.dlc();
self.msg_buf.modify_stat_ctrl(|mut ctrl| {
ctrl.set_dlc(u4::new(dlc as u8));
ctrl
});
if !is_remote {
self.msg_buf
.write_data0(TwoBytesData::new_with_raw_value(0));
self.msg_buf
.write_data1(TwoBytesData::new_with_raw_value(0));
self.msg_buf
.write_data2(TwoBytesData::new_with_raw_value(0));
self.msg_buf
.write_data3(TwoBytesData::new_with_raw_value(0));
for idx in 0..dlc {
match idx {
0 => self.msg_buf.modify_data0(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
1 => self.msg_buf.modify_data0(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
2 => self.msg_buf.modify_data1(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
3 => self.msg_buf.modify_data1(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
4 => self.msg_buf.modify_data2(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
5 => self.msg_buf.modify_data2(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
6 => self.msg_buf.modify_data3(|mut val| {
val.set_data_upper_byte(frame.data()[idx]);
val
}),
7 => self.msg_buf.modify_data3(|mut val| {
val.set_data_lower_byte(frame.data()[idx]);
val
}),
_ => unreachable!(),
}
}
}
self.write_state(BufferState::TxOnce);
}
#[inline]
pub fn clear_interrupt(&mut self) {
let mut regs = unsafe { self.id.steal_regs() };
let mut clear = regs::InterruptClear::new_with_raw_value(0);
clear.set_buffer(self.idx, true);
regs.write_iclr(clear);
}
pub fn enable_error_interrupt(&mut self, enable_translation: bool) {
let mut regs = unsafe { self.id.steal_regs() };
if enable_translation {
regs.modify_icen(|mut val| {
val.set_error(true);
val
});
}
regs.modify_ien(|mut val| {
val.set_error(true);
val
});
}
pub fn enable_interrupt(&mut self, enable_translation: bool) {
let mut regs = unsafe { self.id.steal_regs() };
if enable_translation {
regs.modify_icen(|mut val| {
val.set_buffer(self.idx, true);
val
});
}
regs.modify_ien(|mut val| {
val.set_buffer(self.idx, true);
val
});
}
fn write_id(&mut self, id: embedded_can::Id, is_remote: bool) {
match id {
embedded_can::Id::Standard(standard_id) => {
self.msg_buf.write_id1(
BaseId::builder()
.with_mask_28_18(u11::new(standard_id.as_raw()))
.with_rtr_or_srr(is_remote)
.with_ide(false)
.with_mask_17_15(u3::new(0))
.build(),
);
self.msg_buf.write_id0(ExtendedId::new_with_raw_value(0));
}
embedded_can::Id::Extended(extended_id) => {
let id_raw = extended_id.as_raw();
self.msg_buf.write_id1(
BaseId::builder()
.with_mask_28_18(u11::new(((id_raw >> 18) & 0x7FF) as u16))
.with_rtr_or_srr(true)
.with_ide(true)
.with_mask_17_15(u3::new(((id_raw >> 15) & 0b111) as u8))
.build(),
);
self.msg_buf.write_id0(
ExtendedId::builder()
.with_mask_14_0(u15::new((id_raw & 0x7FFF) as u16))
.with_xrtr(is_remote)
.build(),
);
}
}
}
/// Reads a received CAN frame from the message buffer.
///
/// This function does not check whether the pre-requisites for reading a CAN frame were
/// met and assumes this was already checked by the user.
pub fn read_frame_unchecked(&self) -> CanFrame {
let id0 = self.msg_buf.read_id0();
let id1 = self.msg_buf.read_id1();
let data0 = self.msg_buf.read_data0();
let data1 = self.msg_buf.read_data1();
let data2 = self.msg_buf.read_data2();
let data3 = self.msg_buf.read_data3();
let mut data: [u8; 8] = [0; 8];
let mut read_data = |dlc: u4| {
(0..dlc.as_usize()).for_each(|i| match i {
0 => data[i] = data0.data_upper_byte().as_u8(),
1 => data[i] = data0.data_lower_byte().as_u8(),
2 => data[i] = data1.data_upper_byte().as_u8(),
3 => data[i] = data1.data_lower_byte().as_u8(),
4 => data[i] = data2.data_upper_byte().as_u8(),
5 => data[i] = data2.data_lower_byte().as_u8(),
6 => data[i] = data3.data_upper_byte().as_u8(),
7 => data[i] = data3.data_lower_byte().as_u8(),
_ => unreachable!(),
});
};
let (id, rtr) = if !id1.ide() {
let id = embedded_can::Id::Standard(
embedded_can::StandardId::new(id1.mask_28_18().as_u16()).unwrap(),
);
if id1.rtr_or_srr() {
(id, true)
} else {
(id, false)
}
} else {
let id_raw = (id1.mask_28_18().as_u32() << 18)
| (id1.mask_17_15().as_u32() << 15)
| id0.mask_14_0().as_u32();
let id = embedded_can::Id::Extended(embedded_can::ExtendedId::new(id_raw).unwrap());
if id0.xrtr() {
(id, true)
} else {
(id, false)
}
};
if rtr {
CanFrameRtr::new(id, self.msg_buf.read_stat_ctrl().dlc().as_usize()).into()
} else {
let dlc = self.msg_buf.read_stat_ctrl().dlc();
read_data(dlc);
CanFrameNormal::new(id, &data[0..dlc.as_usize()])
.unwrap()
.into()
}
}
}

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

@ -0,0 +1,830 @@
//! # CAN peripheral driver.
//!
//! The VA416xx CAN module is based on the CP3UB26 module.
//!
//! Using the CAN bus generally involves the following steps:
//!
//! 1. Create a [Can] instance
//! 2. The [CanChannels] resource management singleton can be retrieved by using
//! [Can::take_channels].
//! 3. Individual [CanRx] and [CanTx] channels can be created using the [CanChannels::take]
//! function. These allow to send or receive CAN frames on individual channels.
//! 4. The [asynch::CanTxAsync] structure can be created to transmit frames asynchronously.
//! The [asynch::on_interrupt_can] function should be called in the user interrupt handler
//! for CAN0 and CAN1 for this to work properly. The interrupt handler can also take care of
//! receiving frames on [CanRx] channels with enabled interrupts.
//!
//! # Example
//!
//! - [CAN example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/can.rs)
use core::sync::atomic::AtomicBool;
use arbitrary_int::{u11, u15, u2, u3, u4, u7, Number};
use embedded_can::Frame;
use ll::CanChannelLowLevel;
use regs::{BaseId, BufferState, Control, MmioCan, TimingConfig};
use vorago_shared_periphs::enable_nvic_interrupt;
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
use libm::roundf;
pub mod frame;
pub use frame::*;
pub mod asynch;
pub mod ll;
pub mod regs;
pub const PRESCALER_MIN: u8 = 2;
pub const PRESCALER_MAX: u8 = 128;
/// 1 is the minimum value, but not recommended by Vorago.
pub const TSEG1_MIN: u8 = 1;
pub const TSEG1_MAX: u8 = 16;
pub const TSEG2_MAX: u8 = 8;
/// In addition, SJW may not be larger than TSEG2.
pub const SJW_MAX: u8 = 4;
pub const MIN_SAMPLE_POINT: f32 = 0.5;
pub const MAX_BITRATE_DEVIATION: f32 = 0.005;
static CHANNELS_TAKEN: [AtomicBool; 2] = [AtomicBool::new(false), AtomicBool::new(false)];
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CanId {
Can0 = 0,
Can1 = 1,
}
impl CanId {
/// Steal the register block for the CAN ID.
///
/// # Safety
///
/// See safety of the [regs::Can::new_mmio_fixed_0].
#[inline]
pub const unsafe fn steal_regs(&self) -> regs::MmioCan<'static> {
match self {
CanId::Can0 => unsafe { regs::Can::new_mmio_fixed_0() },
CanId::Can1 => unsafe { regs::Can::new_mmio_fixed_1() },
}
}
#[inline]
pub const fn irq_id(&self) -> va416xx::Interrupt {
match self {
CanId::Can0 => va416xx::Interrupt::CAN0,
CanId::Can1 => va416xx::Interrupt::CAN1,
}
}
}
/// Sample point between 0 and 1.0 for the given time segments.
pub const fn calculate_sample_point(tseg1: u8, tseg2: u8) -> f32 {
let tseg1_val = tseg1 as f32;
(tseg1_val + 1.0) / (1.0 + tseg1_val + tseg2 as f32)
}
#[derive(Debug, Clone, Copy)]
pub struct ClockConfig {
prescaler: u8,
tseg1: u8,
tseg2: u8,
sjw: u8,
}
impl ClockConfig {
/// New clock configuration from the raw configuration values.
///
/// The values specified here are not the register values, but the actual numerical values
/// relevant for calculations.
///
/// The values have the following requirements:
///
/// - Prescaler must be between 2 and 128.
/// - TSEG1 must be smaller than 16 and should be larger than 1.
/// - TSEG2 must be smaller than 8 and small enough so that the calculated sample point
/// is larger than 0.5 (50 %).
/// - SJW (Synchronization Jump Width) must be smaller than the smaller of the time segment
/// configuration values and smaller than 4.
pub fn new(prescaler: u8, tseg1: u8, tseg2: u8, sjw: u8) -> Result<Self, ClockConfigError> {
if !(PRESCALER_MIN..=PRESCALER_MAX).contains(&prescaler.value()) {
return Err(ClockConfigError::CanNotFindPrescaler);
}
if tseg1 == 0 || tseg2 == 0 {
return Err(ClockConfigError::TsegIsZero);
}
if tseg1 > TSEG1_MAX {
return Err(ClockConfigError::InvalidTseg1);
}
if tseg2 > TSEG2_MAX {
return Err(ClockConfigError::InvalidTseg2);
}
let smaller_tseg = core::cmp::min(tseg1.value(), tseg2.value());
if sjw.value() > smaller_tseg || sjw > SJW_MAX {
return Err(InvalidSjwError(sjw).into());
}
let sample_point = calculate_sample_point(tseg1, tseg2);
if sample_point < MIN_SAMPLE_POINT {
return Err(InvalidSamplePointError { sample_point }.into());
}
Ok(Self {
prescaler,
tseg1,
tseg2,
sjw,
})
}
/// Calculate the clock configuration for the given input clock, the target bitrate and for a
/// set of timing parameters. The CAN controller uses the APB1 clock.
///
/// This function basically calculates the necessary prescaler to achieve the given timing
/// parameters. It also performs sanity and validity checks for the calculated prescaler:
/// The bitrate error for the given prescaler needs to be smaller than 0.5 %.
pub fn from_bitrate_and_segments(
clocks: &Clocks,
bitrate: Hertz,
tseg1: u8,
tseg2: u8,
sjw: u8,
) -> Result<ClockConfig, ClockConfigError> {
if bitrate.raw() == 0 {
return Err(ClockConfigError::BitrateIsZero);
}
let nominal_bit_time = 1 + tseg1 as u32 + tseg2 as u32;
let prescaler =
roundf(clocks.apb1().raw() as f32 / (bitrate.raw() as f32 * nominal_bit_time as f32))
as u32;
if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
return Err(ClockConfigError::CanNotFindPrescaler);
}
let actual_bitrate = (clocks.apb1().raw() as f32) / (prescaler * nominal_bit_time) as f32;
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
if bitrate_deviation > MAX_BITRATE_DEVIATION {
return Err(ClockConfigError::BitrateErrorTooLarge);
}
// The subtractions are fine because we made checks to avoid underflows.
Self::new(prescaler as u8, tseg1, tseg2, sjw)
}
#[inline]
pub fn sjw_reg_value(&self) -> u2 {
u2::new(self.sjw.value() - 1)
}
#[inline]
pub fn tseg1_reg_value(&self) -> u4 {
u4::new(self.tseg1.value() - 1)
}
#[inline]
pub fn tseg2_reg_value(&self) -> u3 {
u3::new(self.tseg2.value() - 1)
}
#[inline]
pub fn prescaler_reg_value(&self) -> u7 {
u7::new(self.prescaler.value() - 2)
}
}
/// Calculate all viable clock configurations for the given input clock, the target bitrate and
/// for a sample point between 0.5 and 1.0.
///
/// There are various recommendations for the sample point when using the CAN bus. The value
/// depends on different parameters like the bus length and propagation time, as well as
/// the information processing time of the nodes. It should always be at least 50 %.
/// In doubt, select a value like 0.75.
///
/// - The [Python CAN library](https://python-can.readthedocs.io/en/stable/bit_timing.html)
/// assumes a default value of 69 % as the sample point if none is specified.
/// - CiA-301 recommends 87.5 %
/// - For simpler setups like laboratory setups, smaller values should work as well.
///
/// A clock configuration is consideres viable when
///
/// - The sample point deviation is less than 5 %.
/// - The bitrate error is less than +-0.5 %.
///
/// SJW will be set to either TSEG2 or 4, whichever is smaller.
#[cfg(feature = "alloc")]
pub fn calculate_all_viable_clock_configs(
apb1_clock: Hertz,
bitrate: Hertz,
sample_point: f32,
) -> Result<alloc::vec::Vec<(ClockConfig, f32)>, InvalidSamplePointError> {
if sample_point < 0.5 || sample_point > 1.0 {
return Err(InvalidSamplePointError { sample_point });
}
let mut configs = alloc::vec::Vec::new();
for prescaler in PRESCALER_MIN..PRESCALER_MAX {
let nom_bit_time = calculate_nominal_bit_time(apb1_clock, bitrate, prescaler);
// This is taken from the Python CAN library. NBT should not be too small.
if nom_bit_time < 8 {
break;
}
let actual_bitrate = calculate_actual_bitrate(apb1_clock, prescaler, nom_bit_time);
let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
if bitrate_deviation > 0.05 {
continue;
}
let tseg1 = roundf(sample_point * nom_bit_time as f32) as u32 - 1;
if tseg1 > TSEG1_MAX as u32 || tseg1 < TSEG1_MIN as u32 {
continue;
}
// limit tseg1, so tseg2 is at least 1 TQ
let tseg1 = core::cmp::min(tseg1, nom_bit_time - 2) as u8;
let tseg2 = nom_bit_time - tseg1 as u32 - 1;
if tseg2 > TSEG2_MAX as u32 {
continue;
}
let tseg2 = tseg2 as u8;
let sjw = core::cmp::min(tseg2, 4) as u8;
// Use percent to have a higher resolution for the sample point deviation.
let sample_point_actual = roundf(calculate_sample_point(tseg1, tseg2) * 100.0) as u32;
let sample_point = roundf(sample_point * 100.0) as u32;
let deviation = (sample_point_actual as i32 - sample_point as i32).abs();
if deviation > 5 {
continue;
}
configs.push((
ClockConfig {
prescaler,
tseg1,
tseg2,
sjw,
},
bitrate_deviation,
));
}
Ok(configs)
}
#[inline]
pub const fn calculate_nominal_bit_time(
apb1_clock: Hertz,
target_bitrate: Hertz,
prescaler: u8,
) -> u32 {
apb1_clock.raw() / (target_bitrate.raw() * prescaler as u32)
}
#[inline]
pub const fn calculate_actual_bitrate(apb1_clock: Hertz, prescaler: u8, nom_bit_time: u32) -> f32 {
apb1_clock.raw() as f32 / (prescaler as u32 * nom_bit_time) as f32
}
#[inline]
pub const fn calculate_bitrate_deviation(actual_bitrate: f32, target_bitrate: Hertz) -> f32 {
(actual_bitrate - target_bitrate.raw() as f32).abs() / target_bitrate.raw() as f32
}
pub trait CanMarker {
const ID: CanId;
const IRQ: va416xx::Interrupt;
const PERIPH_SEL: PeripheralSelect;
}
impl CanMarker for va416xx::Can0 {
const ID: CanId = CanId::Can0;
const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0;
}
impl CanMarker for va416xx::Can1 {
const ID: CanId = CanId::Can1;
const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid buffer index {0}")]
pub struct InvalidBufferIndexError(usize);
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("sjw must be less than or equal to the smaller tseg value")]
pub struct InvalidSjwError(u8);
#[derive(Debug, thiserror::Error)]
#[error("invalid sample point {sample_point}")]
pub struct InvalidSamplePointError {
/// Sample point, should be larger than 0.5 (50 %) but was not.
sample_point: f32,
}
#[derive(Debug, thiserror::Error)]
pub enum ClockConfigError {
#[error("invalid sjw: {0}")]
InvalidSjw(#[from] InvalidSjwError),
#[error("TSEG is zero which is not allowed")]
TsegIsZero,
#[error("TSEG1 is larger than 16")]
InvalidTseg1,
#[error("TSEG1 is larger than 8")]
InvalidTseg2,
#[error("invalid sample point: {0}")]
InvalidSamplePoint(#[from] InvalidSamplePointError),
#[error("bitrate is zero")]
BitrateIsZero,
#[error("bitrate error larger than +-0.5 %")]
BitrateErrorTooLarge,
#[error("maximum or minimum allowed prescaler is not sufficient for target bitrate clock")]
CanNotFindPrescaler,
}
/// The main CAN peripheral driver.
pub struct Can {
regs: regs::MmioCan<'static>,
id: CanId,
}
impl Can {
pub fn new<CanI: CanMarker>(_can: CanI, clk_config: ClockConfig) -> Self {
enable_peripheral_clock(CanI::PERIPH_SEL);
let id = CanI::ID;
let mut regs = if id == CanId::Can0 {
unsafe { regs::Can::new_mmio_fixed_0() }
} else {
unsafe { regs::Can::new_mmio_fixed_1() }
};
// Disable the CAN bus before configuring it.
regs.write_control(Control::new_with_raw_value(0));
for i in 0..15 {
regs.cmbs(i).unwrap().reset();
}
regs.write_timing(
TimingConfig::builder()
.with_tseg2(clk_config.tseg2_reg_value())
.with_tseg1(clk_config.tseg1_reg_value())
.with_sync_jump_width(clk_config.sjw_reg_value())
.with_prescaler(clk_config.prescaler_reg_value())
.build(),
);
Self { regs, id }
}
/// This configures the global mask so that acceptance is only determined by an exact match
/// with the ID in the receive message buffers. This is the default reset configuration for
/// the global mask as well.
pub fn set_global_mask_for_exact_id_match(&mut self) {
self.regs
.write_gmskx(regs::ExtendedId::new_with_raw_value(0));
self.regs.write_gmskb(BaseId::new_with_raw_value(0));
}
/// Retrieve a resource management singleton for the 15 CAN channels.
pub fn take_channels(&self) -> Option<CanChannels> {
if CHANNELS_TAKEN[self.id() as usize].swap(true, core::sync::atomic::Ordering::SeqCst) {
return None;
}
Some(CanChannels::new(self.id))
}
/// Similar to [Self::set_global_mask_for_exact_id_match] but masks the XRTR and RTR/SRR bits.
///
/// This is useful for when transmitting remote frames with the RTR bit set. The hardware
/// will automatically go into the [regs::BufferState::RxReady] state after the transmission,
/// but the XRTR and RTR/SRR bits need to be masked for the response frame to be accepted
/// on that buffer.
pub fn set_global_mask_for_exact_id_match_with_rtr_masked(&mut self) {
self.regs.write_gmskx(
regs::ExtendedId::builder()
.with_mask_14_0(u15::new(0))
.with_xrtr(true)
.build(),
);
self.regs.write_gmskb(
BaseId::builder()
.with_mask_28_18(u11::new(0))
.with_rtr_or_srr(true)
.with_ide(false)
.with_mask_17_15(u3::new(0))
.build(),
);
}
/// This configures the base mask for buffer 14 so that acceptance is only determined by an
/// exact match with the ID in the receive message buffers. This is the default reset
/// configuration for the global mask as well.
#[inline]
pub fn set_base_mask_for_exact_id_match(&mut self) {
self.regs
.write_bmskx(regs::ExtendedId::new_with_raw_value(0));
self.regs.write_bmskb(BaseId::new_with_raw_value(0));
}
/// This configures the base mask so that all CAN frames which are not handled by any other
/// buffers are accepted by the base buffer 14.
#[inline]
pub fn set_base_mask_for_all_match(&mut self) {
self.regs
.write_bmskx(regs::ExtendedId::new_with_raw_value(0xffff));
self.regs.write_bmskb(BaseId::new_with_raw_value(0xffff));
}
#[inline]
pub fn regs(&mut self) -> &mut MmioCan<'static> {
&mut self.regs
}
/// Clear all interrupts.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs
.write_iclr(regs::InterruptClear::new_with_raw_value(0xFFFF_FFFF));
}
/// This function only enable the CAN interrupt vector in the NVIC.
///
/// The interrupts for the individual channels or errors still need to be enabled
/// separately.
#[inline]
pub fn enable_nvic_interrupt(&mut self) {
unsafe {
enable_nvic_interrupt(self.id().irq_id());
}
}
#[inline]
pub fn read_error_counters(&self) -> regs::ErrorCounter {
self.regs.read_error_counter()
}
#[inline]
pub fn read_error_diagnostics(&self) -> regs::DiagnosticRegister {
self.regs.read_diag()
}
#[inline]
pub fn id(&self) -> CanId {
self.id
}
#[inline]
pub fn write_ctrl_reg(&mut self, ctrl: Control) {
self.regs.write_control(ctrl);
}
#[inline]
pub fn modify_control<F>(&mut self, f: F)
where
F: FnOnce(Control) -> Control,
{
self.regs.modify_control(f);
}
#[inline]
pub fn set_bufflock(&mut self, enable: bool) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_bufflock(enable);
ctrl
});
}
#[inline]
pub fn enable(&mut self) {
self.regs.modify_control(|mut ctrl| {
ctrl.set_enable(true);
ctrl
});
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TxState {
Idle,
TransmittingDataFrame,
TransmittingRemoteFrame,
AwaitingRemoteFrameReply,
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InvalidTxState {
State(TxState),
BufferState(BufferState),
}
impl From<TxState> for InvalidTxState {
fn from(state: TxState) -> Self {
InvalidTxState::State(state)
}
}
impl From<BufferState> for InvalidTxState {
fn from(state: BufferState) -> Self {
InvalidTxState::BufferState(state)
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid tx state {0:?}")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidTxStateError(pub InvalidTxState);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RxState {
Idle,
Receiving,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid rx state {0:?}")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidRxStateError(pub RxState);
/// Driver instance to use an individual CAN channel as a transmission channel.
#[derive(Debug)]
pub struct CanTx {
ll: CanChannelLowLevel,
mode: TxState,
}
impl CanTx {
pub fn new(mut ll: CanChannelLowLevel, tx_priority: Option<u4>) -> Self {
ll.reset();
ll.configure_for_transmission(tx_priority);
Self {
ll,
mode: TxState::Idle,
}
}
#[inline]
pub fn into_rx_channel(self) -> CanRx {
CanRx::new(self.ll)
}
/// Start transmitting a frame.
///
/// The frame transmission can be polled/awaited to completion using the [Self::transfer_done]
/// method.
///
/// This function will return a [state error][InvalidTxStateError] if a transmission is already
/// active and/or the transmit buffer has an invalid state.
pub fn transmit_frame(&mut self, frame: CanFrame) -> Result<(), InvalidTxStateError> {
if self.mode == TxState::AwaitingRemoteFrameReply {
self.ll.configure_for_transmission(None);
self.mode = TxState::Idle;
}
if self.mode != TxState::Idle {
return Err(InvalidTxStateError(self.mode.into()));
}
if !frame.is_remote_frame() {
self.mode = TxState::TransmittingDataFrame;
} else {
self.mode = TxState::TransmittingRemoteFrame;
}
if let Ok(state) = self.ll.read_state() {
if state != BufferState::TxNotActive {
return Err(InvalidTxStateError(state.into()));
}
}
self.ll.transmit_frame_unchecked(frame);
Ok(())
}
/// Poll whether an active data frame transmission is done.
///
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
pub fn transfer_done(&mut self) -> nb::Result<(), InvalidTxStateError> {
if self.mode != TxState::TransmittingDataFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::TxNotActive {
self.mode = TxState::Idle;
return Ok(());
}
Err(nb::Error::WouldBlock)
}
/// Poll whether an active remote frame transmission is done.
///
/// On success, returns the channel re-configured to a [CanRx] channel. This is because the
/// default behaviour of the hardware will be to re-configure the channel state to
/// [BufferState::RxReady] once the remote frame has been transmitted so that the response
/// frame can be awaited.
///
/// If the channel should instead be re-configured for transmission again,
/// [Self::remote_transfer_done_with_tx_reconfig] can be used.
///
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
pub fn remote_transfer_done(&mut self) -> nb::Result<CanRx, InvalidTxStateError> {
if self.mode != TxState::TransmittingRemoteFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::RxReady {
self.mode = TxState::AwaitingRemoteFrameReply;
return Ok(CanRx {
ll: unsafe { self.ll.clone() },
mode: RxState::Receiving,
});
}
Err(nb::Error::WouldBlock)
}
/// Poll whether an active remote frame transmission is done.
///
/// This function will re-configure the buffer back for transmission once the
/// transmission has completed.
///
/// Returns a [state error][InvalidTxStateError] if no transmission is active.
pub fn remote_transfer_done_with_tx_reconfig(&mut self) -> nb::Result<(), InvalidTxStateError> {
if self.mode != TxState::TransmittingRemoteFrame {
return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::RxReady {
self.ll.write_state(BufferState::TxNotActive);
self.mode = TxState::Idle;
return Ok(());
}
Err(nb::Error::WouldBlock)
}
pub fn reset(&mut self) {
self.ll.reset();
self.mode = TxState::Idle;
}
}
/// Driver instance to use an individual CAN channel as a reception channel.
pub struct CanRx {
ll: CanChannelLowLevel,
mode: RxState,
}
impl CanRx {
pub fn new(mut ll: CanChannelLowLevel) -> Self {
ll.reset();
Self {
ll,
mode: RxState::Idle,
}
}
#[inline]
pub fn into_tx_channel(self, tx_priority: Option<u4>) -> CanTx {
CanTx::new(self.ll, tx_priority)
}
#[inline]
pub fn enable_interrupt(&mut self, enable_translation: bool) {
self.ll.enable_interrupt(enable_translation);
}
pub fn configure_for_reception_with_standard_id(
&mut self,
standard_id: embedded_can::StandardId,
set_rtr: bool,
) {
self.ll.set_standard_id(standard_id, set_rtr);
self.configure_for_reception();
}
pub fn configure_for_reception_with_extended_id(
&mut self,
extended_id: embedded_can::ExtendedId,
set_rtr: bool,
) {
self.ll.set_extended_id(extended_id, set_rtr);
self.configure_for_reception();
}
pub fn configure_for_reception(&mut self) {
self.ll.configure_for_reception();
self.mode = RxState::Receiving;
}
#[inline]
pub fn frame_available(&self) -> bool {
self.ll
.read_state()
.is_ok_and(|state| state == BufferState::RxFull || state == BufferState::RxOverrun)
}
/// Poll for frame reception. Returns the frame if one is available.
pub fn receive(
&mut self,
reconfigure_for_reception: bool,
) -> nb::Result<CanFrame, InvalidRxStateError> {
if self.mode != RxState::Receiving {
return Err(nb::Error::Other(InvalidRxStateError(self.mode)));
}
let status = self.ll.read_state();
if status.is_err() {
return Err(nb::Error::WouldBlock);
}
let status = status.unwrap();
if status == BufferState::RxFull || status == BufferState::RxOverrun {
self.mode = RxState::Idle;
if reconfigure_for_reception {
self.ll.write_state(BufferState::RxReady);
}
return Ok(self.ll.read_frame_unchecked());
}
Err(nb::Error::WouldBlock)
}
}
pub struct CanChannels {
id: CanId,
channels: [Option<CanChannelLowLevel>; 15],
}
impl CanChannels {
const fn new(id: CanId) -> Self {
// Safety: Private function, ownership rules enforced by public API.
unsafe {
Self {
id,
channels: [
Some(CanChannelLowLevel::steal_unchecked(id, 0)),
Some(CanChannelLowLevel::steal_unchecked(id, 1)),
Some(CanChannelLowLevel::steal_unchecked(id, 2)),
Some(CanChannelLowLevel::steal_unchecked(id, 3)),
Some(CanChannelLowLevel::steal_unchecked(id, 4)),
Some(CanChannelLowLevel::steal_unchecked(id, 5)),
Some(CanChannelLowLevel::steal_unchecked(id, 6)),
Some(CanChannelLowLevel::steal_unchecked(id, 7)),
Some(CanChannelLowLevel::steal_unchecked(id, 8)),
Some(CanChannelLowLevel::steal_unchecked(id, 9)),
Some(CanChannelLowLevel::steal_unchecked(id, 10)),
Some(CanChannelLowLevel::steal_unchecked(id, 11)),
Some(CanChannelLowLevel::steal_unchecked(id, 12)),
Some(CanChannelLowLevel::steal_unchecked(id, 13)),
Some(CanChannelLowLevel::steal_unchecked(id, 14)),
],
}
}
}
pub const fn can_id(&self) -> CanId {
self.id
}
/// Take the indidivual CAN channel low level driver instance.
pub fn take(&mut self, idx: usize) -> Option<CanChannelLowLevel> {
if idx > 14 {
return None;
}
self.channels[idx].take()
}
pub fn give(&mut self, idx: usize, channel: CanChannelLowLevel) {
if idx > 14 {
panic!("invalid buffer index for CAN channel");
}
self.channels[idx] = Some(channel);
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use std::println;
#[cfg(feature = "alloc")]
#[test]
pub fn test_clock_calculator_example_1() {
let configs = super::calculate_all_viable_clock_configs(
crate::time::Hertz::from_raw(50_000_000),
crate::time::Hertz::from_raw(25_000),
0.75,
)
.expect("clock calculation failed");
// Bitrate: 25278.05 Hz. Sample point: 0.7391
assert_eq!(configs[0].prescaler, 84);
assert_eq!(configs[0].tseg1, 16);
assert_eq!(configs[0].tseg2, 6);
assert_eq!(configs[0].sjw, 4);
// Vorago sample value.
let sample_cfg = configs
.iter()
.find(|c| c.prescaler == 100)
.expect("clock config not found");
// Slightly different distribution because we use a different sample point, but
// the sum of TSEG1 and TSEG2 is the same as the Vorago example 1.
assert_eq!(sample_cfg.tseg1, 14);
assert_eq!(sample_cfg.tseg2, 5);
}
}

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

@ -0,0 +1,416 @@
//! Custom register definitions for the CAN register block to circumvent PAC API / SVD
//! shortcomings.
use arbitrary_int::{u11, u15, u2, u3, u4, u6, u7, Number};
pub const CAN_0_BASE: usize = 0x4001_4000;
pub const CAN_1_BASE: usize = 0x4001_4400;
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u4)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BufferState {
/// Passive channel.
RxNotActive = 0b0000,
/// This condition indicated that SW wrote RxNotActive to a buffer when a data copy
/// process is still active.
RxBusy = 0b0001,
RxReady = 0b0010,
/// Indicated that data is being copied for the first time (RxRead -> RxBusy0).
RxBusy0 = 0b0011,
RxFull = 0b0100,
/// Indicated that data is being copied for the second time (RxFull -> RxBusy2).
RxBusy1 = 0b0101,
RxOverrun = 0b0110,
RxBusy2 = 0b0111,
TxNotActive = 0b1000,
/// Automatical response to a remote frame.
TxRtr = 0b1010,
/// Transmit one frame.
TxOnce = 0b1100,
TxBusy0 = 0b1101,
/// Transmit one frame, and changes to TxRtr after that. This can either be written by
/// software, or it will be written by the hardware after an auto response of the
/// [BufferState::TxRtr] state.
TxOnceRtr = 0b1110,
TxBusy2 = 0b1111,
}
/// Status control register for individual message buffers.
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct BufStatusAndControl {
/// Data length code.
#[bits(12..=15, rw)]
dlc: u4,
#[bits(4..=7, rw)]
priority: u4,
#[bits(0..=3, rw)]
state: Option<BufferState>,
}
#[derive(Debug)]
pub struct Timestamp(arbitrary_int::UInt<u32, 16>);
impl Timestamp {
pub fn new(value: u16) -> Self {
Self(value.into())
}
pub fn value(&self) -> u16 {
self.0.value() as u16
}
pub fn write(&mut self, value: u16) {
self.0 = value.into();
}
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct TwoBytesData {
#[bits(0..=7, rw)]
data_lower_byte: u8,
#[bits(8..=15, rw)]
data_upper_byte: u8,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct CanMsgBuf {
stat_ctrl: BufStatusAndControl,
timestamp: Timestamp,
data3: TwoBytesData,
data2: TwoBytesData,
data1: TwoBytesData,
data0: TwoBytesData,
id0: ExtendedId,
id1: BaseId,
}
static_assertions::const_assert_eq!(core::mem::size_of::<CanMsgBuf>(), 0x20);
impl MmioCanMsgBuf<'_> {
pub fn reset(&mut self) {
self.write_stat_ctrl(BufStatusAndControl::new_with_raw_value(0));
self.write_timestamp(Timestamp::new(0));
self.write_data0(TwoBytesData::new_with_raw_value(0));
self.write_data1(TwoBytesData::new_with_raw_value(0));
self.write_data2(TwoBytesData::new_with_raw_value(0));
self.write_data3(TwoBytesData::new_with_raw_value(0));
self.write_id1(BaseId::new_with_raw_value(0));
self.write_id0(ExtendedId::new_with_raw_value(0));
}
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum PinLogicLevel {
DominantIsZero = 0b0,
DominantIsOne = 0b1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum ErrorInterruptType {
/// EIPND bit is set on every error.
EveryError = 0b0,
/// EIPND bit is only set if error state changes as a result of a receive or transmit
/// error counter increment.
ErrorOnRxTxCounterChange = 0b1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum DataDirection {
FirstByteAtHighestAddr = 0b0,
LastByteAtHighestAddr = 0b1,
}
#[bitbybit::bitfield(u32)]
pub struct Control {
#[bit(11, rw)]
error_interrupt_type: ErrorInterruptType,
/// Enables special diagnostics features of the CAN like LO, IGNACK, LOOPBACK, INTERNAL.
#[bit(10, rw)]
diag_enable: bool,
/// CANTX and CANRX pins are internally connected to each other.
#[bit(9, rw)]
internal: bool,
/// All messages sent by the CAN controller can also be received by a CAN buffer with a
/// matching buffer ID.
#[bit(8, rw)]
loopback: bool,
/// IGNACK feature. The CAN does not expect to receive an ACK bit.
#[bit(7, rw)]
ignore_ack: bool,
/// LO feature. The CAN is only configured as a receiver.
#[bit(6, rw)]
listen_only: bool,
#[bit(5, rw)]
data_dir: DataDirection,
#[bit(4, rw)]
timestamp_enable: bool,
#[bit(3, rw)]
bufflock: bool,
#[bit(2, rw)]
tx_logic_level: PinLogicLevel,
#[bit(1, rw)]
rx_logic_level: PinLogicLevel,
#[bit(0, rw)]
enable: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct TimingConfig {
#[bits(0..=2, rw)]
tseg2: u3,
#[bits(3..=6, rw)]
tseg1: u4,
#[bits(7..=8, rw)]
sync_jump_width: u2,
#[bits(9..=15, rw)]
prescaler: u7,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptEnable {
#[bit(15, rw)]
error: bool,
#[bit(0, rw)]
buffer: [bool; 15],
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptClear {
#[bit(15, w)]
error: bool,
#[bit(0, w)]
buffer: [bool; 15],
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptPending {
#[bit(15, r)]
error: bool,
#[bit(0, r)]
buffer: [bool; 15],
}
#[derive(Debug)]
#[repr(usize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CanInterruptId {
None = 0b00000,
Error = 0b10000,
Buffer(usize),
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct StatusPending {
#[bits(5..=7, r)]
ns: u3,
#[bit(4, r)]
irq: bool,
#[bits(0..=3, r)]
ist: u4,
}
impl StatusPending {
pub fn interrupt_id(&self) -> Option<CanInterruptId> {
if !self.irq() && self.ist().value() == 0 {
return Some(CanInterruptId::None);
}
if self.irq() && self.ist().value() == 0 {
return Some(CanInterruptId::Error);
}
if !self.irq() {
return None;
}
Some(CanInterruptId::Buffer(self.ist().as_usize() - 1))
}
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct ErrorCounter {
#[bits(0..=7, r)]
transmit: u8,
#[bits(8..=15, r)]
receive: u8,
}
/// This register is unused for standard frames.
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct ExtendedId {
/// Mask for ID bits \[14:0\] of extended frames.
#[bits(1..=15, rw)]
mask_14_0: u15,
/// CAN XRTR bit.
#[bit(0, rw)]
xrtr: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct BaseId {
/// This will contain ID\[10:0\] for standard frames and bits \[28:18\] for extended frames.
#[bits(5..=15, rw)]
mask_28_18: u11,
/// This is the RTR bit for standard frames, and the SRR bit for extended frames.
#[bit(4, rw)]
rtr_or_srr: bool,
/// Identifier extension bit.
#[bit(3, rw)]
ide: bool,
/// Mask for ID bits \[17:15\] of extended frames.
#[bits(0..=2, rw)]
mask_17_15: u3,
}
#[derive(Debug, PartialEq, Eq)]
#[bitbybit::bitenum(u4, exhaustive = true)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ErrorFieldId {
Error = 0b0000,
ErrorDel = 0b0001,
ErrorEcho = 0b0010,
BusIdle = 0b0011,
Ack = 0b0100,
Eof = 0b0101,
Intermission = 0b0110,
SuspendTransmission = 0b0111,
Sof = 0b1000,
Arbitration = 0b1001,
Ide = 0b1010,
ExtendedArbitration = 0b1011,
R1R0 = 0b1100,
Dlc = 0b1101,
Data = 0b1110,
Crc = 0b1111,
}
#[bitbybit::bitfield(u32)]
pub struct DiagnosticRegister {
/// Shows the output value on the CAN TX pin at the time of the error.
#[bit(14, r)]
drive: bool,
/// Shows the bus value on the CAN RX pin as sampled by the CAN module at the time of the
/// error.
#[bit(13, r)]
mon: bool,
/// Indicated whether the CRC is invalid. This bit should only be checked if the EFID field
/// is [ErrorFieldId::Ack].
#[bit(12, r)]
crc: bool,
/// Indicated whether the bit stuffing rule was violated at the time the error occured.
#[bit(11, r)]
stuff: bool,
/// Indicated whether the CAN module was an active transmitter at the time the error occured.
#[bit(10, r)]
txe: bool,
#[bits(4..=9, r)]
ebid: u6,
#[bits(0..=3, r)]
efid: ErrorFieldId,
}
impl core::fmt::Debug for DiagnosticRegister {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DiagnosticRegister")
.field("efid", &self.efid())
.field("ebid", &self.ebid())
.field("txe", &self.txe())
.field("stuff", &self.stuff())
.field("crc", &self.crc())
.field("mon", &self.mon())
.field("drive", &self.drive())
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for DiagnosticRegister {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"DiagnosticRegister {{ efid: {}, ebid: {}, txe: {}, stuff: {}, crc: {}, mon: {}, drive: {} }}",
self.efid(),
self.ebid(),
self.txe(),
self.stuff(),
self.crc(),
self.mon(),
self.drive()
)
}
}
#[derive(derive_mmio::Mmio)]
#[mmio(const_inner)]
#[repr(C)]
pub struct Can {
#[mmio(inner)]
cmbs: [CanMsgBuf; 15],
/// Hidden CAN message buffer. Only allowed to be used internally by the peripheral.
#[mmio(inner)]
_hcmb: CanMsgBuf,
control: Control,
timing: TimingConfig,
/// Global mask extension used for buffers 0 to 13.
gmskx: ExtendedId,
/// Global mask base used for buffers 0 to 13.
gmskb: BaseId,
/// Basic mask extension used for buffer 14.
bmskx: ExtendedId,
/// Basic mask base used for buffer 14.
bmskb: BaseId,
ien: InterruptEnable,
#[mmio(PureRead)]
ipnd: InterruptPending,
#[mmio(Write)]
iclr: InterruptClear,
/// Interrupt Code Enable Register.
icen: InterruptEnable,
#[mmio(PureRead)]
status_pending: StatusPending,
#[mmio(PureRead)]
error_counter: ErrorCounter,
#[mmio(PureRead)]
diag: DiagnosticRegister,
#[mmio(PureRead)]
timer: u32,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Can>(), 0x238);
impl Can {
/// Create a new CAN MMIO instance for peripheral 0.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_0() -> MmioCan<'static> {
Self::new_mmio_at(CAN_0_BASE)
}
/// Create a new CAN MMIO instance for peripheral 1.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_1() -> MmioCan<'static> {
Self::new_mmio_at(CAN_1_BASE)
}
}

View File

@ -1,61 +1,27 @@
//! API for using the [crate::pac::Clkgen] peripheral.
//!
//! It also includes functionality to enable the peripheral clocks.
//! Calling [ClkgenExt::constrain] on the [crate::pac::Clkgen] peripheral generates the
//! [ClkgenCfgr] structure which can be used to configure and set up the clock.
//! Calling [ClockConfigurator::new] returns a builder structure which allows
//! setting up the clock.
//!
//! Calling [ClkgenCfgr::freeze] returns the frozen clock configuration inside the [Clocks]
//! Calling [ClockConfigurator::freeze] returns the frozen clock configuration inside the [Clocks]
//! structure. This structure can also be used to configure other structures provided by this HAL.
//!
//! # 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::time::Hertz;
pub use vorago_shared_periphs::clock::{Clocks, HBO_FREQ};
use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
pub const XTAL_OSC_TSTART_MS: u32 = 15;
#[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 +33,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))]
@ -220,12 +111,12 @@ pub fn pll_setup_delay() {
}
pub trait ClkgenExt {
fn constrain(self) -> ClkgenCfgr;
fn constrain(self) -> ClockConfigurator;
}
impl ClkgenExt for pac::Clkgen {
fn constrain(self) -> ClkgenCfgr {
ClkgenCfgr {
fn constrain(self) -> ClockConfigurator {
ClockConfigurator {
source_clk: None,
ref_clk_sel: RefClkSel::None,
clksel_sys: ClkselSys::Hbo,
@ -238,21 +129,6 @@ impl ClkgenExt for pac::Clkgen {
}
}
pub struct ClkgenCfgr {
ref_clk_sel: RefClkSel,
clksel_sys: ClkselSys,
clk_div_sel: ClkDivSel,
/// The source clock frequency which is either an external clock connected to XTAL_N, or a
/// crystal connected to the XTAL_OSC input.
source_clk: Option<Hertz>,
pll_cfg: Option<PllCfg>,
clk_lost_detection: bool,
/// Feature only works on revision B of the board.
#[cfg(feature = "revb")]
pll_lock_lost_detection: bool,
clkgen: pac::Clkgen,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ClkSourceFreqNotSet;
@ -266,6 +142,21 @@ pub enum ClkCfgError {
InconsistentCfg,
}
pub struct ClockConfigurator {
ref_clk_sel: RefClkSel,
clksel_sys: ClkselSys,
clk_div_sel: ClkDivSel,
/// The source clock frequency which is either an external clock connected to XTAL_N, or a
/// crystal connected to the XTAL_OSC input.
source_clk: Option<Hertz>,
pll_cfg: Option<PllCfg>,
clk_lost_detection: bool,
/// Feature only works on revision B of the board.
#[cfg(feature = "revb")]
pll_lock_lost_detection: bool,
clkgen: pac::Clkgen,
}
/// Delays a given amount of milliseconds.
///
/// Taken from the HAL implementation. This implementation is probably not precise and it
@ -280,7 +171,30 @@ pub fn hbo_clock_delay_ms(ms: u32) {
}
}
impl ClkgenCfgr {
impl ClockConfigurator {
/// Create a new clock configuration instance.
pub fn new(clkgen: pac::Clkgen) -> Self {
ClockConfigurator {
source_clk: None,
ref_clk_sel: RefClkSel::None,
clksel_sys: ClkselSys::Hbo,
clk_div_sel: ClkDivSel::Div1,
clk_lost_detection: false,
pll_lock_lost_detection: false,
pll_cfg: None,
clkgen,
}
}
/// Steals a new [ClockConfigurator] instance.
///
/// # Safety
///
/// Circumvents HAL ownership rules.
pub unsafe fn steal() -> Self {
Self::new(unsafe { pac::Clkgen::steal() })
}
#[inline]
pub fn source_clk(mut self, src_clk: Hertz) -> Self {
self.source_clk = Some(src_clk);
@ -310,6 +224,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 +237,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
@ -325,7 +245,7 @@ impl ClkgenCfgr {
/// might have had a reason for those, so I am going to keep them. Chances are, this
/// process only has to be performed once, and it does not matter if it takes a few
/// microseconds or milliseconds longer.
pub fn freeze(self, syscfg: &mut pac::Sysconfig) -> Result<Clocks, ClkCfgError> {
pub fn freeze(self) -> Result<Clocks, ClkCfgError> {
// Sanitize configuration.
if self.source_clk.is_none() {
return Err(ClkCfgError::ClkSourceFreqNotSet);
@ -340,7 +260,7 @@ impl ClkgenCfgr {
return Err(ClkCfgError::PllConfigNotSet);
}
syscfg.enable_peripheral_clock(PeripheralSelect::Clkgen);
enable_peripheral_clock(PeripheralSelect::Clkgen);
let mut final_sysclk = self.source_clk.unwrap();
// The HAL forces back the HBO clock here with a delay.. Even though this is
// not stricly necessary when coming from a fresh start, it could be still become relevant
@ -424,7 +344,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 +369,20 @@ impl ClkgenCfgr {
.ctrl0()
.modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
Ok(Clocks::__new(
final_sysclk,
#[cfg(not(feature = "va41628"))]
self.cfg_adc_clk_div(final_sysclk),
))
}
#[cfg(not(feature = "va41628"))]
fn cfg_adc_clk_div(&self, final_sysclk: Hertz) -> Hertz {
// I will just do the ADC stuff like Vorago does it.
// 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,60 +392,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,
})
}
}
/// Frozen clock frequencies
///
/// The existence of this value indicates that the clock configuration can no longer be changed.
/// The [self] module documentation gives some more information on how to retrieve an instance
/// of this structure.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Clocks {
sysclk: Hertz,
apb1: Hertz,
apb2: Hertz,
adc_clk: Hertz,
}
impl Clocks {
/// Returns the frequency of the HBO clock
pub fn hbo(&self) -> Hertz {
HBO_FREQ
}
/// Returns the frequency of the APB0 which is equal to the system clock.
pub fn apb0(&self) -> Hertz {
self.sysclk()
}
/// Returns system clock divied by 2.
pub fn apb1(&self) -> Hertz {
self.apb1
}
/// Returns system clock divied by 4.
pub fn apb2(&self) -> Hertz {
self.apb2
}
/// Returns the system (core) frequency
pub fn sysclk(&self) -> Hertz {
self.sysclk
}
/// Returns the ADC clock frequency which has a separate divider.
pub fn adc_clk(&self) -> Hertz {
self.adc_clk
}
}
}

View File

@ -1,21 +1,28 @@
//! 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 vorago_shared_periphs::{
disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
PeripheralSelect,
};
use crate::{clock::Clocks, pac};
pub type DacRegisterBlock = pac::dac0::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all DAC blocks.
pub trait Instance: Deref<Target = DacRegisterBlock> {
pub trait DacMarker: Deref<Target = DacRegisterBlock> {
const IDX: u8;
fn ptr() -> *const DacRegisterBlock;
}
impl Instance for pac::Dac0 {
impl DacMarker for pac::Dac0 {
const IDX: u8 = 0;
#[inline(always)]
@ -24,7 +31,7 @@ impl Instance for pac::Dac0 {
}
}
impl Instance for pac::Dac1 {
impl DacMarker for pac::Dac1 {
const IDX: u8 = 1;
#[inline(always)]
@ -45,40 +52,37 @@ pub enum DacSettling {
Apb2Times150 = 6,
}
pub struct Dac<DacInstance> {
dac: DacInstance,
}
pub struct Dac(*const DacRegisterBlock);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueTooLarge;
impl<DacInstance: Instance> Dac<DacInstance> {
impl Dac {
/// Create a new [Dac] driver instance.
///
/// The [Clocks] structure is expected here as well to ensure the clock was set up properly.
pub fn new(
syscfg: &mut pac::Sysconfig,
dac: DacInstance,
dac_settling: DacSettling,
_clocks: &Clocks,
) -> Self {
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
pub fn new<Dac: DacMarker>(dac: Dac, dac_settling: DacSettling, _clocks: &Clocks) -> Self {
enable_peripheral_clock(PeripheralSelect::Dac);
dac.ctrl1().write(|w| {
w.dac_en().set_bit();
// SAFETY: Enum values are valid values only.
unsafe { w.dac_settling().bits(dac_settling as u8) }
});
let dac = Self { dac };
let mut dac = Self(Dac::ptr());
dac.clear_fifo();
dac.clear_irqs();
dac
}
pub const fn regs(&self) -> &DacRegisterBlock {
unsafe { &*self.0 }
}
#[inline(always)]
pub fn clear_irqs(&self) {
self.dac.irq_clr().write(|w| {
pub fn clear_irqs(&mut self) {
self.regs().irq_clr().write(|w| {
w.fifo_oflow().set_bit();
w.fifo_uflow().set_bit();
w.dac_done().set_bit();
@ -87,31 +91,30 @@ impl<DacInstance: Instance> Dac<DacInstance> {
}
#[inline(always)]
pub fn clear_fifo(&self) {
self.dac.fifo_clr().write(|w| unsafe { w.bits(1) });
pub fn clear_fifo(&mut self) {
self.regs().fifo_clr().write(|w| unsafe { w.bits(1) });
}
/// Load next value into the FIFO.
///
/// Uses the [nb] API to allow blocking and non-blocking usage.
#[inline(always)]
pub fn load_value(&self, val: u16) -> nb::Result<(), ValueTooLarge> {
pub fn load_value(&mut self, val: u16) -> nb::Result<(), ValueTooLarge> {
if val > 2_u16.pow(12) - 1 {
return Err(nb::Error::Other(ValueTooLarge));
}
if self.dac.status().read().fifo_entry_cnt().bits() >= 32_u8 {
let regs = self.regs();
if regs.status().read().fifo_entry_cnt().bits() >= 32_u8 {
return Err(nb::Error::WouldBlock);
}
self.dac
.fifo_data()
.write(|w| unsafe { w.bits(val.into()) });
regs.fifo_data().write(|w| unsafe { w.bits(val.into()) });
Ok(())
}
/// This loads and triggers the next value immediately. It also clears the FIFO before
/// loading the passed value.
#[inline(always)]
pub fn load_and_trigger_manually(&self, val: u16) -> Result<(), ValueTooLarge> {
pub fn load_and_trigger_manually(&mut self, val: u16) -> Result<(), ValueTooLarge> {
if val > 2_u16.pow(12) - 1 {
return Err(ValueTooLarge);
}
@ -127,31 +130,30 @@ impl<DacInstance: Instance> Dac<DacInstance> {
/// to be processed by the DAC.
#[inline(always)]
pub fn trigger_manually(&self) {
self.dac.ctrl0().write(|w| w.man_trig_en().set_bit());
self.regs().ctrl0().write(|w| w.man_trig_en().set_bit());
}
#[inline(always)]
pub fn enable_external_trigger(&self) {
self.dac.ctrl0().write(|w| w.ext_trig_en().set_bit());
self.regs().ctrl0().write(|w| w.ext_trig_en().set_bit());
}
pub fn is_settled(&self) -> nb::Result<(), ()> {
if self.dac.status().read().dac_busy().bit_is_set() {
if self.regs().status().read().dac_busy().bit_is_set() {
return Err(nb::Error::WouldBlock);
}
Ok(())
}
#[inline(always)]
pub fn reset(&mut self, syscfg: &mut pac::Sysconfig) {
syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dac);
pub fn reset(&mut self) {
enable_peripheral_clock(PeripheralSelect::Dac);
reset_peripheral_for_cycles(PeripheralSelect::Dac, 2);
}
/// Relases the DAC, which also disables its peripheral clock.
/// Stops the DAC, which disables its peripheral clock.
#[inline(always)]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> DacInstance {
syscfg.disable_peripheral_clock(PeripheralSelect::Dac);
self.dac
pub fn stop(self) {
disable_peripheral_clock(PeripheralSelect::Dac);
}
}

View File

@ -3,23 +3,23 @@
//! ## Examples
//!
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
use embedded_dma::WriteBuffer;
use crate::{
clock::{PeripheralClock, PeripheralSelect},
enable_interrupt, pac,
prelude::*,
use arbitrary_int::{u10, u3};
use vorago_shared_periphs::{
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
};
use crate::{enable_nvic_interrupt, pac};
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)]
#[bitbybit::bitenum(u3, exhaustive = true)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CycleControl {
/// Indicates that the data structure is invalid.
Stop = 0b000,
@ -44,7 +44,8 @@ pub enum CycleControl {
PeriphScatterGatherAlternate = 0b111,
}
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddrIncrement {
Byte = 0b00,
@ -53,7 +54,8 @@ pub enum AddrIncrement {
None = 0b11,
}
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u2, exhaustive = false)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DataSize {
Byte = 0b00,
@ -62,7 +64,8 @@ pub enum DataSize {
}
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
#[derive(Debug, Clone, Copy)]
#[bitbybit::bitenum(u4, exhaustive = true)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RPower {
EachTransfer = 0b0000,
@ -75,16 +78,24 @@ pub enum RPower {
Every128 = 0b0111,
Every256 = 0b1000,
Every512 = 0b1001,
Every1024Min = 0b1010,
Every1024 = 0b1111,
Every1024 = 0b1010,
Every1024Alt0 = 0b1011,
Every1024Alt1 = 0b1100,
Every1024Alt2 = 0b1101,
Every1024Alt3 = 0b1110,
Every1024Alt4 = 0b1111,
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidCtrlBlockAddr;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("Invalid DMA control block address")]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCtrlBlockAddrError;
/*
bitfield::bitfield! {
#[repr(transparent)]
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelConfig(u32);
impl Debug;
u32;
@ -110,9 +121,37 @@ bitfield::bitfield! {
u8;
pub cycle_ctrl, set_cycle_ctr: 2, 0;
}
*/
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChannelConfig {
#[bits(30..=31, rw)]
dst_inc: AddrIncrement,
#[bits(28..=29, rw)]
dst_size: Option<DataSize>,
#[bits(26..=27, rw)]
src_inc: AddrIncrement,
#[bits(24..=25, rw)]
src_size: Option<DataSize>,
#[bits(21..=23, rw)]
dest_prot_ctrl: u3,
#[bits(18..=20, rw)]
src_prot_ctrl: u3,
#[bits(14..=17, rw)]
r_power: RPower,
#[bits(4..=13, rw)]
n_minus_1: u10,
#[bit(3, rw)]
next_useburst: bool,
#[bits(0..=2, rw)]
cycle_ctrl: CycleControl,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaChannelControl {
pub src_end_ptr: u32,
pub dest_end_ptr: u32,
@ -125,7 +164,7 @@ impl DmaChannelControl {
Self {
src_end_ptr: 0,
dest_end_ptr: 0,
cfg: ChannelConfig(0),
cfg: ChannelConfig::new_with_raw_value(0),
padding: 0,
}
}
@ -162,9 +201,9 @@ impl DmaCtrlBlock {
/// 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, InvalidCtrlBlockAddr> {
pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddrError> {
if addr & BASE_PTR_ADDR_MASK > 0 {
return Err(InvalidCtrlBlockAddr);
return Err(InvalidCtrlBlockAddrError);
}
let ctrl_block_ptr = addr as *mut DmaCtrlBlock;
unsafe { core::ptr::write(ctrl_block_ptr, DmaCtrlBlock::default()) }
@ -177,19 +216,21 @@ pub struct Dma {
ctrl_block: *mut DmaCtrlBlock,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaTransferInitError {
SourceDestLenMissmatch {
src_len: usize,
dest_len: usize,
},
#[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,
@ -205,28 +246,6 @@ pub struct DmaChannel {
pub ch_ctrl_alt: &'static mut DmaChannelControl,
}
/// This transfer structure takes ownership of the mutable destination slice.
///
/// This avoids accidental violation of the ownership rules because the DMA now has mutable
/// access to that slice as well. The mutable slice can be retrieved after DMA transfer completion
/// by using the [Self::release] method.
pub struct DmaTransfer<W> {
buf: W,
//ch: DmaChannel
}
impl<W: WriteBuffer> DmaTransfer<W> {
/// Retrieve the mutable destination slice once the DMA transfer has completed.
///
/// # Safety
///
/// - The user MUST ensure that the DMA transfer has completed, for example by polling a
/// completion flag set by the DMA_DONE ISR.
pub unsafe fn release(self) -> W {
self.buf
}
}
impl DmaChannel {
#[inline(always)]
pub fn channel(&self) -> u8 {
@ -284,7 +303,7 @@ impl DmaChannel {
///
/// This function is `unsafe` because it can break mask-based critical sections.
pub unsafe fn enable_done_interrupt(&mut self) {
enable_interrupt(self.done_interrupt);
enable_nvic_interrupt(self.done_interrupt);
}
/// Enables the DMA_ACTIVE interrupt for the DMA channel.
@ -293,7 +312,7 @@ impl DmaChannel {
///
/// This function is `unsafe` because it can break mask-based critical sections.
pub unsafe fn enable_active_interrupt(&mut self) {
enable_interrupt(self.active_interrupt);
enable_nvic_interrupt(self.active_interrupt);
}
/// Prepares a 8-bit DMA transfer from memory to memory.
@ -305,25 +324,35 @@ impl DmaChannel {
/// 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.
pub fn prepare_mem_to_mem_transfer_8_bit<W: WriteBuffer<Word = u8>>(
///
/// # Safety
///
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
/// is safe for DMA reads. The specific requirements can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
pub unsafe fn prepare_mem_to_mem_transfer_8_bit(
&mut self,
source: &[u8],
mut dest: W,
) -> Result<DmaTransfer<W>, DmaTransferInitError> {
let (write_ptr, len) = unsafe { dest.write_buffer() };
let len = Self::common_mem_transfer_checks(source.len(), len)?;
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)?,
(write_ptr as u32)
(dest.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?,
DataSize::Byte,
AddrIncrement::Byte,
);
Ok(DmaTransfer { buf: dest })
Ok(())
}
/// Prepares a 16-bit DMA transfer from memory to memory.
@ -335,10 +364,21 @@ impl DmaChannel {
/// 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.
pub fn prepare_mem_to_mem_transfer_16_bit<'dest>(
///
/// # Safety
///
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
/// is safe for DMA reads. The specific requirements can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
pub unsafe fn prepare_mem_to_mem_transfer_16_bit(
&mut self,
source: &[u16],
dest: &'dest mut [u16],
dest: &mut [u16],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
@ -364,10 +404,21 @@ impl DmaChannel {
/// 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.
pub fn prepare_mem_to_mem_transfer_32_bit<'dest>(
///
/// # Safety
///
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
/// is safe for DMA reads. The specific requirements can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
pub unsafe fn prepare_mem_to_mem_transfer_32_bit(
&mut self,
source: &[u32],
dest: &'dest mut [u32],
dest: &mut [u32],
) -> Result<(), DmaTransferInitError> {
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
self.generic_mem_to_mem_transfer_init(
@ -384,6 +435,52 @@ impl DmaChannel {
Ok(())
}
/// Prepares a 8-bit DMA transfer from memory to a peripheral.
///
/// It is assumed that a peripheral with a 16-byte FIFO is used here and that the
/// transfer is activated by an IRQ trigger when the half-full interrupt of the peripheral
/// is fired. Therefore, this function configured the DMA in [CycleControl::Basic] mode with
/// rearbitration happening every 8 DMA cycles. It also configures the primary channel control
/// structure to perform the transfer.
///
/// # Safety
///
/// You must ensure that the source buffer is safe for DMA reads. The specific requirements
/// can be read here:
///
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
///
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
/// active or until the DMA is stopped.
///
/// The destination address must be the pointer address of a peripheral FIFO register address.
/// You must also ensure that the regular synchronous transfer API of the peripheral is NOT
/// used to perform transfers.
pub unsafe fn prepare_mem_to_periph_transfer_8_bit(
&mut self,
source: &[u8],
dest: *mut u32,
) -> Result<(), DmaTransferInitError> {
if source.len() > MAX_DMA_TRANSFERS_PER_CYCLE {
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
}
let len = source.len() - 1;
self.ch_ctrl_pri.cfg = ChannelConfig::new_with_raw_value(0);
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
.checked_add(len as u32)
.ok_or(DmaTransferInitError::AddrOverflow)?;
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
self.ch_ctrl_pri.cfg.set_cycle_ctrl(CycleControl::Basic);
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte);
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte);
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte);
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None);
self.ch_ctrl_pri.cfg.set_n_minus_1(u10::new(len as u16));
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8);
self.select_primary_structure();
Ok(())
}
// This function performs common checks and returns the source length minus one which is
// 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.
@ -408,16 +505,18 @@ impl DmaChannel {
data_size: DataSize,
addr_incr: AddrIncrement,
) {
self.ch_ctrl_pri.cfg.set_raw(0);
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_ctr(CycleControl::Auto as u8);
self.ch_ctrl_pri.cfg.set_src_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8);
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8);
self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16);
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8);
self.ch_ctrl_pri.cfg.set_cycle_ctrl(CycleControl::Auto);
self.ch_ctrl_pri.cfg.set_src_size(data_size);
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr);
self.ch_ctrl_pri.cfg.set_dst_size(data_size);
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr);
self.ch_ctrl_pri
.cfg
.set_n_minus_1(u10::new(n_minus_one as u16));
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4);
self.select_primary_structure();
}
}
@ -433,18 +532,17 @@ impl Dma {
/// 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, InvalidCtrlBlockAddr> {
) -> 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(InvalidCtrlBlockAddr);
return Err(InvalidCtrlBlockAddrError);
}
syscfg.enable_peripheral_clock(PeripheralClock::Dma);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
enable_peripheral_clock(PeripheralSelect::Dma);
reset_peripheral_for_cycles(PeripheralSelect::Dma, 2);
let dma = Dma { dma, ctrl_block };
dma.dma
.ctrl_base_ptr()

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

@ -1,453 +0,0 @@
use embedded_hal::digital::{ErrorKind, ErrorType, InputPin, OutputPin, StatefulOutputPin};
use super::{
reg::RegisterInterface, FilterClkSel, FilterType, InterruptEdge, InterruptLevel, Pin, PinId,
PinMode, PinState,
};
//==================================================================================================
// DynPinMode configurations
//==================================================================================================
/// Value-level `enum` for disabled configurations
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum DynDisabled {
Floating,
PullDown,
PullUp,
}
/// Value-level `enum` for input configurations
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum DynInput {
Floating,
PullDown,
PullUp,
}
/// Value-level `enum` for output configurations
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum DynOutput {
PushPull,
OpenDrain,
ReadablePushPull,
ReadableOpenDrain,
}
pub type DynAlternate = crate::FunSel;
//==================================================================================================
// DynPinMode
//==================================================================================================
/// Value-level `enum` representing pin modes
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum DynPinMode {
Input(DynInput),
Output(DynOutput),
Alternate(DynAlternate),
}
/// Value-level variant of [`DynPinMode`] for floating input mode
pub const DYN_FLOATING_INPUT: DynPinMode = DynPinMode::Input(DynInput::Floating);
/// Value-level variant of [`DynPinMode`] for pull-down input mode
pub const DYN_PULL_DOWN_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullDown);
/// Value-level variant of [`DynPinMode`] for pull-up input mode
pub const DYN_PULL_UP_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullUp);
/// Value-level variant of [`DynPinMode`] for push-pull output mode
pub const DYN_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::PushPull);
/// Value-level variant of [`DynPinMode`] for open-drain output mode
pub const DYN_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::OpenDrain);
/// Value-level variant of [`DynPinMode`] for readable push-pull output mode
pub const DYN_RD_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadablePushPull);
/// Value-level variant of [`DynPinMode`] for readable opendrain output mode
pub const DYN_RD_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadableOpenDrain);
/// Value-level variant of [`DynPinMode`] for function select 1
pub const DYN_ALT_FUNC_1: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel1);
/// Value-level variant of [`DynPinMode`] for function select 2
pub const DYN_ALT_FUNC_2: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel2);
/// Value-level variant of [`DynPinMode`] for function select 3
pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel3);
//==================================================================================================
// DynGroup & DynPinId
//==================================================================================================
/// Value-level `enum` for pin groups
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum DynGroup {
A,
B,
C,
D,
E,
F,
G,
}
/// Value-level `struct` representing pin IDs
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct DynPinId {
pub group: DynGroup,
pub num: u8,
}
//==============================================================================
// DynRegisters
//==============================================================================
/// Provide a safe register interface for [`DynPin`]s
///
/// This `struct` takes ownership of a [`DynPinId`] and provides an API to
/// access the corresponding regsiters.
struct DynRegisters {
id: DynPinId,
}
// [`DynRegisters`] takes ownership of the [`DynPinId`], and [`DynPin`]
// guarantees that each pin is a singleton, so this implementation is safe.
unsafe impl RegisterInterface for DynRegisters {
#[inline]
fn id(&self) -> DynPinId {
self.id
}
}
impl DynRegisters {
/// Create a new instance of [`DynRegisters`]
///
/// # Safety
///
/// Users must never create two simultaneous instances of this `struct` with
/// the same [`DynPinId`]
#[inline]
unsafe fn new(id: DynPinId) -> Self {
DynRegisters { id }
}
}
//==============================================================================
// Error
//==============================================================================
/// GPIO error type
///
/// [`DynPin`]s are not tracked and verified at compile-time, so run-time
/// operations are fallible. This `enum` represents the corresponding errors.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidPinTypeError(pub(crate) ());
impl embedded_hal::digital::Error for InvalidPinTypeError {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
ErrorKind::Other
}
}
//==================================================================================================
// DynPin
//==================================================================================================
/// A value-level pin, parameterized by [`DynPinId`] and [`DynPinMode`]
///
/// This type acts as a type-erased version of [`Pin`]. Every pin is represented
/// by the same type, and pins are tracked and distinguished at run-time.
pub struct DynPin {
regs: DynRegisters,
mode: DynPinMode,
}
impl DynPin {
/// Create a new [`DynPin`]
///
/// # Safety
///
/// Each [`DynPin`] must be a singleton. For a given [`DynPinId`], there
/// must be at most one corresponding [`DynPin`] in existence at any given
/// time. Violating this requirement is `unsafe`.
#[inline]
unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self {
DynPin {
regs: DynRegisters::new(id),
mode,
}
}
/// Return a copy of the pin ID
#[inline]
pub fn id(&self) -> DynPinId {
self.regs.id
}
/// Return a copy of the pin mode
#[inline]
pub fn mode(&self) -> DynPinMode {
self.mode
}
/// Convert the pin to the requested [`DynPinMode`]
#[inline]
pub fn into_mode(&mut self, mode: DynPinMode) {
// Only modify registers if we are actually changing pin mode
if mode != self.mode {
self.regs.change_mode(mode);
self.mode = mode;
}
}
#[inline]
pub fn into_funsel_1(&mut self) {
self.into_mode(DYN_ALT_FUNC_1);
}
#[inline]
pub fn into_funsel_2(&mut self) {
self.into_mode(DYN_ALT_FUNC_2);
}
#[inline]
pub fn into_funsel_3(&mut self) {
self.into_mode(DYN_ALT_FUNC_3);
}
/// Configure the pin to operate as a floating input
#[inline]
pub fn into_floating_input(&mut self) {
self.into_mode(DYN_FLOATING_INPUT);
}
/// Configure the pin to operate as a pulled down input
#[inline]
pub fn into_pull_down_input(&mut self) {
self.into_mode(DYN_PULL_DOWN_INPUT);
}
/// Configure the pin to operate as a pulled up input
#[inline]
pub fn into_pull_up_input(&mut self) {
self.into_mode(DYN_PULL_UP_INPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_push_pull_output(&mut self) {
self.into_mode(DYN_PUSH_PULL_OUTPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_open_drain_output(&mut self) {
self.into_mode(DYN_OPEN_DRAIN_OUTPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_readable_push_pull_output(&mut self) {
self.into_mode(DYN_RD_PUSH_PULL_OUTPUT);
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_readable_open_drain_output(&mut self) {
self.into_mode(DYN_RD_OPEN_DRAIN_OUTPUT);
}
common_reg_if_functions!();
/// See p.53 of the programmers guide for more information.
/// Possible delays in clock cycles:
/// - Delay 1: 1
/// - Delay 2: 2
/// - Delay 1 + Delay 2: 3
#[inline]
pub fn delay(self, delay_1: bool, delay_2: bool) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.regs.delay(delay_1, delay_2);
Ok(self)
}
_ => Err(InvalidPinTypeError(())),
}
}
/// See p.52 of the programmers guide for more information.
/// When configured for pulse mode, a given pin will set the non-default state for exactly
/// one clock cycle before returning to the configured default state
pub fn pulse_mode(
self,
enable: bool,
default_state: PinState,
) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.regs.pulse_mode(enable, default_state);
Ok(self)
}
_ => Err(InvalidPinTypeError(())),
}
}
/// See p.37 and p.38 of the programmers guide for more information.
#[inline]
pub fn filter_type(
self,
filter: FilterType,
clksel: FilterClkSel,
) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) => {
self.regs.filter_type(filter, clksel);
Ok(self)
}
_ => Err(InvalidPinTypeError(())),
}
}
pub fn interrupt_edge(mut self, edge_type: InterruptEdge) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.regs.interrupt_edge(edge_type);
self.irq_enb();
Ok(self)
}
_ => Err(InvalidPinTypeError(())),
}
}
pub fn interrupt_level(
mut self,
level_type: InterruptLevel,
) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.regs.interrupt_level(level_type);
self.irq_enb();
Ok(self)
}
_ => Err(InvalidPinTypeError(())),
}
}
#[inline]
fn _read(&self) -> Result<bool, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => {
Ok(self.regs.read_pin())
}
_ => Err(InvalidPinTypeError(())),
}
}
#[inline]
fn _write(&mut self, bit: bool) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.regs.write_pin(bit);
Ok(())
}
_ => Err(InvalidPinTypeError(())),
}
}
#[inline]
fn _is_low(&self) -> Result<bool, InvalidPinTypeError> {
self._read().map(|v| !v)
}
#[inline]
fn _is_high(&self) -> Result<bool, InvalidPinTypeError> {
self._read()
}
#[inline]
fn _set_low(&mut self) -> Result<(), InvalidPinTypeError> {
self._write(false)
}
#[inline]
fn _set_high(&mut self) -> Result<(), InvalidPinTypeError> {
self._write(true)
}
}
//==============================================================================
// Convert between Pin and DynPin
//==============================================================================
impl<I, M> From<Pin<I, M>> for DynPin
where
I: PinId,
M: PinMode,
{
/// Erase the type-level information in a [`Pin`] and return a value-level
/// [`DynPin`]
#[inline]
fn from(_pin: Pin<I, M>) -> Self {
// The `Pin` is consumed, so it is safe to replace it with the
// corresponding `DynPin`
unsafe { DynPin::new(I::DYN, M::DYN) }
}
}
impl<I, M> TryFrom<DynPin> for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Error = InvalidPinTypeError;
/// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
///
/// There is no way for the compiler to know if the conversion will be
/// successful at compile-time. We must verify the conversion at run-time
/// or refuse to perform it.
#[inline]
fn try_from(pin: DynPin) -> Result<Self, Self::Error> {
if pin.regs.id == I::DYN && pin.mode == M::DYN {
// The `DynPin` is consumed, so it is safe to replace it with the
// corresponding `Pin`
Ok(unsafe { Self::new() })
} else {
Err(InvalidPinTypeError(()))
}
}
}
//==============================================================================
// Embedded HAL v1 traits
//==============================================================================
impl ErrorType for DynPin {
type Error = InvalidPinTypeError;
}
impl OutputPin for DynPin {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self._set_high()
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self._set_low()
}
}
impl InputPin for DynPin {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
self._is_high()
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
self._is_low()
}
}
impl StatefulOutputPin for DynPin {
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
self._is_high()
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
self._is_low()
}
}

View File

@ -1,82 +1,20 @@
//! # API for the GPIO peripheral
//! # GPIO support module.
//!
//! The implementation of this GPIO module is heavily based on the
//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/index.html).
//! Contains abstractions to use the pins provided by the [crate::pins] module as GPIO or
//! IO peripheral pins.
//!
//! This API provides two different submodules, [pin] and [dynpin],
//! representing two different ways to handle GPIO pins. The default, [pin],
//! is a type-level API that tracks the state of each pin at compile-time. The
//! alternative, [dynpin] is a type-erased, value-level API that tracks the
//! state of each pin at run-time.
//! The core data structures provided for this are the
//!
//! The type-level API is strongly preferred. By representing the state of each
//! pin within the type system, the compiler can detect logic errors at
//! compile-time. Furthermore, the type-level API has absolutely zero run-time
//! cost.
//! - [Output] for push-pull output pins.
//! - [Input] for input pins.
//! - [Flex] for pins with flexible configuration requirements.
//! - [IoPeriphPin] for IO peripheral pins.
//!
//! If needed, [dynpin] can be used to erase the type-level differences
//! between pins. However, by doing so, pins must now be tracked at run-time,
//! and each pin has a non-zero memory footprint.
//! The [crate::pins] module exposes singletons to access the [Pin]s required by this module
//! in a type-safe way.
//!
//! ## Examples
//! # Examples
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IsMaskedError;
macro_rules! common_reg_if_functions {
() => {
paste::paste!(
#[inline]
pub fn datamask(&self) -> bool {
self.regs.datamask()
}
#[inline]
pub fn clear_datamask(self) -> Self {
self.regs.clear_datamask();
self
}
#[inline]
pub fn set_datamask(self) -> Self {
self.regs.set_datamask();
self
}
#[inline]
pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.regs.read_pin_masked()
}
#[inline]
pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
self.regs.read_pin_masked().map(|v| !v)
}
#[inline]
pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.regs.write_pin_masked(true)
}
#[inline]
pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
self.regs.write_pin_masked(false)
}
fn irq_enb(&mut self) {
self.regs.enable_irq();
}
);
};
}
pub mod pin;
pub use pin::*;
pub mod dynpin;
pub use dynpin::*;
mod reg;
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
pub use vorago_shared_periphs::gpio::*;

View File

@ -1,911 +0,0 @@
//! # Type-level module for GPIO pins
//!
//! This documentation is strongly based on the
//! [atsamd documentation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/pin/index.html).
//!
//! This module provides a type-level API for GPIO pins. It uses the type system
//! to track the state of pins at compile-time. Representing GPIO pins in this
//! manner incurs no run-time overhead. Each [`Pin`] struct is zero-sized, so
//! there is no data to copy around. Instead, real code is generated as a side
//! effect of type transformations, and the resulting assembly is nearly
//! identical to the equivalent, hand-written C.
//!
//! To track the state of pins at compile-time, this module uses traits to
//! represent [type classes] and types as instances of those type classes. For
//! example, the trait [`InputConfig`] acts as a [type-level enum] of the
//! available input configurations, and the types [`Floating`], [`PullDown`] and
//! [`PullUp`] are its type-level variants.
//!
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
//! [`PinMode`].
//!
//! ```
//! pub struct Pin<I, M>
//! where
//! I: PinId,
//! M: PinMode,
//! {
//! // ...
//! }
//! ```
//!
//! A `PinId` identifies a pin by it's group (A to G) and pin number. Each
//! `PinId` instance is named according to its datasheet identifier, e.g.
//! [PA2].
//!
//! A `PinMode` represents the various pin modes. The available `PinMode`
//! variants are [`Input`], [`Output`] and [`Alternate`], each with its own
//! corresponding configurations.
//!
//! It is not possible for users to create new instances of a [`Pin`]. Singleton
//! instances of each pin are made available to users through the PinsX
//! struct.
//!
//! Example for the pins of PORT A:
//!
//! To create the [PinsA] struct, users must supply the PAC
//! [Port](crate::pac::Porta) peripheral. The [PinsA] struct takes
//! ownership of the [Porta] and provides the corresponding pins. Each [`Pin`]
//! within the [PinsA] struct can be moved out and used individually.
//!
//!
//! ```no_run
//! let mut peripherals = Peripherals::take().unwrap();
//! let pinsa = PinsA::new(peripherals.porta);
//! ```
//!
//! Pins can be converted between modes using several different methods.
//!
//! ```no_run
//! // Use one of the literal function names
//! let pa0 = pinsa.pa0.into_floating_input();
//! // Use a generic method and one of the `PinMode` variant types
//! let pa0 = pinsa.pa0.into_mode::<FloatingInput>();
//! // Specify the target type and use `From`/`Into`
//! let pa0: Pin<PA0, FloatingInput> = pinsa.pa27.into();
//! ```
//!
//! # Embedded HAL traits
//!
//! This module implements all of the embedded HAL GPIO traits for each [`Pin`]
//! in the corresponding [`PinMode`]s, namely: [`InputPin`], [`OutputPin`],
//! and [`StatefulOutputPin`].
use core::{convert::Infallible, marker::PhantomData, mem::transmute};
pub use crate::clock::FilterClkSel;
use crate::typelevel::Sealed;
use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin};
use va416xx::{Porta, Portb, Portc, Portd, Porte, Portf, Portg};
use super::{
reg::RegisterInterface, DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode,
};
//==================================================================================================
// Errors and Definitions
//==================================================================================================
#[derive(Debug, PartialEq, Eq)]
pub enum InterruptEdge {
HighToLow,
LowToHigh,
BothEdges,
}
#[derive(Debug, PartialEq, Eq)]
pub enum InterruptLevel {
Low = 0,
High = 1,
}
#[derive(Debug, PartialEq, Eq)]
pub enum PinState {
Low = 0,
High = 1,
}
//==================================================================================================
// Input configuration
//==================================================================================================
/// Type-level enum for input configurations
///
/// The valid options are [Floating], [PullDown] and [PullUp].
pub trait InputConfig: Sealed {
/// Corresponding [DynInput]
const DYN: DynInput;
}
pub enum Floating {}
pub enum PullDown {}
pub enum PullUp {}
impl InputConfig for Floating {
const DYN: DynInput = DynInput::Floating;
}
impl InputConfig for PullDown {
const DYN: DynInput = DynInput::PullDown;
}
impl InputConfig for PullUp {
const DYN: DynInput = DynInput::PullUp;
}
impl Sealed for Floating {}
impl Sealed for PullDown {}
impl Sealed for PullUp {}
/// Type-level variant of [PinMode] for floating input mode
pub type InputFloating = Input<Floating>;
/// Type-level variant of [PinMode] for pull-down input mode
pub type InputPullDown = Input<PullDown>;
/// Type-level variant of [PinMode] for pull-up input mode
pub type InputPullUp = Input<PullUp>;
/// Type-level variant of [PinMode] for input modes
///
/// Type `C` is one of three input configurations: [Floating], [PullDown] or
/// [PullUp]
pub struct Input<C: InputConfig> {
cfg: PhantomData<C>,
}
impl<C: InputConfig> Sealed for Input<C> {}
#[derive(Debug, PartialEq, Eq)]
pub enum FilterType {
SystemClock = 0,
DirectInputWithSynchronization = 1,
FilterOneClockCycle = 2,
FilterTwoClockCycles = 3,
FilterThreeClockCycles = 4,
FilterFourClockCycles = 5,
}
//==================================================================================================
// Output configuration
//==================================================================================================
pub trait OutputConfig: Sealed {
const DYN: DynOutput;
}
pub trait ReadableOutput: Sealed {}
/// Type-level variant of [`OutputConfig`] for a push-pull configuration
pub enum PushPull {}
/// Type-level variant of [`OutputConfig`] for an open drain configuration
pub enum OpenDrain {}
/// Type-level variant of [`OutputConfig`] for a readable push-pull configuration
pub enum ReadablePushPull {}
/// Type-level variant of [`OutputConfig`] for a readable open-drain configuration
pub enum ReadableOpenDrain {}
impl Sealed for PushPull {}
impl Sealed for OpenDrain {}
impl Sealed for ReadableOpenDrain {}
impl Sealed for ReadablePushPull {}
impl ReadableOutput for ReadableOpenDrain {}
impl ReadableOutput for ReadablePushPull {}
impl OutputConfig for PushPull {
const DYN: DynOutput = DynOutput::PushPull;
}
impl OutputConfig for OpenDrain {
const DYN: DynOutput = DynOutput::OpenDrain;
}
impl OutputConfig for ReadablePushPull {
const DYN: DynOutput = DynOutput::ReadablePushPull;
}
impl OutputConfig for ReadableOpenDrain {
const DYN: DynOutput = DynOutput::ReadableOpenDrain;
}
/// Type-level variant of [`PinMode`] for output modes
///
/// Type `C` is one of four output configurations: [`PushPull`], [`OpenDrain`] or
/// their respective readable versions
pub struct Output<C: OutputConfig> {
cfg: PhantomData<C>,
}
impl<C: OutputConfig> Sealed for Output<C> {}
/// Type-level variant of [`PinMode`] for push-pull output mode
pub type PushPullOutput = Output<PushPull>;
/// Type-level variant of [`PinMode`] for open drain output mode
pub type OutputOpenDrain = Output<OpenDrain>;
pub type OutputReadablePushPull = Output<ReadablePushPull>;
pub type OutputReadableOpenDrain = Output<ReadableOpenDrain>;
//==================================================================================================
// Alternate configurations
//==================================================================================================
/// Type-level enum for alternate peripheral function configurations
pub trait AlternateConfig: Sealed {
const DYN: DynAlternate;
}
pub enum Funsel1 {}
pub enum Funsel2 {}
pub enum Funsel3 {}
impl AlternateConfig for Funsel1 {
const DYN: DynAlternate = DynAlternate::Sel1;
}
impl AlternateConfig for Funsel2 {
const DYN: DynAlternate = DynAlternate::Sel2;
}
impl AlternateConfig for Funsel3 {
const DYN: DynAlternate = DynAlternate::Sel3;
}
impl Sealed for Funsel1 {}
impl Sealed for Funsel2 {}
impl Sealed for Funsel3 {}
/// Type-level variant of [`PinMode`] for alternate peripheral functions
///
/// Type `C` is an [`AlternateConfig`]
pub struct Alternate<C: AlternateConfig> {
cfg: PhantomData<C>,
}
impl<C: AlternateConfig> Sealed for Alternate<C> {}
pub type AltFunc1 = Alternate<Funsel1>;
pub type AltFunc2 = Alternate<Funsel2>;
pub type AltFunc3 = Alternate<Funsel3>;
/// Type alias for the [`PinMode`] at reset
pub type Reset = InputFloating;
//==================================================================================================
// Pin modes
//==================================================================================================
/// Type-level enum representing pin modes
///
/// The valid options are [Input], [Output] and [Alternate].
pub trait PinMode: Sealed {
/// Corresponding [DynPinMode]
const DYN: DynPinMode;
}
impl<C: InputConfig> PinMode for Input<C> {
const DYN: DynPinMode = DynPinMode::Input(C::DYN);
}
impl<C: OutputConfig> PinMode for Output<C> {
const DYN: DynPinMode = DynPinMode::Output(C::DYN);
}
impl<C: AlternateConfig> PinMode for Alternate<C> {
const DYN: DynPinMode = DynPinMode::Alternate(C::DYN);
}
//==================================================================================================
// Pin IDs
//==================================================================================================
/// Type-level enum for pin IDs
pub trait PinId: Sealed {
/// Corresponding [DynPinId]
const DYN: DynPinId;
}
macro_rules! pin_id {
($Group:ident, $Id:ident, $NUM:literal) => {
// Need paste macro to use ident in doc attribute
paste::paste! {
#[doc = "Pin ID representing pin " $Id]
pub enum $Id {}
impl Sealed for $Id {}
impl PinId for $Id {
const DYN: DynPinId = DynPinId {
group: DynGroup::$Group,
num: $NUM,
};
}
}
};
}
//==================================================================================================
// Pin
//==================================================================================================
/// A type-level GPIO pin, parameterized by [`PinId`] and [`PinMode`] types
pub struct Pin<I: PinId, M: PinMode> {
pub(in crate::gpio) regs: Registers<I>,
mode: PhantomData<M>,
}
impl<I: PinId, M: PinMode> Pin<I, M> {
/// Create a new [`Pin`]
///
/// # Safety
///
/// Each [`Pin`] must be a singleton. For a given [`PinId`], there must be
/// at most one corresponding [`Pin`] in existence at any given time.
/// Violating this requirement is `unsafe`.
#[inline]
pub(crate) unsafe fn new() -> Pin<I, M> {
Pin {
regs: Registers::new(),
mode: PhantomData,
}
}
/// Convert the pin to the requested [`PinMode`]
#[inline]
pub fn into_mode<N: PinMode>(mut self) -> Pin<I, N> {
// Only modify registers if we are actually changing pin mode
// This check should compile away
if N::DYN != M::DYN {
self.regs.change_mode::<N>();
}
// Safe because we drop the existing Pin
unsafe { Pin::new() }
}
/// Configure the pin for function select 1. See Programmer Guide p.40 for the function table
#[inline]
pub fn into_funsel_1(self) -> Pin<I, AltFunc1> {
self.into_mode()
}
/// Configure the pin for function select 2. See Programmer Guide p.40 for the function table
#[inline]
pub fn into_funsel_2(self) -> Pin<I, AltFunc2> {
self.into_mode()
}
/// Configure the pin for function select 3. See Programmer Guide p.40 for the function table
#[inline]
pub fn into_funsel_3(self) -> Pin<I, AltFunc3> {
self.into_mode()
}
/// Configure the pin to operate as a floating input
#[inline]
pub fn into_floating_input(self) -> Pin<I, InputFloating> {
self.into_mode()
}
/// Configure the pin to operate as a pulled down input
#[inline]
pub fn into_pull_down_input(self) -> Pin<I, InputPullDown> {
self.into_mode()
}
/// Configure the pin to operate as a pulled up input
#[inline]
pub fn into_pull_up_input(self) -> Pin<I, InputPullUp> {
self.into_mode()
}
/// Configure the pin to operate as a push-pull output
#[inline]
pub fn into_push_pull_output(self) -> Pin<I, PushPullOutput> {
self.into_mode()
}
/// Configure the pin to operate as a readable push-pull output
#[inline]
pub fn into_readable_push_pull_output(self) -> Pin<I, OutputReadablePushPull> {
self.into_mode()
}
/// Configure the pin to operate as a readable open-drain output
#[inline]
pub fn into_readable_open_drain_output(self) -> Pin<I, OutputReadableOpenDrain> {
self.into_mode()
}
common_reg_if_functions!();
#[inline]
pub(crate) fn _set_high(&mut self) {
self.regs.write_pin(true)
}
#[inline]
pub(crate) fn _set_low(&mut self) {
self.regs.write_pin(false)
}
#[inline]
pub(crate) fn _is_low(&self) -> bool {
!self.regs.read_pin()
}
#[inline]
pub(crate) fn _is_high(&self) -> bool {
self.regs.read_pin()
}
}
//==============================================================================
// AnyPin
//==============================================================================
/// Type class for [`Pin`] types
///
/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for
/// [`Pin`] types. See the `AnyKind` documentation for more details on the
/// pattern.
///
/// ## `v1` Compatibility
///
/// Normally, this trait would use `Is<Type = SpecificPin<Self>>` as a super
/// trait. But doing so would restrict implementations to only the `v2` `Pin`
/// type in this module. To aid in backwards compatibility, we want to implement
/// `AnyPin` for the `v1` `Pin` type as well. This is possible for a few
/// reasons. First, both structs are zero-sized, so there is no meaningful
/// memory layout to begin with. And even if there were, the `v1` `Pin` type is
/// a newtype wrapper around a `v2` `Pin`, and single-field structs are
/// guaranteed to have the same layout as the field, even for `repr(Rust)`.
///
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
/// [type class]: crate::typelevel#type-classes
pub trait AnyPin
where
Self: Sealed,
Self: From<SpecificPin<Self>>,
Self: Into<SpecificPin<Self>>,
Self: AsRef<SpecificPin<Self>>,
Self: AsMut<SpecificPin<Self>>,
{
/// [`PinId`] of the corresponding [`Pin`]
type Id: PinId;
/// [`PinMode`] of the corresponding [`Pin`]
type Mode: PinMode;
}
impl<I, M> Sealed for Pin<I, M>
where
I: PinId,
M: PinMode,
{
}
impl<I, M> AnyPin for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Id = I;
type Mode = M;
}
/// Type alias to recover the specific [`Pin`] type from an implementation of
/// [`AnyPin`]
///
/// See the [`AnyKind`] documentation for more details on the pattern.
///
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
pub type SpecificPin<P> = Pin<<P as AnyPin>::Id, <P as AnyPin>::Mode>;
impl<P: AnyPin> AsRef<P> for SpecificPin<P> {
#[inline]
fn as_ref(&self) -> &P {
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
// both are zero-sized, and single-field, newtype structs are guaranteed
// to have the same layout as the field anyway, even for repr(Rust).
unsafe { transmute(self) }
}
}
impl<P: AnyPin> AsMut<P> for SpecificPin<P> {
#[inline]
fn as_mut(&mut self) -> &mut P {
// SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
// Transmuting between `v1` and `v2` `Pin` types is also safe, because
// both are zero-sized, and single-field, newtype structs are guaranteed
// to have the same layout as the field anyway, even for repr(Rust).
unsafe { transmute(self) }
}
}
//==================================================================================================
// Additional functionality
//==================================================================================================
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
pub fn interrupt_edge(mut self, edge_type: InterruptEdge) -> Self {
self.regs.interrupt_edge(edge_type);
self.irq_enb();
self
}
pub fn interrupt_level(mut self, level_type: InterruptLevel) -> Self {
self.regs.interrupt_level(level_type);
self.irq_enb();
self
}
}
impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
/// See p.53 of the programmers guide for more information.
/// Possible delays in clock cycles:
/// - Delay 1: 1
/// - Delay 2: 2
/// - Delay 1 + Delay 2: 3
#[inline]
pub fn delay(self, delay_1: bool, delay_2: bool) -> Self {
self.regs.delay(delay_1, delay_2);
self
}
/// See p.52 of the programmers guide for more information.
/// When configured for pulse mode, a given pin will set the non-default state for exactly
/// one clock cycle before returning to the configured default state
pub fn pulse_mode(self, enable: bool, default_state: PinState) -> Self {
self.regs.pulse_mode(enable, default_state);
self
}
pub fn interrupt_edge(mut self, edge_type: InterruptEdge) -> Self {
self.regs.interrupt_edge(edge_type);
self.irq_enb();
self
}
pub fn interrupt_level(mut self, level_type: InterruptLevel) -> Self {
self.regs.interrupt_level(level_type);
self.irq_enb();
self
}
}
impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
/// See p.37 and p.38 of the programmers guide for more information.
#[inline]
pub fn filter_type(self, filter: FilterType, clksel: FilterClkSel) -> Self {
self.regs.filter_type(filter, clksel);
self
}
}
//==================================================================================================
// Embedded HAL traits
//==================================================================================================
impl<I, M> ErrorType for Pin<I, M>
where
I: PinId,
M: PinMode,
{
type Error = Infallible;
}
impl<I: PinId, C: OutputConfig> OutputPin for Pin<I, Output<C>> {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self._set_high();
Ok(())
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self._set_low();
Ok(())
}
}
impl<I, C> InputPin for Pin<I, Input<C>>
where
I: PinId,
C: InputConfig,
{
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_high())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
}
}
impl<I, C> StatefulOutputPin for Pin<I, Output<C>>
where
I: PinId,
C: OutputConfig + ReadableOutput,
{
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_high())
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
}
}
impl<I, C> InputPin for Pin<I, Output<C>>
where
I: PinId,
C: OutputConfig + ReadableOutput,
{
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_high())
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self._is_low())
}
}
//==================================================================================================
// Registers
//==================================================================================================
/// Provide a safe register interface for [`Pin`]s
///
/// This `struct` takes ownership of a [`PinId`] and provides an API to
/// access the corresponding registers.
pub(in crate::gpio) struct Registers<I: PinId> {
id: PhantomData<I>,
}
// [`Registers`] takes ownership of the [`PinId`], and [`Pin`] guarantees that
// each pin is a singleton, so this implementation is safe.
unsafe impl<I: PinId> RegisterInterface for Registers<I> {
#[inline]
fn id(&self) -> DynPinId {
I::DYN
}
}
impl<I: PinId> Registers<I> {
/// Create a new instance of [`Registers`]
///
/// # Safety
///
/// Users must never create two simultaneous instances of this `struct` with
/// the same [`PinId`]
#[inline]
unsafe fn new() -> Self {
Registers { id: PhantomData }
}
/// Provide a type-level equivalent for the
/// [`RegisterInterface::change_mode`] method.
#[inline]
pub(in crate::gpio) fn change_mode<M: PinMode>(&mut self) {
RegisterInterface::change_mode(self, M::DYN);
}
}
//==================================================================================================
// Pin definitions
//==================================================================================================
macro_rules! pins {
(
$Port:ident, $PinsName:ident, $($Id:ident,)+,
) => {
paste::paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName {
port: $Port,
$(
#[doc = "Pin " $Id]
pub [<$Id:lower>]: Pin<$Id, Reset>,
)+
}
impl $PinsName {
/// Create a new struct containing all the Pins. Passing the IOCONFIG peripheral
/// is optional because it might be required to create pin definitions for both
/// ports.
#[inline]
pub fn new(
syscfg: &mut va416xx::Sysconfig,
port: $Port
) -> $PinsName {
syscfg.peripheral_clk_enable().modify(|_, w| {
w.[<$Port:lower>]().set_bit();
w.ioconfig().set_bit()
});
$PinsName {
port,
// Safe because we only create one `Pin` per `PinId`
$(
[<$Id:lower>]: unsafe { Pin::new() },
)+
}
}
/// Get the peripheral ID
/// Safety: Read-only register
pub fn get_perid() -> u32 {
let port = unsafe { &(*$Port::ptr()) };
port.perid().read().bits()
}
/// Consumes the Pins struct and returns the port definitions
pub fn release(self) -> $Port {
self.port
}
}
);
}
}
macro_rules! declare_pins {
(
$Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+]
) => {
pins!($Port, $PinsName, $($Id,)+,);
$(
pin_id!($Group, $Id, $NUM);
)+
}
}
declare_pins!(
A,
PinsA,
Porta,
[
(PA0, 0),
(PA1, 1),
(PA2, 2),
(PA3, 3),
(PA4, 4),
(PA5, 5),
(PA6, 6),
(PA7, 7),
(PA8, 8),
(PA9, 9),
(PA10, 10),
(PA11, 11),
(PA12, 12),
(PA13, 13),
(PA14, 14),
(PA15, 15),
]
);
declare_pins!(
B,
PinsB,
Portb,
[
(PB0, 0),
(PB1, 1),
(PB2, 2),
(PB3, 3),
(PB4, 4),
(PB5, 5),
(PB6, 6),
(PB7, 7),
(PB8, 8),
(PB9, 9),
(PB10, 10),
(PB11, 11),
(PB12, 12),
(PB13, 13),
(PB14, 14),
(PB15, 15),
]
);
declare_pins!(
C,
PinsC,
Portc,
[
(PC0, 0),
(PC1, 1),
(PC2, 2),
(PC3, 3),
(PC4, 4),
(PC5, 5),
(PC6, 6),
(PC7, 7),
(PC8, 8),
(PC9, 9),
(PC10, 10),
(PC11, 11),
(PC12, 12),
(PC13, 13),
(PC14, 14),
(PC15, 15),
]
);
declare_pins!(
D,
PinsD,
Portd,
[
(PD0, 0),
(PD1, 1),
(PD2, 2),
(PD3, 3),
(PD4, 4),
(PD5, 5),
(PD6, 6),
(PD7, 7),
(PD8, 8),
(PD9, 9),
(PD10, 10),
(PD11, 11),
(PD12, 12),
(PD13, 13),
(PD14, 14),
(PD15, 15),
]
);
declare_pins!(
E,
PinsE,
Porte,
[
(PE0, 0),
(PE1, 1),
(PE2, 2),
(PE3, 3),
(PE4, 4),
(PE5, 5),
(PE6, 6),
(PE7, 7),
(PE8, 8),
(PE9, 9),
(PE10, 10),
(PE11, 11),
(PE12, 12),
(PE13, 13),
(PE14, 14),
(PE15, 15),
]
);
declare_pins!(
F,
PinsF,
Portf,
[
(PF0, 0),
(PF1, 1),
(PF2, 2),
(PF3, 3),
(PF4, 4),
(PF5, 5),
(PF6, 6),
(PF7, 7),
(PF8, 8),
(PF9, 9),
(PF10, 10),
(PF11, 11),
(PF12, 12),
(PF13, 13),
(PF14, 14),
(PF15, 15),
]
);
declare_pins!(
G,
PinsG,
Portg,
[
(PG0, 0),
(PG1, 1),
(PG2, 2),
(PG3, 3),
(PG4, 4),
(PG5, 5),
(PG6, 6),
(PG7, 7),
]
);

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

@ -3,899 +3,4 @@
//! ## Examples
//!
//! - [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,
};
use core::{marker::PhantomData, ops::Deref};
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
//==================================================================================================
// Defintions
//==================================================================================================
const CLK_100K: Hertz = Hertz::from_raw(100_000);
const CLK_400K: Hertz = Hertz::from_raw(400_000);
const MIN_CLK_400K: Hertz = Hertz::from_raw(10_000_000);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FifoEmptyMode {
Stall = 0,
EndTransaction = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ClockTooSlowForFastI2c;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
InvalidTimingParams,
ArbitrationLost,
NackAddr,
/// Data not acknowledged in write operation
NackData,
/// Not enough data received in read operation
InsufficientDataReceived,
/// Number of bytes in transfer too large (larger than 0x7fe)
DataTooLarge,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InitError {
/// Wrong address used in constructor
WrongAddrMode,
/// APB1 clock is too slow for fast I2C mode.
ClkTooSlow(ClockTooSlowForFastI2c),
}
impl From<ClockTooSlowForFastI2c> for InitError {
fn from(value: ClockTooSlowForFastI2c) -> Self {
Self::ClkTooSlow(value)
}
}
impl embedded_hal::i2c::Error for Error {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
match self {
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
Error::NackAddr => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
}
Error::NackData => {
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
}
Error::DataTooLarge | Error::InsufficientDataReceived | Error::InvalidTimingParams => {
embedded_hal::i2c::ErrorKind::Other
}
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum I2cCmd {
Start = 0b00,
Stop = 0b10,
StartWithStop = 0b11,
Cancel = 0b100,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cSpeed {
Regular100khz = 0,
Fast400khz = 1,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cDirection {
Send = 0,
Read = 1,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cAddress {
Regular(u8),
TenBit(u16),
}
pub type I2cRegBlock = pac::i2c0::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all SPI blocks.
pub trait Instance: Deref<Target = I2cRegBlock> {
const IDX: u8;
const PERIPH_SEL: PeripheralSelect;
fn ptr() -> *const I2cRegBlock;
}
impl Instance for pac::I2c0 {
const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
impl Instance for pac::I2c1 {
const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
impl Instance for pac::I2c2 {
const IDX: u8 = 2;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2;
#[inline(always)]
fn ptr() -> *const I2cRegBlock {
Self::ptr()
}
}
//==================================================================================================
// Config
//==================================================================================================
pub struct TrTfThighTlow(u8, u8, u8, u8);
pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
pub struct TimingCfg {
// 4 bit max width
tr: u8,
// 4 bit max width
tf: u8,
// 4 bit max width
thigh: u8,
// 4 bit max width
tlow: u8,
// 4 bit max width
tsu_sto: u8,
// 4 bit max width
tsu_sta: u8,
// 4 bit max width
thd_sta: u8,
// 4 bit max width
tbuf: u8,
}
impl TimingCfg {
pub fn new(
first_16_bits: TrTfThighTlow,
second_16_bits: TsuStoTsuStaThdStaTBuf,
) -> Result<Self, Error> {
if first_16_bits.0 > 0xf
|| first_16_bits.1 > 0xf
|| first_16_bits.2 > 0xf
|| first_16_bits.3 > 0xf
|| second_16_bits.0 > 0xf
|| second_16_bits.1 > 0xf
|| second_16_bits.2 > 0xf
|| second_16_bits.3 > 0xf
{
return Err(Error::InvalidTimingParams);
}
Ok(TimingCfg {
tr: first_16_bits.0,
tf: first_16_bits.1,
thigh: first_16_bits.2,
tlow: first_16_bits.3,
tsu_sto: second_16_bits.0,
tsu_sta: second_16_bits.1,
thd_sta: second_16_bits.2,
tbuf: second_16_bits.3,
})
}
pub fn reg(&self) -> u32 {
(self.tbuf as u32) << 28
| (self.thd_sta as u32) << 24
| (self.tsu_sta as u32) << 20
| (self.tsu_sto as u32) << 16
| (self.tlow as u32) << 12
| (self.thigh as u32) << 8
| (self.tf as u32) << 4
| (self.tr as u32)
}
}
impl Default for TimingCfg {
fn default() -> Self {
TimingCfg {
tr: 0x02,
tf: 0x01,
thigh: 0x08,
tlow: 0x09,
tsu_sto: 0x8,
tsu_sta: 0x0a,
thd_sta: 0x8,
tbuf: 0xa,
}
}
}
pub struct MasterConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
/// Enable the analog delay glitch filter
pub alg_filt: bool,
/// Enable the digital glitch filter
pub dlg_filt: bool,
pub tm_cfg: Option<TimingCfg>,
// Loopback mode
// lbm: bool,
}
impl Default for MasterConfig {
fn default() -> Self {
MasterConfig {
tx_fe_mode: FifoEmptyMode::Stall,
rx_fe_mode: FifoEmptyMode::Stall,
alg_filt: false,
dlg_filt: false,
tm_cfg: None,
}
}
}
impl Sealed for MasterConfig {}
pub struct SlaveConfig {
pub tx_fe_mode: FifoEmptyMode,
pub rx_fe_mode: FifoEmptyMode,
/// Maximum number of words before issuing a negative acknowledge.
/// Range should be 0 to 0x7fe. Setting the value to 0x7ff has the same effect as not setting
/// the enable bit since RXCOUNT stops counting at 0x7fe.
pub max_words: Option<usize>,
/// A received address is compared to the ADDRESS register (addr) using the address mask
/// (addr_mask). Those bits with a 1 in the address mask must match for there to be an address
/// match
pub addr: I2cAddress,
/// The default address mask will be 0x3ff to only allow full matches
pub addr_mask: Option<u16>,
/// Optionally specify a second I2C address the slave interface responds to
pub addr_b: Option<I2cAddress>,
pub addr_b_mask: Option<u16>,
}
impl SlaveConfig {
/// Build a default slave config given a specified slave address to respond to
pub fn new(addr: I2cAddress) -> Self {
SlaveConfig {
tx_fe_mode: FifoEmptyMode::Stall,
rx_fe_mode: FifoEmptyMode::Stall,
max_words: None,
addr,
addr_mask: None,
addr_b: None,
addr_b_mask: None,
}
}
}
impl Sealed for SlaveConfig {}
//==================================================================================================
// I2C Base
//==================================================================================================
pub struct I2cBase<I2c> {
i2c: I2c,
clock: Hertz,
}
impl<I2C> I2cBase<I2C> {
#[inline]
fn unwrap_addr(addr: I2cAddress) -> (u16, u32) {
match addr {
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
I2cAddress::TenBit(addr) => (addr, 1 << 15),
}
}
}
impl<I2c: Instance> I2cBase<I2c> {
pub fn new(
i2c: I2c,
syscfg: &mut pac::Sysconfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
ms_cfg: Option<&MasterConfig>,
sl_cfg: Option<&SlaveConfig>,
) -> Result<Self, ClockTooSlowForFastI2c> {
syscfg.enable_peripheral_clock(I2c::PERIPH_SEL);
let mut i2c_base = I2cBase {
i2c,
clock: clocks.apb1(),
};
if let Some(ms_cfg) = ms_cfg {
i2c_base.cfg_master(ms_cfg);
}
if let Some(sl_cfg) = sl_cfg {
i2c_base.cfg_slave(sl_cfg);
}
i2c_base.cfg_clk_scale(speed_mode)?;
Ok(i2c_base)
}
fn cfg_master(&mut self, ms_cfg: &MasterConfig) {
let (txfemd, rxfemd) = match (ms_cfg.tx_fe_mode, ms_cfg.rx_fe_mode) {
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
};
self.i2c.ctrl().modify(|_, w| {
w.txfemd().bit(txfemd);
w.rxffmd().bit(rxfemd);
w.dlgfilter().bit(ms_cfg.dlg_filt);
w.algfilter().bit(ms_cfg.alg_filt)
});
if let Some(ref tm_cfg) = ms_cfg.tm_cfg {
self.i2c
.tmconfig()
.write(|w| unsafe { w.bits(tm_cfg.reg()) });
}
self.i2c.fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
}
fn cfg_slave(&mut self, sl_cfg: &SlaveConfig) {
let (txfemd, rxfemd) = match (sl_cfg.tx_fe_mode, sl_cfg.rx_fe_mode) {
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
};
self.i2c.s0_ctrl().modify(|_, w| {
w.txfemd().bit(txfemd);
w.rxffmd().bit(rxfemd)
});
self.i2c.s0_fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
let max_words = sl_cfg.max_words;
if let Some(max_words) = max_words {
self.i2c
.s0_maxwords()
.write(|w| unsafe { w.bits(1 << 31 | max_words as u32) });
}
let (addr, addr_mode_mask) = Self::unwrap_addr(sl_cfg.addr);
// The first bit is the read/write value. Normally, both read and write are matched
// using the RWMASK bit of the address mask register
self.i2c
.s0_address()
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
if let Some(addr_mask) = sl_cfg.addr_mask {
self.i2c
.s0_addressmask()
.write(|w| unsafe { w.bits((addr_mask << 1) as u32) });
}
if let Some(addr_b) = sl_cfg.addr_b {
let (addr, addr_mode_mask) = Self::unwrap_addr(addr_b);
self.i2c
.s0_addressb()
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) })
}
if let Some(addr_b_mask) = sl_cfg.addr_b_mask {
self.i2c
.s0_addressmaskb()
.write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) })
}
}
#[inline]
pub fn filters(&mut self, digital_filt: bool, analog_filt: bool) {
self.i2c.ctrl().modify(|_, w| {
w.dlgfilter().bit(digital_filt);
w.algfilter().bit(analog_filt)
});
}
#[inline]
pub fn fifo_empty_mode(&mut self, rx: FifoEmptyMode, tx: FifoEmptyMode) {
self.i2c.ctrl().modify(|_, w| {
w.txfemd().bit(tx as u8 != 0);
w.rxffmd().bit(rx as u8 != 0)
});
}
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2c> {
if speed_mode == I2cSpeed::Regular100khz {
Ok(((self.clock.raw() / CLK_100K.raw() / 20) - 1) as u8)
} else {
if self.clock.raw() < MIN_CLK_400K.raw() {
return Err(ClockTooSlowForFastI2c);
}
Ok(((self.clock.raw() / CLK_400K.raw() / 25) - 1) as u8)
}
}
/// Configures the clock scale for a given speed mode setting
pub fn cfg_clk_scale(&mut self, speed_mode: I2cSpeed) -> Result<(), ClockTooSlowForFastI2c> {
let clk_div = self.calc_clk_div(speed_mode)?;
self.i2c
.clkscale()
.write(|w| unsafe { w.bits((speed_mode as u32) << 31 | clk_div as u32) });
Ok(())
}
pub fn load_address(&mut self, addr: u16) {
// Load address
self.i2c
.address()
.write(|w| unsafe { w.bits((addr << 1) as u32) });
}
#[inline]
fn stop_cmd(&mut self) {
self.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::Stop as u32) });
}
}
//==================================================================================================
// I2C Master
//==================================================================================================
pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
i2c_base: I2cBase<I2c>,
addr: PhantomData<Addr>,
}
impl<I2c: Instance, Addr> I2cMaster<I2c, Addr> {
pub fn new(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: MasterConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2c> {
Ok(I2cMaster {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, Some(&cfg), None)?,
addr: PhantomData,
}
.enable_master())
}
#[inline]
pub fn cancel_transfer(&self) {
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::Cancel as u32) });
}
#[inline]
pub fn clear_tx_fifo(&self) {
self.i2c_base.i2c.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn clear_rx_fifo(&self) {
self.i2c_base.i2c.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
pub fn enable_master(self) -> Self {
self.i2c_base.i2c.ctrl().modify(|_, w| w.enable().set_bit());
self
}
#[inline]
pub fn disable_master(self) -> Self {
self.i2c_base
.i2c
.ctrl()
.modify(|_, w| w.enable().clear_bit());
self
}
#[inline(always)]
fn load_fifo(&self, word: u8) {
self.i2c_base
.i2c
.data()
.write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
fn read_fifo(&self) -> u8 {
self.i2c_base.i2c.data().read().bits() as u8
}
fn error_handler_write(&mut self, init_cmd: &I2cCmd) {
self.clear_tx_fifo();
if *init_cmd == I2cCmd::Start {
self.i2c_base.stop_cmd()
}
}
fn write_base(
&mut self,
addr: I2cAddress,
init_cmd: I2cCmd,
bytes: impl IntoIterator<Item = u8>,
) -> Result<(), Error> {
let mut iter = bytes.into_iter();
// Load address
let (addr, addr_mode_bit) = I2cBase::<I2c>::unwrap_addr(addr);
self.i2c_base.i2c.address().write(|w| unsafe {
w.bits(I2cDirection::Send as u32 | (addr << 1) as u32 | addr_mode_bit)
});
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(init_cmd as u32) });
let mut load_if_next_available = || {
if let Some(next_byte) = iter.next() {
self.load_fifo(next_byte);
}
};
loop {
let status_reader = self.i2c_base.i2c.status().read();
if status_reader.arblost().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::ArbitrationLost);
} else if status_reader.nackaddr().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::NackAddr);
} else if status_reader.nackdata().bit_is_set() {
self.error_handler_write(&init_cmd);
return Err(Error::NackData);
} else if status_reader.idle().bit_is_set() {
return Ok(());
} else {
while !status_reader.txnfull().bit_is_set() {
load_if_next_available();
}
}
}
}
fn write_from_buffer(
&mut self,
init_cmd: I2cCmd,
addr: I2cAddress,
output: &[u8],
) -> Result<(), Error> {
let len = output.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Load number of words
self.i2c_base
.i2c
.words()
.write(|w| unsafe { w.bits(len as u32) });
let mut bytes = output.iter();
// FIFO has a depth of 16. We load slightly above the trigger level
// but not all of it because the transaction might fail immediately
const FILL_DEPTH: usize = 12;
// load the FIFO
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
self.load_fifo(*bytes.next().unwrap());
}
self.write_base(addr, init_cmd, output.iter().cloned())
}
fn read_internal(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
let len = buffer.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Clear the receive FIFO
self.clear_rx_fifo();
// Load number of words
self.i2c_base
.i2c
.words()
.write(|w| unsafe { w.bits(len as u32) });
let (addr, addr_mode_bit) = match addr {
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
I2cAddress::TenBit(addr) => (addr, 1 << 15),
};
// Load address
self.i2c_base.i2c.address().write(|w| unsafe {
w.bits(I2cDirection::Read as u32 | (addr << 1) as u32 | addr_mode_bit)
});
let mut buf_iter = buffer.iter_mut();
let mut read_bytes = 0;
// Start receive transfer
self.i2c_base
.i2c
.cmd()
.write(|w| unsafe { w.bits(I2cCmd::StartWithStop as u32) });
let mut read_if_next_available = || {
if let Some(next_byte) = buf_iter.next() {
*next_byte = self.read_fifo();
}
};
loop {
let status_reader = self.i2c_base.i2c.status().read();
if status_reader.arblost().bit_is_set() {
self.clear_rx_fifo();
return Err(Error::ArbitrationLost);
} else if status_reader.nackaddr().bit_is_set() {
self.clear_rx_fifo();
return Err(Error::NackAddr);
} else if status_reader.idle().bit_is_set() {
if read_bytes != len {
return Err(Error::InsufficientDataReceived);
}
return Ok(());
} else if status_reader.rxnempty().bit_is_set() {
read_if_next_available();
read_bytes += 1;
}
}
}
}
//======================================================================================
// Embedded HAL I2C implementations
//======================================================================================
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, SevenBitAddress> {
type Error = Error;
}
impl<I2c: Instance> embedded_hal::i2c::I2c for I2cMaster<I2c, SevenBitAddress> {
fn transaction(
&mut self,
address: SevenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
for operation in operations {
match operation {
Operation::Read(buf) => self.read_internal(I2cAddress::Regular(address), buf)?,
Operation::Write(buf) => self.write_from_buffer(
I2cCmd::StartWithStop,
I2cAddress::Regular(address),
buf,
)?,
}
}
Ok(())
}
}
impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, TenBitAddress> {
type Error = Error;
}
impl<I2c: Instance> embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<I2c, TenBitAddress> {
fn transaction(
&mut self,
address: TenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
for operation in operations {
match operation {
Operation::Read(buf) => self.read_internal(I2cAddress::TenBit(address), buf)?,
Operation::Write(buf) => {
self.write_from_buffer(I2cCmd::StartWithStop, I2cAddress::TenBit(address), buf)?
}
}
}
Ok(())
}
}
//==================================================================================================
// I2C Slave
//==================================================================================================
pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
i2c_base: I2cBase<I2c>,
addr: PhantomData<Addr>,
}
impl<I2c: Instance, Addr> I2cSlave<I2c, Addr> {
fn new_generic(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2c> {
Ok(I2cSlave {
i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, None, Some(&cfg))?,
addr: PhantomData,
}
.enable_slave())
}
#[inline]
pub fn enable_slave(self) -> Self {
self.i2c_base
.i2c
.s0_ctrl()
.modify(|_, w| w.enable().set_bit());
self
}
#[inline]
pub fn disable_slave(self) -> Self {
self.i2c_base
.i2c
.s0_ctrl()
.modify(|_, w| w.enable().clear_bit());
self
}
#[inline(always)]
fn load_fifo(&self, word: u8) {
self.i2c_base
.i2c
.s0_data()
.write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
fn read_fifo(&self) -> u8 {
self.i2c_base.i2c.s0_data().read().bits() as u8
}
#[inline]
fn clear_tx_fifo(&self) {
self.i2c_base
.i2c
.s0_fifo_clr()
.write(|w| w.txfifo().set_bit());
}
#[inline]
fn clear_rx_fifo(&self) {
self.i2c_base
.i2c
.s0_fifo_clr()
.write(|w| w.rxfifo().set_bit());
}
/// Get the last address that was matched by the slave control and the corresponding
/// master direction
pub fn last_address(&self) -> (I2cDirection, u32) {
let bits = self.i2c_base.i2c.s0_lastaddress().read().bits();
match bits & 0x01 {
0 => (I2cDirection::Send, bits >> 1),
1 => (I2cDirection::Read, bits >> 1),
_ => (I2cDirection::Send, bits >> 1),
}
}
pub fn write(&mut self, output: &[u8]) -> Result<(), Error> {
let len = output.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
let mut bytes = output.iter();
// FIFO has a depth of 16. We load slightly above the trigger level
// but not all of it because the transaction might fail immediately
const FILL_DEPTH: usize = 12;
// load the FIFO
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
self.load_fifo(*bytes.next().unwrap());
}
let status_reader = self.i2c_base.i2c.s0_status().read();
let mut load_if_next_available = || {
if let Some(next_byte) = bytes.next() {
self.load_fifo(*next_byte);
}
};
loop {
if status_reader.nackdata().bit_is_set() {
self.clear_tx_fifo();
return Err(Error::NackData);
} else if status_reader.idle().bit_is_set() {
return Ok(());
} else {
while !status_reader.txnfull().bit_is_set() {
load_if_next_available();
}
}
}
}
pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
let len = buffer.len();
// It should theoretically possible to transfer larger data sizes by tracking
// the number of sent words and setting it to 0x7fe as soon as only that many
// bytes are remaining. However, large transfer like this are not common. This
// feature will therefore not be supported for now.
if len > 0x7fe {
return Err(Error::DataTooLarge);
}
// Clear the receive FIFO
self.clear_rx_fifo();
let mut buf_iter = buffer.iter_mut();
let mut read_bytes = 0;
let mut read_if_next_available = || {
if let Some(next_byte) = buf_iter.next() {
*next_byte = self.read_fifo();
}
};
loop {
let status_reader = self.i2c_base.i2c.s0_status().read();
if status_reader.idle().bit_is_set() {
if read_bytes != len {
return Err(Error::InsufficientDataReceived);
}
return Ok(());
} else if status_reader.rxnempty().bit_is_set() {
read_bytes += 1;
read_if_next_available();
}
}
}
}
impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
/// Create a new I2C slave for seven bit addresses
pub fn new(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, InitError> {
if let I2cAddress::TenBit(_) = cfg.addr {
return Err(InitError::WrongAddrMode);
}
Ok(Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)?)
}
}
impl<I2c: Instance> I2cSlave<I2c, TenBitAddress> {
pub fn new_ten_bit_addr(
i2c: I2c,
sys_cfg: &mut pac::Sysconfig,
cfg: SlaveConfig,
clocks: &Clocks,
speed_mode: I2cSpeed,
) -> Result<Self, ClockTooSlowForFastI2c> {
Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)
}
}
pub use vorago_shared_periphs::i2c::*;

View File

@ -0,0 +1,28 @@
//! IRQ Router peripheral support.
use vorago_shared_periphs::{
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
};
use crate::pac;
/// This enables and initiates the peripheral.
///
/// Please note that this method also writes 0 to the registers which do not have 0 as the default
/// reset value. The programmers guide v1.2 and the actual values inspected using a SVD viewer
/// are inconsistent here, and the registers being non-zero can actually lead to weird bugs
/// when working with interrupts. Registers DMASELx and ADCSEL/DMASELx will reset to 0x7f and 0x1f
/// respectively instead of 0x00.
pub fn enable_and_init_irq_router() {
let irq_router = unsafe { pac::IrqRouter::steal() };
enable_peripheral_clock(PeripheralSelect::IrqRouter);
reset_peripheral_for_cycles(PeripheralSelect::IrqRouter, 2);
unsafe {
irq_router.dmasel0().write_with_zero(|w| w);
irq_router.dmasel1().write_with_zero(|w| w);
irq_router.dmasel2().write_with_zero(|w| w);
irq_router.dmasel3().write_with_zero(|w| w);
irq_router.adcsel().write_with_zero(|w| w);
irq_router.dacsel0().write_with_zero(|w| w);
irq_router.dacsel1().write_with_zero(|w| w);
}
}

View File

@ -1,49 +1,139 @@
//! 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 pins;
pub mod prelude;
pub mod pwm;
pub mod spi;
pub mod time;
pub mod timer;
pub mod typelevel;
pub mod uart;
pub mod wdt;
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
pub enum FunSel {
Sel0 = 0b00,
Sel1 = 0b01,
Sel2 = 0b10,
Sel3 = 0b11,
#[cfg(feature = "va41630")]
pub mod nvm;
#[cfg(not(feature = "va41628"))]
pub mod adc;
#[cfg(not(feature = "va41628"))]
pub mod dac;
pub use vorago_shared_periphs::{
assert_peripheral_reset, deassert_peripheral_reset, disable_nvic_interrupt,
disable_peripheral_clock, enable_nvic_interrupt, enable_peripheral_clock,
reset_peripheral_for_cycles, FunSel, PeripheralSelect,
};
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid pin with number {0}")]
pub struct InvalidPinError(u8);
/// Can be used to manually manipulate the function select of port pins.
///
/// The function selection table can be found on p.286 of the programmers guide. Please note
/// 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(())
}
/// 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 trait SyscfgExt {
fn enable_peripheral_clock(&mut self, clock: PeripheralSelect);
fn disable_peripheral_clock(&mut self, clock: PeripheralSelect);
fn assert_periph_reset(&mut self, periph: PeripheralSelect);
fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize);
}
impl SyscfgExt for pac::Sysconfig {
#[inline(always)]
fn enable_peripheral_clock(&mut self, clock: PeripheralSelect) {
enable_peripheral_clock(clock)
}
#[inline(always)]
fn disable_peripheral_clock(&mut self, clock: PeripheralSelect) {
disable_peripheral_clock(clock)
}
#[inline(always)]
fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
assert_peripheral_reset(clock)
}
#[inline(always)]
fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
deassert_peripheral_reset(clock)
}
#[inline(always)]
fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize) {
reset_peripheral_for_cycles(periph, cycles)
}
}
/// Disable a specific interrupt using the NVIC peripheral.
#[inline]
pub fn disable_interrupt(irq: pac::Interrupt) {
cortex_m::peripheral::NVIC::mask(irq);
}

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

@ -0,0 +1,277 @@
//! Non-volatile memory (NVM) driver.
//!
//! Provides a basic API to work with the internal NVM of the VA41630 MCU.
//!
//! # Examples
//!
//! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
use embedded_hal::spi::MODE_0;
use vorago_shared_periphs::{
disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
};
use crate::clock::Clocks;
use crate::pac;
use crate::spi::{
mode_to_cpo_cph_bit, spi_clk_config_from_div, SpiMarker, WordProvider, BMSTART_BMSTOP_MASK,
};
const NVM_CLOCK_DIV: u16 = 2;
// Commands. The internal FRAM is based on the Cypress FM25V20A device.
/// Write enable register.
pub const FRAM_WREN: u8 = 0x06;
pub const FRAM_WRDI: u8 = 0x04;
pub const FRAM_RDSR: u8 = 0x05;
/// Write single status register
pub const FRAM_WRSR: u8 = 0x01;
pub const FRAM_READ: u8 = 0x03;
pub const FRAM_WRITE: u8 = 0x02;
pub const FRAM_RDID: u8 = 0x9F;
pub const FRAM_SLEEP: u8 = 0xB9;
/* Address Masks */
const ADDR_MSB_MASK: u32 = 0xFF0000;
const ADDR_MID_MASK: u32 = 0x00FF00;
const ADDR_LSB_MASK: u32 = 0x0000FF;
#[inline(always)]
const fn msb_addr_byte(addr: u32) -> u8 {
((addr & ADDR_MSB_MASK) >> 16) as u8
}
#[inline(always)]
const fn mid_addr_byte(addr: u32) -> u8 {
((addr & ADDR_MID_MASK) >> 8) as u8
}
#[inline(always)]
const fn lsb_addr_byte(addr: u32) -> u8 {
(addr & ADDR_LSB_MASK) as u8
}
pub const WPEN_ENABLE_MASK: u8 = 1 << 7;
pub const BP_0_ENABLE_MASK: u8 = 1 << 2;
pub const BP_1_ENABLE_MASK: u8 = 1 << 3;
pub struct Nvm {
spi: Option<pac::Spi3>,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct VerifyError {
addr: u32,
found: u8,
expected: u8,
}
impl Nvm {
pub fn new(spi: pac::Spi3, _clocks: &Clocks) -> Self {
enable_peripheral_clock(pac::Spi3::PERIPH_SEL);
// This is done in the C HAL.
reset_peripheral_for_cycles(pac::Spi3::PERIPH_SEL, 2);
let spi_clk_cfg = spi_clk_config_from_div(NVM_CLOCK_DIV).unwrap();
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
spi.ctrl0().write(|w| {
unsafe {
w.size().bits(u8::word_reg());
w.scrdv().bits(spi_clk_cfg.scrdv());
// Clear clock phase and polarity. Will be set to correct value for each
// transfer
w.spo().bit(cpo_bit);
w.sph().bit(cph_bit)
}
});
spi.ctrl1().write(|w| {
w.blockmode().set_bit();
unsafe { w.ss().bits(0) };
w.bmstart().set_bit();
w.bmstall().set_bit()
});
spi.clkprescale()
.write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val() as u32) });
spi.fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
// Enable the peripheral as the last step as recommended in the
// programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit());
let mut nvm = Self { spi: Some(spi) };
nvm.disable_write_prot();
nvm
}
pub fn disable_write_prot(&mut self) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(FRAM_WRSR);
self.write_with_bmstop(0x00);
self.wait_for_tx_idle();
}
pub fn read_rdsr(&self) -> u8 {
self.write_single(FRAM_RDSR);
self.write_with_bmstop(0x00);
self.wait_for_rx_available();
self.read_single_word();
self.wait_for_rx_available();
(self.read_single_word() & 0xff) as u8
}
pub fn enable_write_prot(&mut self) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(FRAM_WRSR);
self.write_with_bmstop(0x00);
}
#[inline(always)]
pub fn spi(&self) -> &pac::Spi3 {
self.spi.as_ref().unwrap()
}
#[inline(always)]
pub fn write_single(&self, word: u8) {
self.spi().data().write(|w| unsafe { w.bits(word as u32) });
}
#[inline(always)]
pub fn write_with_bmstop(&self, word: u8) {
self.spi()
.data()
.write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) });
}
#[inline(always)]
pub fn wait_for_tx_idle(&self) {
while self.spi().status().read().tfe().bit_is_clear() {
cortex_m::asm::nop();
}
while self.spi().status().read().busy().bit_is_set() {
cortex_m::asm::nop();
}
self.clear_fifos()
}
#[inline(always)]
pub fn clear_fifos(&self) {
self.spi().fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
}
#[inline(always)]
pub fn wait_for_rx_available(&self) {
while !self.spi().status().read().rne().bit_is_set() {
cortex_m::asm::nop();
}
}
#[inline(always)]
pub fn read_single_word(&self) -> u32 {
self.spi().data().read().bits()
}
pub fn write_data(&self, addr: u32, data: &[u8]) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(FRAM_WRITE);
self.write_single(msb_addr_byte(addr));
self.write_single(mid_addr_byte(addr));
self.write_single(lsb_addr_byte(addr));
for byte in data.iter().take(data.len() - 1) {
while self.spi().status().read().tnf().bit_is_clear() {
cortex_m::asm::nop();
}
self.write_single(*byte);
self.read_single_word();
}
while self.spi().status().read().tnf().bit_is_clear() {
cortex_m::asm::nop();
}
self.write_with_bmstop(*data.last().unwrap());
self.wait_for_tx_idle();
}
pub fn read_data(&self, addr: u32, buf: &mut [u8]) {
self.common_read_start(addr);
for byte in buf {
// Pump the SPI.
self.write_single(0);
self.wait_for_rx_available();
*byte = self.read_single_word() as u8;
}
self.write_with_bmstop(0);
self.wait_for_tx_idle();
}
pub fn verify_data(&self, addr: u32, comp_buf: &[u8]) -> Result<(), VerifyError> {
self.common_read_start(addr);
for (idx, byte) in comp_buf.iter().enumerate() {
// Pump the SPI.
self.write_single(0);
self.wait_for_rx_available();
let next_word = self.read_single_word() as u8;
if next_word != *byte {
self.write_with_bmstop(0);
self.wait_for_tx_idle();
return Err(VerifyError {
addr: addr + idx as u32,
found: next_word,
expected: *byte,
});
}
}
self.write_with_bmstop(0);
self.wait_for_tx_idle();
Ok(())
}
/// Enable write-protection and disables the peripheral clock.
pub fn shutdown(&mut self) {
self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle();
self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK);
disable_peripheral_clock(pac::Spi3::PERIPH_SEL);
}
/// This function calls [Self::shutdown] and gives back the peripheral structure.
pub fn release(mut self) -> pac::Spi3 {
self.shutdown();
self.spi.take().unwrap()
}
fn common_read_start(&self, addr: u32) {
self.wait_for_tx_idle();
self.write_single(FRAM_READ);
self.write_single(msb_addr_byte(addr));
self.write_single(mid_addr_byte(addr));
self.write_single(lsb_addr_byte(addr));
for _ in 0..4 {
// Pump the SPI.
self.write_single(0);
self.wait_for_rx_available();
// The first 4 data bytes received need to be ignored.
self.read_single_word();
}
}
}
/// Call [Self::shutdown] on drop.
impl Drop for Nvm {
fn drop(&mut self) {
if self.spi.is_some() {
self.shutdown();
}
}
}

6
va416xx-hal/src/pins.rs Normal file
View File

@ -0,0 +1,6 @@
//! Pin resource management singletons.
//!
//! This module contains the pin singletons. It allows creating those singletons
//! to access the [Pin] structures of individual ports in a safe way with checked ownership
//! rules.
pub use vorago_shared_periphs::pins::*;

View File

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

View File

@ -1,388 +1,8 @@
//! API for Pulse-Width Modulation (PWM)
//!
//! The Vorago VA416xx devices use the TIM peripherals to perform PWM related tasks.
//! The Vorago devices use the TIM peripherals to perform PWM related tasks
//!
//! ## Examples
//!
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
use core::convert::Infallible;
use core::marker::PhantomData;
use crate::pac;
use crate::{clock::Clocks, gpio::DynPinId};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX;
pub struct PwmBase {
clock: Hertz,
/// For PWMB, this is the upper limit
current_duty: u16,
/// For PWMA, this value will not be used
current_lower_limit: u16,
current_period: Hertz,
current_rst_val: u32,
}
enum StatusSelPwm {
PwmA = 3,
PwmB = 4,
}
pub struct PwmA {}
pub struct PwmB {}
//==================================================================================================
// Common
//==================================================================================================
macro_rules! pwm_common_func {
() => {
#[inline]
fn enable_pwm_a(&mut self) {
self.reg
.reg()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
}
#[inline]
fn enable_pwm_b(&mut self) {
self.reg
.reg()
.ctrl()
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
}
#[inline]
pub fn get_period(&self) -> Hertz {
self.pwm_base.current_period
}
#[inline]
pub fn set_period(&mut self, period: impl Into<Hertz>) {
self.pwm_base.current_period = period.into();
// Avoid division by 0
if self.pwm_base.current_period.raw() == 0 {
return;
}
self.pwm_base.current_rst_val =
self.pwm_base.clock.raw() / self.pwm_base.current_period.raw();
self.reg
.reg()
.rst_value()
.write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) });
}
#[inline]
pub fn disable(&mut self) {
self.reg.reg().ctrl().modify(|_, w| w.enable().clear_bit());
}
#[inline]
pub fn enable(&mut self) {
self.reg.reg().ctrl().modify(|_, w| w.enable().set_bit());
}
#[inline]
pub fn period(&self) -> Hertz {
self.pwm_base.current_period
}
#[inline(always)]
pub fn duty(&self) -> u16 {
self.pwm_base.current_duty
}
};
}
macro_rules! pwmb_func {
() => {
pub fn pwmb_lower_limit(&self) -> u16 {
self.pwm_base.current_lower_limit
}
pub fn pwmb_upper_limit(&self) -> u16 {
self.pwm_base.current_duty
}
/// Set the lower limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is larger than
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
self.pwm_base.current_lower_limit = duty;
let pwmb_val: u64 = (self.pwm_base.current_rst_val as u64
* self.pwm_base.current_lower_limit as u64)
/ DUTY_MAX as u64;
self.reg
.reg()
.pwmb_value()
.write(|w| unsafe { w.bits(pwmb_val as u32) });
}
/// Set the higher limit for PWMB
///
/// The PWM signal will be 1 as long as the current RST counter is smaller than
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
/// state
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
self.pwm_base.current_duty = duty;
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
* self.pwm_base.current_duty as u64)
/ DUTY_MAX as u64;
self.reg
.reg()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
}
};
}
//==================================================================================================
// Strongly typed PWM pin
//==================================================================================================
pub struct PwmPin<Pin: TimPin, Tim: ValidTim, Mode = PwmA> {
reg: TimAndPinRegister<Pin, Tim>,
pwm_base: PwmBase,
mode: PhantomData<Mode>,
}
impl<Pin: TimPin, Tim: ValidTim, Mode> PwmPin<Pin, Tim, Mode>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
/// Create a new stronlgy typed PWM pin
pub fn new(
pin_and_tim: (Pin, Tim),
sys_cfg: &mut pac::Sysconfig,
clocks: &Clocks,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin = PwmPin {
pwm_base: PwmBase {
current_duty: 0,
current_lower_limit: 0,
current_period: initial_period.into(),
current_rst_val: 0,
clock: Tim::clock(clocks),
},
reg: unsafe { TimAndPinRegister::new(pin_and_tim.0, pin_and_tim.1) },
mode: PhantomData,
};
sys_cfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | pin.reg.mask_32()) });
pin.enable_pwm_a();
pin.set_period(initial_period);
pin
}
pub fn release(self) -> (Pin, Tim) {
self.reg.release()
}
pwm_common_func!();
}
impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
fn from(other: PwmPin<Pin, Tim, PwmA>) -> Self {
let mut pwmb = Self {
reg: other.reg,
pwm_base: other.pwm_base,
mode: PhantomData,
};
pwmb.enable_pwm_b();
pwmb
}
}
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PwmB>> for PwmPin<PIN, TIM, PwmA>
where
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
{
fn from(other: PwmPin<PIN, TIM, PwmB>) -> Self {
let mut pwmb = Self {
reg: other.reg,
pwm_base: other.pwm_base,
mode: PhantomData,
};
pwmb.enable_pwm_a();
pwmb
}
}
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmA>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwma(
tim_and_pin: (Pin, Tim),
sys_cfg: &mut pac::Sysconfig,
clocks: &Clocks,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmA> =
Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
pin.enable_pwm_a();
pin
}
}
impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
pub fn pwmb(
tim_and_pin: (Pin, Tim),
sys_cfg: &mut pac::Sysconfig,
clocks: &Clocks,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmB> =
Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
pin.enable_pwm_b();
pin
}
}
//==================================================================================================
// Reduced PWM pin
//==================================================================================================
/// Reduced version where type information is deleted
pub struct ReducedPwmPin<Mode = PwmA> {
reg: TimDynRegister,
pwm_base: PwmBase,
pin_id: DynPinId,
mode: PhantomData<Mode>,
}
impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM>> for ReducedPwmPin<PwmA> {
fn from(pwm_pin: PwmPin<PIN, TIM>) -> Self {
ReducedPwmPin {
reg: TimDynRegister::from(pwm_pin.reg),
pwm_base: pwm_pin.pwm_base,
pin_id: PIN::DYN,
mode: PhantomData,
}
}
}
impl<MODE> ReducedPwmPin<MODE> {
pwm_common_func!();
}
impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<PwmB> {
fn from(other: ReducedPwmPin<PwmA>) -> Self {
let mut pwmb = Self {
reg: other.reg,
pwm_base: other.pwm_base,
pin_id: other.pin_id,
mode: PhantomData,
};
pwmb.enable_pwm_b();
pwmb
}
}
impl From<ReducedPwmPin<PwmB>> for ReducedPwmPin<PwmA> {
fn from(other: ReducedPwmPin<PwmB>) -> Self {
let mut pwmb = Self {
reg: other.reg,
pwm_base: other.pwm_base,
pin_id: other.pin_id,
mode: PhantomData,
};
pwmb.enable_pwm_a();
pwmb
}
}
//==================================================================================================
// PWMB implementations
//==================================================================================================
impl<PIN: TimPin, TIM: ValidTim> PwmPin<PIN, TIM, PwmB>
where
(PIN, TIM): ValidTimAndPin<PIN, TIM>,
{
pwmb_func!();
}
impl ReducedPwmPin<PwmB> {
pwmb_func!();
}
//==================================================================================================
// Embedded HAL implementation: PWMA only
//==================================================================================================
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::ErrorType for PwmPin<Pin, Tim> {
type Error = Infallible;
}
impl embedded_hal::pwm::ErrorType for ReducedPwmPin {
type Error = Infallible;
}
impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
#[inline]
fn max_duty_cycle(&self) -> u16 {
DUTY_MAX
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.pwm_base.current_duty = duty;
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
/ DUTY_MAX as u64;
self.reg
.reg()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())
}
}
impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin, Tim> {
#[inline]
fn max_duty_cycle(&self) -> u16 {
DUTY_MAX
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.pwm_base.current_duty = duty;
let pwma_val: u64 = (self.pwm_base.current_rst_val as u64
* (DUTY_MAX as u64 - self.pwm_base.current_duty as u64))
/ DUTY_MAX as u64;
self.reg
.reg()
.pwma_value()
.write(|w| unsafe { w.bits(pwma_val as u32) });
Ok(())
}
}
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
///
/// Please note that this might load a lot of floating point code because this processor does not
/// have a FPU
pub fn get_duty_from_percent(percent: f32) -> u16 {
if percent > 1.0 {
DUTY_MAX
} else if percent <= 0.0 {
0
} else {
(percent * DUTY_MAX as f32) as u16
}
}
pub use vorago_shared_periphs::pwm::*;

View File

@ -1,891 +1,11 @@
//! API for the SPI peripheral
//! API for the SPI peripheral.
//!
//! The main abstraction provided by this module is the [Spi] an structure.
//! It provides the [SpiBus trait](https://docs.rs/embedded-hal/latest/embedded_hal/spi/trait.SpiBus.html),
//! but also offer a low level interface via the [SpiLowLevel] trait.
//!
//! ## Examples
//!
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs)
use core::{convert::Infallible, marker::PhantomData, ops::Deref};
use embedded_hal::spi::Mode;
use crate::{
clock::{PeripheralSelect, SyscfgExt},
gpio::{
AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1,
PC10, PC11, PC7, PC8, PC9, PE10, PE11, PE12, PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9,
PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PG2, PG3, PG4,
},
pac,
time::Hertz,
typelevel::{NoneT, Sealed},
};
//==================================================================================================
// Defintions
//==================================================================================================
// FIFO has a depth of 16.
const FILL_DEPTH: usize = 12;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum HwChipSelectId {
Id0 = 0,
Id1 = 1,
Id2 = 2,
Id3 = 3,
Id4 = 4,
Id5 = 5,
Id6 = 6,
Id7 = 7,
Invalid = 0xff,
}
#[derive(Debug)]
pub enum SpiId {
Spi0,
Spi1,
Spi2,
Spi3,
Invalid,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum WordSize {
OneBit = 0x00,
FourBits = 0x03,
EightBits = 0x07,
SixteenBits = 0x0f,
}
//==================================================================================================
// Pin type definitions
//==================================================================================================
pub trait PinSck<SPI>: Sealed {}
pub trait PinMosi<SPI>: Sealed {}
pub trait PinMiso<SPI>: Sealed {}
pub trait HwCsProvider: Sealed {
const CS_ID: HwChipSelectId;
const SPI_ID: SpiId;
}
pub trait OptionalHwCs<Spi>: HwCsProvider + Sealed {}
macro_rules! hw_cs_pins {
($SPIx:path, $portId: path:
$(
($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident),
)+
) => {
$(
impl HwCsProvider for Pin<$PXx, $AFx> {
const CS_ID: HwChipSelectId = $HwCsIdent;
const SPI_ID: SpiId = $portId;
}
impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
pub type $typedef = Pin<$PXx, $AFx>;
)+
};
}
impl HwCsProvider for NoneT {
const CS_ID: HwChipSelectId = HwChipSelectId::Invalid;
const SPI_ID: SpiId = SpiId::Invalid;
}
impl OptionalHwCs<pac::Spi0> for NoneT {}
impl OptionalHwCs<pac::Spi1> for NoneT {}
impl OptionalHwCs<pac::Spi2> for NoneT {}
impl OptionalHwCs<pac::Spi3> for NoneT {}
// SPI 0
impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
impl PinMosi<pac::Spi0> for Pin<PC1, AltFunc1> {}
impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
// SPI 1
impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
impl PinMosi<pac::Spi1> for Pin<PC11, AltFunc2> {}
impl PinMiso<pac::Spi1> for Pin<PC10, AltFunc2> {}
impl PinSck<pac::Spi1> for Pin<PG3, AltFunc2> {}
impl PinMiso<pac::Spi1> for Pin<PG4, AltFunc2> {}
impl PinSck<pac::Spi1> for Pin<PE13, AltFunc2> {}
impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
// SPI 2
impl PinSck<pac::Spi2> for Pin<PA5, AltFunc2> {}
impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
// SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
// SPI 0 HW CS pins
hw_cs_pins!(
pac::Spi0, SpiId::Spi0:
(PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
(PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
(PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
(PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0),
);
hw_cs_pins!(
pac::Spi1, SpiId::Spi1:
(PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb),
(PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb),
(PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb),
(PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
(PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
(PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
(PB1, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pb),
(PB0, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pb),
(PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
(PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
(PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
(PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe),
(PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe),
(PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
(PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
(PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
(PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
(PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
(PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf),
(PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
);
hw_cs_pins!(
pac::Spi2, SpiId::Spi2:
(PA4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pa),
(PA3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pa),
(PA2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pa),
(PA1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pa),
(PA0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pa),
(PA8, AltFunc2, HwChipSelectId::Id6, HwCs6Spi2Pa),
(PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
(PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
(PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
(PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf),
(PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf),
(PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf),
);
//==================================================================================================
// Config
//==================================================================================================
pub trait TransferConfigProvider {
fn sod(&mut self, sod: bool);
fn blockmode(&mut self, blockmode: bool);
fn mode(&mut self, mode: Mode);
fn frequency(&mut self, spi_clk: Hertz);
fn hw_cs_id(&self) -> u8;
}
/// This struct contains all configuration parameter which are transfer specific
/// and might change for transfers to different SPI slaves
#[derive(Copy, Clone)]
pub struct TransferConfig<HwCs> {
pub spi_clk: Hertz,
pub mode: Mode,
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
/// false
pub hw_cs: Option<HwCs>,
pub sod: bool,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words
pub blockmode: bool,
}
/// Type erased variant of the transfer configuration. This is required to avoid generics in
/// the SPI constructor.
pub struct ErasedTransferConfig {
pub spi_clk: Hertz,
pub mode: Mode,
pub sod: bool,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words
pub blockmode: bool,
pub hw_cs: HwChipSelectId,
}
impl TransferConfig<NoneT> {
pub fn new_no_hw_cs(spi_clk: impl Into<Hertz>, mode: Mode, blockmode: bool, sod: bool) -> Self {
TransferConfig {
spi_clk: spi_clk.into(),
mode,
hw_cs: None,
sod,
blockmode,
}
}
}
impl<HwCs: HwCsProvider> TransferConfig<HwCs> {
pub fn new(
spi_clk: impl Into<Hertz>,
mode: Mode,
hw_cs: Option<HwCs>,
blockmode: bool,
sod: bool,
) -> Self {
TransferConfig {
spi_clk: spi_clk.into(),
mode,
hw_cs,
sod,
blockmode,
}
}
pub fn downgrade(self) -> ErasedTransferConfig {
ErasedTransferConfig {
spi_clk: self.spi_clk,
mode: self.mode,
sod: self.sod,
blockmode: self.blockmode,
hw_cs: HwCs::CS_ID,
}
}
}
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
/// Slave Output Disable
fn sod(&mut self, sod: bool) {
self.sod = sod;
}
fn blockmode(&mut self, blockmode: bool) {
self.blockmode = blockmode;
}
fn mode(&mut self, mode: Mode) {
self.mode = mode;
}
fn frequency(&mut self, spi_clk: Hertz) {
self.spi_clk = spi_clk;
}
fn hw_cs_id(&self) -> u8 {
HwCs::CS_ID as u8
}
}
#[derive(Default)]
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
pub struct SpiConfig {
/// Serial clock rate divider. Together with the CLKPRESCALE register, it determines
/// the SPI clock rate in master mode. 0 by default. Specifying a higher value
/// limits the maximum attainable SPI speed
pub ser_clock_rate_div: u8,
/// By default, configure SPI for master mode (ms == false)
ms: bool,
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
pub slave_output_disable: bool,
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
pub loopback_mode: bool,
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
pub master_delayer_capture: bool,
}
impl SpiConfig {
pub fn loopback(mut self, enable: bool) -> Self {
self.loopback_mode = enable;
self
}
pub fn master_mode(mut self, master: bool) -> Self {
self.ms = !master;
self
}
pub fn slave_output_disable(mut self, sod: bool) -> Self {
self.slave_output_disable = sod;
self
}
}
//==================================================================================================
// Word Size
//==================================================================================================
/// Configuration trait for the Word Size used by the SPI peripheral
pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
const MASK: u32;
fn word_reg() -> u8;
}
impl WordProvider for u8 {
const MASK: u32 = 0xff;
fn word_reg() -> u8 {
0x07
}
}
impl WordProvider for u16 {
const MASK: u32 = 0xffff;
fn word_reg() -> u8 {
0x0f
}
}
pub type SpiRegBlock = pac::spi0::RegisterBlock;
/// Common trait implemented by all PAC peripheral access structures. The register block
/// format is the same for all SPI blocks.
pub trait Instance: Deref<Target = SpiRegBlock> {
const IDX: u8;
const PERIPH_SEL: PeripheralSelect;
fn ptr() -> *const SpiRegBlock;
}
impl Instance for pac::Spi0 {
const IDX: u8 = 0;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
#[inline(always)]
fn ptr() -> *const SpiRegBlock {
Self::ptr()
}
}
impl Instance for pac::Spi1 {
const IDX: u8 = 1;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
#[inline(always)]
fn ptr() -> *const SpiRegBlock {
Self::ptr()
}
}
impl Instance for pac::Spi2 {
const IDX: u8 = 2;
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
#[inline(always)]
fn ptr() -> *const SpiRegBlock {
Self::ptr()
}
}
//==================================================================================================
// Spi
//==================================================================================================
pub struct SpiBase<SpiInstance, Word = u8> {
spi: SpiInstance,
cfg: SpiConfig,
apb1_clk: Hertz,
/// Fill word for read-only SPI transactions.
pub fill_word: Word,
blockmode: bool,
word: PhantomData<Word>,
}
pub struct Spi<SpiInstance, Pins, Word = u8> {
inner: SpiBase<SpiInstance, Word>,
pins: Pins,
}
fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
match mode {
embedded_hal::spi::MODE_0 => (false, false),
embedded_hal::spi::MODE_1 => (false, true),
embedded_hal::spi::MODE_2 => (true, false),
embedded_hal::spi::MODE_3 => (true, true),
}
}
impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
#[inline]
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
let clk_prescale =
self.apb1_clk.raw() / (spi_clk.into().raw() * (self.cfg.ser_clock_rate_div as u32 + 1));
self.spi
.clkprescale()
.write(|w| unsafe { w.bits(clk_prescale) });
}
#[inline]
pub fn cfg_mode(&mut self, mode: Mode) {
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
self.spi.ctrl0().modify(|_, w| {
w.spo().bit(cpo_bit);
w.sph().bit(cph_bit)
});
}
#[inline]
pub fn clear_tx_fifo(&self) {
self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn clear_rx_fifo(&self) {
self.spi.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
pub fn perid(&self) -> u32 {
self.spi.perid().read().bits()
}
#[inline]
pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
if hw_cs == HwChipSelectId::Invalid {
return;
}
self.spi.ctrl1().modify(|_, w| {
w.sod().clear_bit();
unsafe {
w.ss().bits(hw_cs as u8);
}
w
});
}
#[inline]
pub fn cfg_hw_cs_with_pin<HwCs: OptionalHwCs<SpiInstance>>(&mut self, _: &HwCs) {
self.cfg_hw_cs(HwCs::CS_ID);
}
#[inline]
pub fn cfg_hw_cs_disable(&mut self) {
self.spi.ctrl1().modify(|_, w| {
w.sod().set_bit();
w
});
}
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
&mut self,
transfer_cfg: &TransferConfig<HwCs>,
) {
self.cfg_clock(transfer_cfg.spi_clk);
self.cfg_mode(transfer_cfg.mode);
self.blockmode = transfer_cfg.blockmode;
self.spi.ctrl1().modify(|_, w| {
if transfer_cfg.sod {
w.sod().set_bit();
} else if transfer_cfg.hw_cs.is_some() {
w.sod().clear_bit();
unsafe {
w.ss().bits(HwCs::CS_ID as u8);
}
} else {
w.sod().clear_bit();
}
if transfer_cfg.blockmode {
w.blockmode().set_bit();
} else {
w.blockmode().clear_bit();
}
w
});
}
/// Sends a word to the slave
#[inline(always)]
fn send_blocking(&self, word: Word) {
// TODO: Upper limit for wait cycles to avoid complete hangups?
while self.spi.status().read().tnf().bit_is_clear() {}
self.send(word)
}
#[inline(always)]
fn send(&self, word: Word) {
self.spi.data().write(|w| unsafe { w.bits(word.into()) });
}
/// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call
#[inline(always)]
fn read_blocking(&self) -> Word {
// TODO: Upper limit for wait cycles to avoid complete hangups?
while self.spi.status().read().rne().bit_is_clear() {}
self.read_single_word()
}
#[inline(always)]
fn read_single_word(&self) -> Word {
(self.spi.data().read().bits() & Word::MASK)
.try_into()
.unwrap()
}
fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
if words.is_empty() {
return Ok(());
}
let mut status_reg = self.spi.status().read();
// Wait until all bytes have been transferred.
while status_reg.tfe().bit_is_clear() {
// Ignore all received read words.
if status_reg.rne().bit_is_set() {
self.clear_rx_fifo();
}
status_reg = self.spi.status().read();
}
// Ignore all received read words.
if status_reg.rne().bit_is_set() {
self.clear_rx_fifo();
}
Ok(())
}
fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize {
if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
}
// Fill the first half of the write FIFO
let mut current_write_idx = 0;
for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) {
self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx]));
current_write_idx += 1;
}
if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit())
}
current_write_idx
}
}
impl<
SpiI: Instance,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
Word: WordProvider,
> Spi<SpiI, (Sck, Miso, Mosi), Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
/// Create a new SPI struct
///
/// You can delete the pin type information by calling the
/// [`downgrade`](Self::downgrade) function
///
/// ## Arguments
/// * `spi` - SPI bus to use
/// * `pins` - Pins to be used for SPI transactions. These pins are consumed
/// to ensure the pins can not be used for other purposes anymore
/// * `spi_cfg` - Configuration specific to the SPI bus
/// * `transfer_cfg` - Optional initial transfer configuration which includes
/// configuration which can change across individual SPI transfers like SPI mode
/// or SPI clock. If only one device is connected, this configuration only needs
/// to be done once.
/// * `syscfg` - Can be passed optionally to enable the peripheral clock
pub fn new(
spi: SpiI,
pins: (Sck, Miso, Mosi),
clocks: &crate::clock::Clocks,
spi_cfg: SpiConfig,
syscfg: &mut pac::Sysconfig,
transfer_cfg: Option<&ErasedTransferConfig>,
) -> Self {
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
// This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
let SpiConfig {
ser_clock_rate_div,
ms,
slave_output_disable,
loopback_mode,
master_delayer_capture,
} = spi_cfg;
let mut mode = embedded_hal::spi::MODE_0;
let mut clk_prescale = 0x02;
let mut ss = 0;
let mut init_blockmode = false;
let apb1_clk = clocks.apb1();
if let Some(transfer_cfg) = transfer_cfg {
mode = transfer_cfg.mode;
clk_prescale =
apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1));
if transfer_cfg.hw_cs != HwChipSelectId::Invalid {
ss = transfer_cfg.hw_cs as u8;
}
init_blockmode = transfer_cfg.blockmode;
}
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
spi.ctrl0().write(|w| {
unsafe {
w.size().bits(Word::word_reg());
w.scrdv().bits(ser_clock_rate_div);
// Clear clock phase and polarity. Will be set to correct value for each
// transfer
w.spo().bit(cpo_bit);
w.sph().bit(cph_bit)
}
});
spi.ctrl1().write(|w| {
w.lbm().bit(loopback_mode);
w.sod().bit(slave_output_disable);
w.ms().bit(ms);
w.mdlycap().bit(master_delayer_capture);
w.blockmode().bit(init_blockmode);
unsafe { w.ss().bits(ss) }
});
spi.fifo_clr().write(|w| {
w.rxfifo().set_bit();
w.txfifo().set_bit()
});
spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) });
// Enable the peripheral as the last step as recommended in the
// programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit());
Spi {
inner: SpiBase {
spi,
cfg: spi_cfg,
apb1_clk,
fill_word: Default::default(),
blockmode: init_blockmode,
word: PhantomData,
},
pins,
}
}
#[inline]
pub fn cfg_clock(&mut self, spi_clk: impl Into<Hertz>) {
self.inner.cfg_clock(spi_clk);
}
#[inline]
pub fn cfg_mode(&mut self, mode: Mode) {
self.inner.cfg_mode(mode);
}
pub fn set_fill_word(&mut self, fill_word: Word) {
self.inner.fill_word = fill_word;
}
pub fn fill_word(&self) -> Word {
self.inner.fill_word
}
#[inline]
pub fn perid(&self) -> u32 {
self.inner.perid()
}
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(&mut self, transfer_cfg: &TransferConfig<HwCs>) {
self.inner.cfg_transfer(transfer_cfg);
}
/// Releases the SPI peripheral and associated pins
pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
(self.inner.spi, self.pins, self.inner.cfg)
}
pub fn downgrade(self) -> SpiBase<SpiI, Word> {
self.inner
}
}
/// Changing the word size also requires a type conversion
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self {
old_spi
.inner
.spi
.ctrl0()
.modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
Spi {
inner: SpiBase {
spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode,
fill_word: Default::default(),
apb1_clk: old_spi.inner.apb1_clk,
word: PhantomData,
},
pins: old_spi.pins,
}
}
}
/// Changing the word size also requires a type conversion
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
old_spi
.inner
.spi
.ctrl0()
.modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
Spi {
inner: SpiBase {
spi: old_spi.inner.spi,
cfg: old_spi.inner.cfg,
blockmode: old_spi.inner.blockmode,
apb1_clk: old_spi.inner.apb1_clk,
fill_word: Default::default(),
word: PhantomData,
},
pins: old_spi.pins,
}
}
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
type Error = Infallible;
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping(None);
loop {
if current_write_idx < words.len() {
self.send_blocking(self.fill_word);
current_write_idx += 1;
}
if current_read_idx < words.len() {
words[current_read_idx] = self.read_blocking();
current_read_idx += 1;
}
if current_read_idx >= words.len() && current_write_idx >= words.len() {
break;
}
}
Ok(())
}
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
while current_write_idx < words.len() {
self.send_blocking(words[current_write_idx]);
current_write_idx += 1;
// Ignore received words.
if self.spi.status().read().rne().bit_is_set() {
self.clear_rx_fifo();
}
}
Ok(())
}
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
self.transfer_preparation(write)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping(Some(write));
while current_read_idx < read.len() || current_write_idx < write.len() {
if current_write_idx < write.len() {
self.send_blocking(write[current_write_idx]);
current_write_idx += 1;
}
if current_read_idx < read.len() {
read[current_read_idx] = self.read_blocking();
current_read_idx += 1;
}
}
Ok(())
}
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.transfer_preparation(words)?;
let mut current_read_idx = 0;
let mut current_write_idx = self.initial_send_fifo_pumping(Some(words));
while current_read_idx < words.len() || current_write_idx < words.len() {
if current_write_idx < words.len() {
self.send_blocking(words[current_write_idx]);
current_write_idx += 1;
}
if current_read_idx < words.len() && current_read_idx < current_write_idx {
words[current_read_idx] = self.read_blocking();
current_read_idx += 1;
}
}
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
let status_reg = self.spi.status().read();
while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() {
if status_reg.rne().bit_is_set() {
self.read_single_word();
}
}
Ok(())
}
}
impl<
SpiI: Instance,
Word: WordProvider,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
> embedded_hal::spi::ErrorType for Spi<SpiI, (Sck, Miso, Mosi), Word>
{
type Error = Infallible;
}
impl<
SpiI: Instance,
Word: WordProvider,
Sck: PinSck<SpiI>,
Miso: PinMiso<SpiI>,
Mosi: PinMosi<SpiI>,
> embedded_hal::spi::SpiBus<Word> for Spi<SpiI, (Sck, Miso, Mosi), Word>
where
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
{
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.inner.read(words)
}
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
self.inner.write(words)
}
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
self.inner.transfer(read, write)
}
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.inner.transfer_in_place(words)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.inner.flush()
}
}
//! - [NVM library][crate::nvm]
pub use vorago_shared_periphs::spi::*;

View File

@ -2,804 +2,8 @@
//!
//! ## Examples
//!
//! TODO.
use core::cell::Cell;
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
pub use vorago_shared_periphs::timer::*;
use cortex_m::interrupt::Mutex;
use crate::clock::Clocks;
use crate::gpio::{
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3,
PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, PD0, PD1, PD10, PD11, PD12, PD13, PD14, PD15, PD2, PD3,
PD4, PD5, PD6, PD7, PD8, PD9, PE0, PE1, PE10, PE11, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5,
PE6, PE7, PE8, PE9, PF0, PF1, PF10, PF11, PF12, PF13, PF14, PF15, PF2, PF3, PF4, PF5, PF6, PF7,
PF8, PF9, PG0, PG1, PG2, PG3, PG6,
};
use crate::time::Hertz;
use crate::typelevel::Sealed;
use crate::{disable_interrupt, prelude::*};
use crate::{enable_interrupt, pac};
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
//==================================================================================================
// Defintions
//==================================================================================================
/// Interrupt events
//pub enum Event {
/// Timer timed out / count down ended
//TimeOut,
//}
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub struct CascadeCtrl {
/// Enable Cascade 0 signal active as a requirement for counting
pub enb_start_src_csd0: bool,
/// Invert Cascade 0, making it active low
pub inv_csd0: bool,
/// Enable Cascade 1 signal active as a requirement for counting
pub enb_start_src_csd1: bool,
/// Invert Cascade 1, making it active low
pub inv_csd1: bool,
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
pub dual_csd_op: bool,
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
/// cascade signal active, but once the counter is active, cascade control will be ignored
pub trg_csd0: bool,
/// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1
pub trg_csd1: bool,
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
/// to the REQ_STOP control bit, but signalled by a Cascade source
pub enb_stop_src_csd2: bool,
/// Invert Cascade 2, making it active low
pub inv_csd2: bool,
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
/// ignored
pub trg_csd2: bool,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CascadeSel {
Sel0 = 0,
Sel1 = 1,
Sel2 = 2,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCascadeSourceId;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CascadeSource {
PortA(u8),
PortB(u8),
PortC(u8),
PortD(u8),
PortE(u8),
Tim(u8),
TxEv,
AdcIrq,
RomSbe,
RomMbe,
Ram0Sbe,
Ram0Mbe,
Ram1Sbe,
Ram2Mbe,
WdogIrq,
}
impl CascadeSource {
fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
let port_check = |base: u8, id: u8| {
if id > 15 {
return Err(InvalidCascadeSourceId);
}
Ok(base + id)
};
match self {
CascadeSource::PortA(id) => port_check(0, *id),
CascadeSource::PortB(id) => port_check(16, *id),
CascadeSource::PortC(id) => port_check(32, *id),
CascadeSource::PortD(id) => port_check(48, *id),
CascadeSource::PortE(id) => port_check(65, *id),
CascadeSource::Tim(id) => {
if *id > 23 {
return Err(InvalidCascadeSourceId);
}
Ok(80 + id)
}
CascadeSource::TxEv => Ok(104),
CascadeSource::AdcIrq => Ok(105),
CascadeSource::RomSbe => Ok(106),
CascadeSource::RomMbe => Ok(106),
CascadeSource::Ram0Sbe => Ok(108),
CascadeSource::Ram0Mbe => Ok(109),
CascadeSource::Ram1Sbe => Ok(110),
CascadeSource::Ram2Mbe => Ok(111),
CascadeSource::WdogIrq => Ok(112),
}
}
}
//==================================================================================================
// Valid TIM and PIN combinations
//==================================================================================================
pub trait TimPin {
const DYN: DynPinId;
}
pub trait ValidTim {
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
const TIM_ID: u8;
const IRQ: pac::Interrupt;
fn clock(clocks: &Clocks) -> Hertz {
if Self::TIM_ID <= 15 {
clocks.apb1()
} else {
clocks.apb2()
}
}
}
macro_rules! tim_markers {
(
$(
($TimX:path, $id:expr, $Irq:path),
)+
) => {
$(
impl ValidTim for $TimX {
const TIM_ID: u8 = $id;
const IRQ: pac::Interrupt = $Irq;
}
)+
};
}
tim_markers!(
(pac::Tim0, 0, pac::Interrupt::TIM0),
(pac::Tim1, 1, pac::Interrupt::TIM1),
(pac::Tim2, 2, pac::Interrupt::TIM2),
(pac::Tim3, 3, pac::Interrupt::TIM3),
(pac::Tim4, 4, pac::Interrupt::TIM4),
(pac::Tim5, 5, pac::Interrupt::TIM5),
(pac::Tim6, 6, pac::Interrupt::TIM6),
(pac::Tim7, 7, pac::Interrupt::TIM7),
(pac::Tim8, 8, pac::Interrupt::TIM8),
(pac::Tim9, 9, pac::Interrupt::TIM9),
(pac::Tim10, 10, pac::Interrupt::TIM10),
(pac::Tim11, 11, pac::Interrupt::TIM11),
(pac::Tim12, 12, pac::Interrupt::TIM12),
(pac::Tim13, 13, pac::Interrupt::TIM13),
(pac::Tim14, 14, pac::Interrupt::TIM14),
(pac::Tim15, 15, pac::Interrupt::TIM15),
(pac::Tim16, 16, pac::Interrupt::TIM16),
(pac::Tim17, 17, pac::Interrupt::TIM17),
(pac::Tim18, 18, pac::Interrupt::TIM18),
(pac::Tim19, 19, pac::Interrupt::TIM19),
(pac::Tim20, 20, pac::Interrupt::TIM20),
(pac::Tim21, 21, pac::Interrupt::TIM21),
(pac::Tim22, 22, pac::Interrupt::TIM22),
(pac::Tim23, 23, pac::Interrupt::TIM23),
);
pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
macro_rules! valid_pin_and_tims {
(
$(
($PinX:ident, $AltFunc:ident, $TimX:path),
)+
) => {
$(
impl TimPin for Pin<$PinX, $AltFunc>
where
$PinX: PinId,
{
const DYN: DynPinId = $PinX::DYN;
}
impl<
PinInstance: TimPin,
Tim: ValidTim
> ValidTimAndPin<PinInstance, Tim> for (Pin<$PinX, $AltFunc>, $TimX)
where
Pin<$PinX, $AltFunc>: TimPin,
$PinX: PinId,
{
}
impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
)+
};
}
valid_pin_and_tims!(
(PA0, AltFunc1, pac::Tim0),
(PA1, AltFunc1, pac::Tim1),
(PA2, AltFunc1, pac::Tim2),
(PA3, AltFunc1, pac::Tim3),
(PA4, AltFunc1, pac::Tim4),
(PA5, AltFunc1, pac::Tim5),
(PA6, AltFunc1, pac::Tim6),
(PA7, AltFunc1, pac::Tim7),
(PA10, AltFunc2, pac::Tim23),
(PA11, AltFunc2, pac::Tim22),
(PA12, AltFunc2, pac::Tim21),
(PA13, AltFunc2, pac::Tim20),
(PA14, AltFunc2, pac::Tim19),
(PA15, AltFunc2, pac::Tim18),
(PB0, AltFunc2, pac::Tim17),
(PB1, AltFunc2, pac::Tim16),
(PB2, AltFunc2, pac::Tim15),
(PB3, AltFunc2, pac::Tim14),
(PB4, AltFunc2, pac::Tim13),
(PB5, AltFunc2, pac::Tim12),
(PB6, AltFunc2, pac::Tim11),
(PB7, AltFunc2, pac::Tim10),
(PB8, AltFunc2, pac::Tim9),
(PB9, AltFunc2, pac::Tim8),
(PB10, AltFunc2, pac::Tim7),
(PB11, AltFunc2, pac::Tim6),
(PB12, AltFunc2, pac::Tim5),
(PB13, AltFunc2, pac::Tim4),
(PB14, AltFunc2, pac::Tim3),
(PB15, AltFunc2, pac::Tim2),
(PC0, AltFunc2, pac::Tim1),
(PC1, AltFunc2, pac::Tim0),
(PD0, AltFunc2, pac::Tim0),
(PD1, AltFunc2, pac::Tim1),
(PD2, AltFunc2, pac::Tim2),
(PD3, AltFunc2, pac::Tim3),
(PD4, AltFunc2, pac::Tim4),
(PD5, AltFunc2, pac::Tim5),
(PD6, AltFunc2, pac::Tim6),
(PD7, AltFunc2, pac::Tim7),
(PD8, AltFunc2, pac::Tim8),
(PD9, AltFunc2, pac::Tim9),
(PD10, AltFunc2, pac::Tim10),
(PD11, AltFunc2, pac::Tim11),
(PD12, AltFunc2, pac::Tim12),
(PD13, AltFunc2, pac::Tim13),
(PD14, AltFunc2, pac::Tim14),
(PD15, AltFunc2, pac::Tim15),
(PE0, AltFunc2, pac::Tim16),
(PE1, AltFunc2, pac::Tim17),
(PE2, AltFunc2, pac::Tim18),
(PE3, AltFunc2, pac::Tim19),
(PE4, AltFunc2, pac::Tim20),
(PE5, AltFunc2, pac::Tim21),
(PE6, AltFunc2, pac::Tim22),
(PE7, AltFunc2, pac::Tim23),
(PE8, AltFunc3, pac::Tim16),
(PE9, AltFunc3, pac::Tim17),
(PE10, AltFunc3, pac::Tim18),
(PE11, AltFunc3, pac::Tim19),
(PE12, AltFunc3, pac::Tim20),
(PE13, AltFunc3, pac::Tim21),
(PE14, AltFunc3, pac::Tim22),
(PE15, AltFunc3, pac::Tim23),
(PF0, AltFunc3, pac::Tim0),
(PF1, AltFunc3, pac::Tim1),
(PF2, AltFunc3, pac::Tim2),
(PF3, AltFunc3, pac::Tim3),
(PF4, AltFunc3, pac::Tim4),
(PF5, AltFunc3, pac::Tim5),
(PF6, AltFunc3, pac::Tim6),
(PF7, AltFunc3, pac::Tim7),
(PF8, AltFunc3, pac::Tim8),
(PF9, AltFunc3, pac::Tim9),
(PF10, AltFunc3, pac::Tim10),
(PF11, AltFunc3, pac::Tim11),
(PF12, AltFunc3, pac::Tim12),
(PF13, AltFunc2, pac::Tim19),
(PF14, AltFunc2, pac::Tim20),
(PF15, AltFunc2, pac::Tim21),
(PG0, AltFunc2, pac::Tim22),
(PG1, AltFunc2, pac::Tim23),
(PG2, AltFunc1, pac::Tim9),
(PG3, AltFunc1, pac::Tim10),
(PG6, AltFunc1, pac::Tim12),
);
//==================================================================================================
// Register Interface for TIM registers and TIM pins
//==================================================================================================
/// Clear the reset bit of the TIM, holding it in reset
///
/// # Safety
///
/// Only the bit related to the corresponding TIM peripheral is modified
#[inline]
fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
syscfg
.tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) })
}
#[inline]
fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
syscfg
.tim_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) })
}
pub type TimRegBlock = pac::tim0::RegisterBlock;
/// Register interface.
///
/// This interface provides valid TIM pins a way to access their corresponding TIM
/// registers
///
/// # Safety
///
/// Users should only implement the [`tim_id`] function. No default function
/// implementations should be overridden. The implementing type must also have
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
/// pin ID is a singleton.
pub(super) unsafe trait TimRegInterface {
fn tim_id(&self) -> u8;
const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
/// All 24 TIM blocks are identical. This helper functions returns the correct
/// memory mapped peripheral depending on the TIM ID.
#[inline(always)]
fn reg(&self) -> &TimRegBlock {
unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
}
#[inline(always)]
fn mask_32(&self) -> u32 {
1 << self.tim_id()
}
/// Clear the reset bit of the TIM, holding it in reset
///
/// # Safety
///
/// Only the bit related to the corresponding TIM peripheral is modified
#[inline]
#[allow(dead_code)]
fn assert_tim_reset(&self, syscfg: &mut pac::Sysconfig) {
assert_tim_reset(syscfg, self.tim_id());
}
#[inline]
#[allow(dead_code)]
fn deassert_time_reset(&self, syscfg: &mut pac::Sysconfig) {
deassert_tim_reset(syscfg, self.tim_id());
}
}
/// Provide a safe register interface for [`ValidTimAndPin`]s
///
/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to
/// access the corresponding registers.
pub(super) struct TimAndPinRegister<Pin: TimPin, Tim: ValidTim> {
pin: Pin,
tim: Tim,
}
pub(super) struct TimRegister<TIM: ValidTim> {
tim: TIM,
}
impl<TIM: ValidTim> TimRegister<TIM> {
#[inline]
pub(super) unsafe fn new(tim: TIM) -> Self {
TimRegister { tim }
}
pub(super) fn release(self) -> TIM {
self.tim
}
}
unsafe impl<Tim: ValidTim> TimRegInterface for TimRegister<Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::TIM_ID
}
}
impl<Pin: TimPin, Tim: ValidTim> TimAndPinRegister<Pin, Tim>
where
(Pin, Tim): ValidTimAndPin<Pin, Tim>,
{
#[inline]
pub(super) unsafe fn new(pin: Pin, tim: Tim) -> Self {
TimAndPinRegister { pin, tim }
}
pub(super) fn release(self) -> (Pin, Tim) {
(self.pin, self.tim)
}
}
unsafe impl<Pin: TimPin, Tim: ValidTim> TimRegInterface for TimAndPinRegister<Pin, Tim> {
#[inline(always)]
fn tim_id(&self) -> u8 {
Tim::TIM_ID
}
}
pub(super) struct TimDynRegister {
tim_id: u8,
#[allow(dead_code)]
pin_id: DynPinId,
}
impl<Pin: TimPin, Tim: ValidTim> From<TimAndPinRegister<Pin, Tim>> for TimDynRegister {
fn from(_reg: TimAndPinRegister<Pin, Tim>) -> Self {
Self {
tim_id: Tim::TIM_ID,
pin_id: Pin::DYN,
}
}
}
unsafe impl TimRegInterface for TimDynRegister {
#[inline(always)]
fn tim_id(&self) -> u8 {
self.tim_id
}
}
//==================================================================================================
// Timers
//==================================================================================================
/// Hardware timers
pub struct CountdownTimer<TIM: ValidTim> {
tim: TimRegister<TIM>,
curr_freq: Hertz,
clock: Hertz,
rst_val: u32,
last_cnt: u32,
listening: bool,
}
#[inline]
fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
}
unsafe impl<TIM: ValidTim> TimRegInterface for CountdownTimer<TIM> {
#[inline]
fn tim_id(&self) -> u8 {
TIM::TIM_ID
}
}
impl<Tim: ValidTim> CountdownTimer<Tim> {
/// Create a new countdown timer, but does not start it.
///
/// You can use [Self::start] to start the countdown timer, and you may optionally call
/// [Self::listen] to enable interrupts for the TIM peripheral as well.
pub fn new(syscfg: &mut pac::Sysconfig, tim: Tim, clocks: &Clocks) -> Self {
enable_tim_clk(syscfg, Tim::TIM_ID);
assert_tim_reset(syscfg, Tim::TIM_ID);
cortex_m::asm::nop();
cortex_m::asm::nop();
deassert_tim_reset(syscfg, Tim::TIM_ID);
CountdownTimer {
tim: unsafe { TimRegister::new(tim) },
clock: Tim::clock(clocks),
rst_val: 0,
curr_freq: 0_u32.Hz(),
listening: false,
last_cnt: 0,
}
}
#[inline]
pub fn start(&mut self, timeout: impl Into<Hertz>) {
self.load(timeout);
self.enable();
}
/// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the
/// IRQSEL peripheral for the provided interrupt and unmasks the interrupt
#[inline]
pub fn listen(&mut self) {
self.listening = true;
self.enable_interrupt();
unsafe { enable_interrupt(Tim::IRQ) }
}
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
/// flag and restart the time if configured correctly
pub fn wait(&mut self) -> nb::Result<(), void::Void> {
let cnt = self.tim.reg().cnt_value().read().bits();
if (cnt > self.last_cnt) || cnt == 0 {
self.last_cnt = self.rst_val;
Ok(())
} else {
self.last_cnt = cnt;
Err(nb::Error::WouldBlock)
}
}
#[inline]
pub fn stop(&mut self) {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
}
#[inline]
pub fn unlisten(&mut self) {
self.listening = true;
self.disable_interrupt();
disable_interrupt(Tim::IRQ);
}
#[inline(always)]
pub fn enable_interrupt(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().set_bit());
}
#[inline(always)]
pub fn disable_interrupt(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit());
}
#[inline]
pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
syscfg
.tim_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) });
self.tim.release()
}
/// Load the count down timer with a timeout but do not start it.
pub fn load(&mut self, timeout: impl Into<Hertz>) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
self.curr_freq = timeout.into();
self.rst_val = self.clock.raw() / self.curr_freq.raw();
self.set_reload(self.rst_val);
self.set_count(0);
}
#[inline(always)]
pub fn set_reload(&mut self, val: u32) {
self.tim.reg().rst_value().write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn set_count(&mut self, val: u32) {
self.tim.reg().cnt_value().write(|w| unsafe { w.bits(val) });
}
#[inline(always)]
pub fn count(&self) -> u32 {
self.tim.reg().cnt_value().read().bits()
}
#[inline(always)]
pub fn enable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit());
}
#[inline(always)]
pub fn disable(&mut self) {
self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit());
}
/// Disable the counter, setting both enable and active bit to 0
#[inline]
pub fn auto_disable(self, enable: bool) -> Self {
if enable {
self.tim
.reg()
.ctrl()
.modify(|_, w| w.auto_disable().set_bit());
} else {
self.tim
.reg()
.ctrl()
.modify(|_, w| w.auto_disable().clear_bit());
}
self
}
/// This option only applies when the Auto-Disable functionality is 0.
///
/// The active bit is changed to 0 when count reaches 0, but the counter stays
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
#[inline]
pub fn auto_deactivate(self, enable: bool) -> Self {
if enable {
self.tim
.reg()
.ctrl()
.modify(|_, w| w.auto_deactivate().set_bit());
} else {
self.tim
.reg()
.ctrl()
.modify(|_, w| w.auto_deactivate().clear_bit());
}
self
}
/// Configure the cascade parameters
#[inline]
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
self.tim.reg().csd_ctrl().write(|w| {
w.csden0().bit(ctrl.enb_start_src_csd0);
w.csdinv0().bit(ctrl.inv_csd0);
w.csden1().bit(ctrl.enb_start_src_csd1);
w.csdinv1().bit(ctrl.inv_csd1);
w.dcasop().bit(ctrl.dual_csd_op);
w.csdtrg0().bit(ctrl.trg_csd0);
w.csdtrg1().bit(ctrl.trg_csd1);
w.csden2().bit(ctrl.enb_stop_src_csd2);
w.csdinv2().bit(ctrl.inv_csd2);
w.csdtrg2().bit(ctrl.trg_csd2)
});
}
#[inline]
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.cascade0()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
#[inline]
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.cascade1()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
#[inline]
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
let id = src.id()?;
self.tim
.reg()
.cascade2()
.write(|w| unsafe { w.cassel().bits(id) });
Ok(())
}
#[inline]
pub fn curr_freq(&self) -> Hertz {
self.curr_freq
}
#[inline]
pub fn listening(&self) -> bool {
self.listening
}
}
impl<Tim: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<Tim> {
fn delay_ns(&mut self, ns: u32) {
let ticks = (u64::from(ns)) * (u64::from(self.clock.raw())) / 1_000_000_000;
let full_cycles = ticks >> 32;
let mut last_count;
let mut new_count;
if full_cycles > 0 {
self.set_reload(u32::MAX);
self.set_count(u32::MAX);
self.enable();
for _ in 0..full_cycles {
// Always ensure that both values are the same at the start.
new_count = self.count();
last_count = new_count;
loop {
new_count = self.count();
if new_count == 0 {
// Wait till timer has wrapped.
while self.count() == 0 {
cortex_m::asm::nop()
}
break;
}
// Timer has definitely wrapped.
if new_count > last_count {
break;
}
last_count = new_count;
}
}
}
let ticks = (ticks & u32::MAX as u64) as u32;
self.disable();
if ticks > 1 {
self.set_reload(ticks);
self.set_count(ticks);
self.enable();
last_count = ticks;
loop {
new_count = self.count();
if new_count == 0 || (new_count > last_count) {
break;
}
last_count = new_count;
}
}
self.disable();
}
}
//==================================================================================================
// MS tick implementations
//==================================================================================================
// Set up a millisecond timer on TIM0. Please note that the user still has to provide an IRQ handler
// which should call [default_ms_irq_handler].
pub fn set_up_ms_tick<Tim: ValidTim>(
sys_cfg: &mut pac::Sysconfig,
tim: Tim,
clocks: &Clocks,
) -> CountdownTimer<Tim> {
let mut ms_timer = CountdownTimer::new(sys_cfg, tim, clocks);
ms_timer.listen();
ms_timer.start(1000.Hz());
ms_timer
}
/// This function can be called in a specified interrupt handler to increment
/// the MS counter
pub fn default_ms_irq_handler() {
cortex_m::interrupt::free(|cs| {
let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1;
MS_COUNTER.borrow(cs).set(ms);
});
}
/// Get the current MS tick count
pub fn get_ms_ticks() -> u32 {
cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get())
}
pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);
impl<Tim: ValidTim> DelayMs<Tim> {
pub fn new(timer: CountdownTimer<Tim>) -> Option<Self> {
if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
return None;
}
Some(Self(timer))
}
}
/// This assumes that the user has already set up a MS tick timer with [set_up_ms_tick]
impl embedded_hal::delay::DelayNs for DelayMs {
fn delay_ns(&mut self, ns: u32) {
let ns_as_ms = ns / 1_000_000;
if self.0.curr_freq() != Hertz::from_raw(1000) || !self.0.listening() {
return;
}
let start_time = get_ms_ticks();
while get_ms_ticks() - start_time < ns_as_ms {
cortex_m::asm::nop();
}
}
}
pub const TIM_IRQ_OFFSET: usize = 48;

View File

@ -1,155 +0,0 @@
//! Module supporting type-level programming
//!
//! This module is identical to the
//! [atsamd typelevel](https://docs.rs/atsamd-hal/latest/atsamd_hal/typelevel/index.html).
use core::ops::{Add, Sub};
use typenum::{Add1, Bit, Sub1, UInt, Unsigned, B1, U0};
mod private {
/// Super trait used to mark traits with an exhaustive set of
/// implementations
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for i8 {}
impl Sealed for u16 {}
impl Sealed for i16 {}
impl Sealed for u32 {}
impl Sealed for i32 {}
impl Sealed for f32 {}
/// Mapping from an instance of a countable type to its successor
pub trait Increment {
/// Successor type of `Self`
type Inc;
/// Consume an instance of `Self` and return its successor
fn inc(self) -> Self::Inc;
}
/// Mapping from an instance of a countable type to its predecessor
pub trait Decrement {
/// Predecessor type of `Self`
type Dec;
/// Consume an instance of `Self` and return its predecessor
fn dec(self) -> Self::Dec;
}
}
pub(crate) use private::Decrement as PrivateDecrement;
pub(crate) use private::Increment as PrivateIncrement;
pub(crate) use private::Sealed;
/// Type-level version of the [`None`] variant
#[derive(Default)]
pub struct NoneT;
impl Sealed for NoneT {}
//==============================================================================
// Is
//==============================================================================
/// Marker trait for type identity
///
/// This trait is used as part of the [`AnyKind`] trait pattern. It represents
/// the concept of type identity, because all implementors have
/// `<Self as Is>::Type == Self`. When used as a trait bound with a specific
/// type, it guarantees that the corresponding type parameter is exactly the
/// specific type. Stated differently, it guarantees that `T == Specific` in
/// the following example.
///
/// ```ignore
/// where T: Is<Type = Specific>
/// ```
///
/// Moreover, the super traits guarantee that any instance of or reference to a
/// type `T` can be converted into the `Specific` type.
///
/// ```ignore
/// fn example<T>(mut any: T)
/// where
/// T: Is<Type = Specific>,
/// {
/// let specific_mut: &mut Specific = any.as_mut();
/// let specific_ref: &Specific = any.as_ref();
/// let specific: Specific = any.into();
/// }
/// ```
///
/// [`AnyKind`]: #anykind-trait-pattern
pub trait Is
where
Self: Sealed,
Self: From<IsType<Self>>,
Self: Into<IsType<Self>>,
Self: AsRef<IsType<Self>>,
Self: AsMut<IsType<Self>>,
{
type Type;
}
/// Type alias for [`Is::Type`]
pub type IsType<T> = <T as Is>::Type;
impl<T> Is for T
where
T: Sealed + AsRef<T> + AsMut<T>,
{
type Type = T;
}
//==============================================================================
// Counting
//==============================================================================
/// Implement `Sealed` for [`U0`]
impl Sealed for U0 {}
/// Implement `Sealed` for all type-level, [`Unsigned`] integers *except* [`U0`]
impl<U: Unsigned, B: Bit> Sealed for UInt<U, B> {}
/// Trait mapping each countable type to its successor
///
/// This trait maps each countable type to its corresponding successor type. The
/// actual implementation of this trait is contained within `PrivateIncrement`.
/// Access to `PrivateIncrement` is restricted, so that safe HAL APIs can be
/// built with it.
pub trait Increment: PrivateIncrement {}
impl<T: PrivateIncrement> Increment for T {}
/// Trait mapping each countable type to its predecessor
///
/// This trait maps each countable type to its corresponding predecessor type.
/// The actual implementation of this trait is contained within
/// `PrivateDecrement`. Access to `PrivateDecrement` is restricted, so that safe
/// HAL APIs can be built with it.
pub trait Decrement: PrivateDecrement {}
impl<T: PrivateDecrement> Decrement for T {}
impl<N> PrivateIncrement for N
where
N: Unsigned + Add<B1>,
Add1<N>: Unsigned,
{
type Inc = Add1<N>;
#[inline]
fn inc(self) -> Self::Inc {
Self::Inc::default()
}
}
impl<N> PrivateDecrement for N
where
N: Unsigned + Sub<B1>,
Sub1<N>: Unsigned,
{
type Dec = Sub1<N>;
#[inline]
fn dec(self) -> Self::Dec {
Self::Dec::default()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
//! # API for the UART peripheral
//!
//! The core of this API are the [Uart], [Rx] and [Tx] structures.
//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver
//! using interrupts.
//!
//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART
//! peripheral.
//!
//! ## Examples
//!
//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/rtic/src/bin/uart-echo-rtic.rs)
//! - [Flashloader exposing a CCSDS interface via UART](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
pub use vorago_shared_periphs::uart::*;

View File

@ -3,21 +3,26 @@
//! ## Examples
//!
//! - [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 vorago_shared_periphs::{
enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
};
use crate::{disable_interrupt, enable_interrupt};
use crate::time::Hertz;
use crate::{clock::Clocks, pac};
use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
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,33 +30,22 @@ 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 {
pub fn new(
&self,
syscfg: &mut pac::Sysconfig,
wdt: pac::WatchDog,
clocks: &Clocks,
wdt_freq_ms: u32,
) -> Self {
Self::start(syscfg, wdt, clocks, wdt_freq_ms)
impl Wdt {
pub fn new(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
Self::start(wdt, clocks, wdt_freq_ms)
}
pub fn start(
syscfg: &mut pac::Sysconfig,
wdt: pac::WatchDog,
clocks: &Clocks,
wdt_freq_ms: u32,
) -> Self {
syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog);
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Watchdog);
pub fn start(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
enable_peripheral_clock(PeripheralSelect::Watchdog);
reset_peripheral_for_cycles(PeripheralSelect::Watchdog, 2);
let wdt_clock = clocks.apb2();
let mut wdt_ctrl = Self {
@ -76,12 +70,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 = "0.3", 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

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