150 Commits

Author SHA1 Message Date
adee40d953 init new shared periph crate 2025-04-24 12:42:55 +02:00
5b2ded51f9 remove some old cruft 2025-04-12 16:55:45 +02:00
bab979ece3 clippy, fmt, docs 2025-04-12 16:54:57 +02:00
0515ca5eaa defmt works properly now 2025-04-12 16:45:43 +02:00
86ac7428bb small README update 2025-03-11 16:32:52 +01:00
686b689a91 Merge pull request 'UART embedded-io fixes' (#72) from uart-embedded-io-fixes into main
Reviewed-on: #72
2025-03-10 17:36:19 +01:00
5daa85269f UART embedded-io fixes 2025-03-10 17:33:13 +01:00
3bc2ee4343 Merge pull request 'use released packages' (#71) from use-released-packages into main
Reviewed-on: #71
2025-03-07 16:44:37 +01:00
1ec66e826c use released packages 2025-03-07 16:43:29 +01:00
606d6a43b4 bump embassy version to v0.2.1 2025-03-07 16:40:22 +01:00
a83d9cca16 bump bitfield dep 2025-03-07 16:35:12 +01:00
9eeaae76f7 some typos 2025-03-07 16:33:52 +01:00
39aaea086a Merge pull request 'prepare reb1 and embassy releases' (#70) from prep-reb1-embassy-release into main
Reviewed-on: #70
2025-03-07 16:32:43 +01:00
a6ef0954cb prepare reb1 and embassy releases 2025-03-07 16:30:11 +01:00
102d11114a doc improvements 2025-03-07 16:22:13 +01:00
8cdee8b733 minor tweak for simple UART example 2025-03-07 16:20:23 +01:00
d419325312 Merge pull request 'check all updated dependencies' (#69) from check-all-updates-deps into main
Reviewed-on: #69
2025-03-07 16:17:41 +01:00
561a8419c5 check all updated dependencies 2025-03-07 16:15:17 +01:00
9bd8efcada Merge pull request 'simplfied UART error handling' (#68) from simplify-uart-error-handling into main
Reviewed-on: #68
2025-03-07 16:06:41 +01:00
b401085f32 simplfied UART error handling 2025-03-07 16:06:18 +01:00
c693530ab7 Move to released packages, done 2025-02-18 00:00:27 +01:00
45ee5ad726 Merge pull request 'prep embassy v0.2.0' (#67) from prep-embassy-v0.2.0 into main
Reviewed-on: #67
2025-02-17 23:54:08 +01:00
16591346e5 prep embassy v0.2.0 2025-02-17 23:52:26 +01:00
9ccd147ff6 Merge pull request 'prep embassy v0.1.3' (#66) from prep-embassy-v0.1.3 into main
Reviewed-on: #66
2025-02-17 21:28:24 +01:00
521c07460a prep embassy v0.1.3 2025-02-17 20:50:28 +01:00
5bf7793e6b Merge pull request 'prep BSP v0.8.0' (#65) from prep-bsp-v0.8.0 into main
Reviewed-on: #65
2025-02-17 20:46:39 +01:00
75e6d98e44 prep BSP v0.8.0 2025-02-17 20:34:53 +01:00
80eea170ef Merge pull request 'prep HAL v0.10.0' (#64) from prep-hal-v0.10.0 into main
Reviewed-on: #64
2025-02-17 20:17:53 +01:00
f9d1233d3f prep HAL v0.10.0 2025-02-17 20:15:47 +01:00
41f7f9d25b Merge pull request 'prep PAC v0.5.0' (#63) from prep-pac-v0.5.0 into main
Reviewed-on: #63
2025-02-17 20:05:21 +01:00
47e754433d prep PAC v0.5.0 2025-02-17 20:04:55 +01:00
e5e010a276 Merge pull request 'larger GPIO refactoring and Async UART update' (#62) from uart-gpio-update into main
Reviewed-on: #62
2025-02-17 10:56:21 +01:00
caf54e5a70 larger GPIO refactoring and Async UART update 2025-02-17 10:53:40 +01:00
31b25b0211 Merge pull request 'Adaption Async GPIO' (#59) from adaption-gpio-asynch into main
Reviewed-on: #59
2025-02-15 12:48:48 +01:00
8b55d0923f Bugfix and improvements for async GPIO 2025-02-15 12:46:10 +01:00
a65f4039ee Merge pull request 'minor CI update' (#58) from minor-ci-update into main
Reviewed-on: #58
2025-02-14 16:43:03 +01:00
97da2a0752 minor CI update 2025-02-14 16:40:58 +01:00
c43d1f8861 Merge pull request 'defmt in PAC is optional' (#57) from defmt-in-pac-optional into main
Reviewed-on: #57
2025-02-14 16:29:05 +01:00
181c2bdc7b defmt in PAC is optional 2025-02-14 16:26:27 +01:00
2bca96b5db Merge pull request 'HAL update' (#53) from hal-update into main
Reviewed-on: #53
2025-02-14 16:20:45 +01:00
bf41b59a24 HAL update 2025-02-14 16:18:59 +01:00
6cbba8414c Merge pull request 'bump version' (#55) from pac-update into main
Reviewed-on: #55
2025-02-14 16:14:05 +01:00
fe04a3e7cd bump version 2025-02-14 16:11:53 +01:00
e25fb20b08 Merge pull request 'PAC update' (#54) from pac-update into main
Reviewed-on: #54
2025-02-14 16:06:49 +01:00
67af1bb9b5 PAC update 2025-02-14 16:02:11 +01:00
1a83f932b5 Merge pull request 'document probe-rs' (#52) from document-probe-rs into main
Reviewed-on: #52
2025-02-13 18:48:47 +01:00
cdc4807686 document probe-rs 2025-02-13 18:48:12 +01:00
62a4123f82 Merge pull request 'minor improvments for embassy lib' (#51) from minor-improvements-embassy into main
Reviewed-on: #51
2025-02-13 18:41:15 +01:00
17f13fc4dc minor improvments for embassy lib 2025-02-13 18:27:05 +01:00
b4f1512463 docs patch 2 2025-02-13 15:47:50 +01:00
e9ec01fc60 Merge pull request 'doc fixes' (#48) from va108xx-embassy-doc-fix into main
Reviewed-on: #48
2025-02-13 15:38:50 +01:00
d57cd383cd doc fixes 2025-02-13 15:38:38 +01:00
df4e943f48 Merge pull request 'use released package versions' (#47) from use-released-package-versions into main
Reviewed-on: #47
2025-02-13 15:26:41 +01:00
93b67a4795 README update 2025-02-13 15:26:24 +01:00
a9f2e6dcee use released package versions 2025-02-13 15:25:01 +01:00
271c853df1 Merge pull request 'prepare va108xx-embassy release' (#46) from prepare-va108xx-embassy-release into main
Reviewed-on: #46
2025-02-13 15:22:36 +01:00
107189b166 prepare va108xx-embassy release 2025-02-13 15:22:18 +01:00
872944bebf Merge pull request 'prepare BSP release' (#45) from vorago-reb1-release into main
Reviewed-on: #45
2025-02-13 15:08:22 +01:00
d077bb6210 prepare BSP release 2025-02-13 15:06:02 +01:00
bd286bdb2a Merge pull request 'small README tweak' (#44) from readme-tweak into main
Reviewed-on: #44
2025-02-13 14:56:48 +01:00
d3cc00a4a5 small README tweak 2025-02-13 14:56:24 +01:00
1018a65447 Merge pull request 'add more defmt features' (#42) from add-more-defmt-features into main
Reviewed-on: #42
2025-02-13 14:52:03 +01:00
0a31b637e6 add more defmt features 2025-02-13 14:51:54 +01:00
6e1ae70054 Merge pull request 'add various debug impls' (#43) from add-various-debug-impls into main
Reviewed-on: #43
2025-02-13 14:51:27 +01:00
8ae2d6189a add various debug impls 2025-02-13 14:50:00 +01:00
549a98dbaf Merge pull request 'all doc fixes' (#41) from doc-fixes into main
Reviewed-on: #41
2025-02-13 12:23:56 +01:00
e24fc608a3 all doc fixes 2025-02-13 11:44:14 +01:00
7b74312013 Merge pull request 'completed async RX support as well' (#39) from async-uart-rx into main
Reviewed-on: #39
2025-02-13 11:33:36 +01:00
417f5b7f67 completed async RX support as well 2025-02-13 11:31:17 +01:00
3e796ef22b Merge pull request 'UART B hotfix' (#40) from uart-b-hotfix into main
Reviewed-on: #40
2025-02-13 11:30:37 +01:00
b145047b95 UART B hotfix 2025-02-13 11:14:39 +01:00
82b4c16f8e Merge pull request 'bump PAC version' (#38) from bump-va108xx-version into main
Reviewed-on: #38
2025-02-12 14:57:53 +01:00
189ac2d256 bump PAC version 2025-02-12 14:47:18 +01:00
c5543d8606 Merge pull request 'add back doc attribute' (#37) from prep-pac-release into main
Reviewed-on: #37
2025-02-12 14:25:28 +01:00
691911d087 add back doc attribute 2025-02-12 14:23:25 +01:00
3953897c48 Merge pull request 'Async UART support' (#33) from async-uart into main
Reviewed-on: #33
2025-02-12 14:15:10 +01:00
6e0d417a5c Async UART TX support 2025-02-12 14:13:35 +01:00
4edba63b02 Merge pull request 'docs fix' (#36) from docs-fix into main
Reviewed-on: #36
2025-02-12 14:11:21 +01:00
bcd79f0f20 docs fix 2025-02-12 14:07:18 +01:00
77608da74e Merge pull request 'dynpin defmt bugfix' (#35) from dynpin-fix-2 into main
Reviewed-on: #35
2025-02-12 14:06:51 +01:00
066d91aee5 dynpin defmt bugfix 2025-02-12 14:06:41 +01:00
e869355960 Merge pull request 'Regenerate PAC' (#29) from regenerate-pac into main
Reviewed-on: #29
2025-02-12 14:05:06 +01:00
99631dbd03 va108xx v0.4.0: Regnerate PAC 2025-02-12 14:04:56 +01:00
698ed3a700 Merge pull request 'dynpin bugfix' (#34) from bugfix-dynpin into main
Reviewed-on: #34
2025-02-12 13:41:34 +01:00
f3d840ace7 dynpin bugfix 2025-02-12 13:41:06 +01:00
f781505ec5 Merge pull request 'Asynchronous GPIO support' (#30) from async-gpio into main
Reviewed-on: #30
2025-02-11 18:56:28 +01:00
c6e840a991 Asynchronous GPIO support 2025-02-11 18:56:11 +01:00
454635a473 Merge pull request 'update error handling' (#31) from update-error-types into main
Reviewed-on: #31
2025-02-11 16:19:47 +01:00
67ddba9c42 update error handling 2025-02-11 16:19:20 +01:00
6efc902e02 Merge pull request 'disable the unittests' (#32) from disable-empty-unittests into main
Reviewed-on: #32
2025-02-11 16:18:31 +01:00
b6e9a7f68e disable the unittests 2025-02-10 17:13:03 +01:00
6842e06bc6 Merge pull request 'Update for va108xx' (#28) from update-deps-add-embassy-lib into main
Reviewed-on: #28
2025-02-10 11:42:03 +01:00
5b614e1280 Update for va108xx
- New `va108xx-embassy` crate.
- Embassy library uses new crate
- Updated all dependencies

va108xx-hal

- Refactored and simplified PWM driver
- Added new raw getter API for TIM peripheral blocks
2025-02-10 11:40:37 +01:00
16e5a5f197 Merge pull request 'GPIO refactoring and API improvements' (#27) from gpio-refactoring into main
Reviewed-on: #27
2025-02-10 11:36:45 +01:00
da1f2902b2 GPIO refactoring and API improvements 2025-02-10 11:35:20 +01:00
b2d17e10ed Merge pull request 'bootloader tweak' (#26) from bootloader-tweak into main
Reviewed-on: #26
2025-01-14 10:30:08 +01:00
1412e1b7d1 bootloader tweak 2025-01-14 01:21:20 +01:00
88ee85a4cd Merge pull request 'new update for ringbuf' (#25) from update-for-ringbuf into main
Reviewed-on: #25
2025-01-11 11:39:59 +01:00
4b318ecc76 new update for ringbuf 2025-01-11 11:38:04 +01:00
badeea8071 Merge pull request 'fix memory x file' (#24) from fix-memory-x-file into main
Reviewed-on: #24
2025-01-10 17:49:45 +01:00
c95558ff55 Merge branch 'main' into fix-memory-x-file 2025-01-10 17:49:38 +01:00
cd222fd1e1 Merge pull request 'some clippy fixes' (#23) from some-clippy-fixes into main
Reviewed-on: #23
2025-01-10 17:49:20 +01:00
f438e7e40f some clippy fixes 2025-01-10 17:19:28 +01:00
9e547668c2 fix memory x file 2025-01-10 16:38:17 +01:00
cf55fe1504 Merge pull request 'bootloader and flashloader update' (#22) from bootloader-flashloader-update into main
Reviewed-on: #22
2025-01-10 16:31:39 +01:00
74eebdcc03 bootloader and flashloader update 2025-01-10 16:31:15 +01:00
35527f092a Merge pull request 'update probe-rs files' (#21) from probe-rs-update into main
Reviewed-on: #21
2024-11-26 10:23:37 +01:00
c6314f48d7 update probe-rs files 2024-11-26 10:23:27 +01:00
7189cb246b Merge pull request 'delete some more re-exports' (#20) from delete-some-re-exports into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #20
2024-10-07 09:48:13 +02:00
39b8633065 delete some more re-exports
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-10-07 09:47:24 +02:00
df0760da98 Merge pull request 'prepare BSP release' (#19) from prep-bsp-release into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #19
2024-09-30 12:12:20 +02:00
8ed26db6a7 prepare BSP release
Some checks are pending
Rust/va108xx-rs/pipeline/head Build queued...
2024-09-30 12:10:03 +02:00
307174b938 Merge pull request 'prepare HAL release' (#18) from prep-hal-release into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #18
2024-09-30 12:02:23 +02:00
46df7f1007 prepare HAL release
Some checks are pending
Rust/va108xx-rs/pipeline/pr-main Build queued...
2024-09-30 11:58:04 +02:00
48dd00661f Merge pull request 'added correction for link' (#17) from link-correction into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #17
2024-09-30 11:47:57 +02:00
e98ef8501e added correction for link
Some checks are pending
Rust/va108xx-rs/pipeline/head Build queued...
2024-09-30 11:46:16 +02:00
b753a465bf Merge pull request 'Flashloader and Bootloader' (#16) from flashloader into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #16
2024-09-30 11:43:58 +02:00
d6f69d4a54 Finished flashloader and bootloader implementation
Some checks are pending
Rust/va108xx-rs/pipeline/pr-main Build queued...
2024-09-30 11:41:52 +02:00
e2a55e7309 Merge pull request 'bmstall is configurable now as well' (#15) from va108xx-update-package into main
Some checks failed
Rust/va108xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #15
2024-09-20 12:13:12 +02:00
e971e8dc0d bmstall is configurable now as well
All checks were successful
Rust/va108xx-rs/pipeline/pr-main This commit looks good
2024-09-20 11:42:41 +02:00
501d1c973e Merge pull request 'update package' (#14) from va108xx-update-package into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #14
2024-09-20 11:29:58 +02:00
acb8b67ae7 update package
All checks were successful
Rust/va108xx-rs/pipeline/pr-main This commit looks good
- Add embassy example
- improve timer API
- restructure examples
- restructure and improve SPI
- Add REB1 M95M01 NVM module
2024-09-20 10:53:42 +02:00
405cc089c3 update flasher script file
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-07-11 11:52:56 +02:00
f48ee8231a another link correction
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-07-04 18:55:30 +02:00
4fb19fe234 link fix
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-07-04 18:54:37 +02:00
652af5cb3c README correction
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-07-04 18:41:33 +02:00
6e231e2553 prepare next vorago-reb1 release
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-07-04 18:40:26 +02:00
79b7d7b4c2 Merge pull request 'Improve UART CLK calculation' (#12) from improve-uart-clk-calc into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #12
2024-07-04 18:33:08 +02:00
3196d74a34 doc correction
Some checks are pending
Rust/va108xx-rs/pipeline/head Build queued...
2024-07-04 18:32:30 +02:00
d7c27446e0 Merge remote-tracking branch 'origin/main' into improve-uart-clk-calc
Some checks are pending
Rust/va108xx-rs/pipeline/head Build started...
2024-07-04 18:27:17 +02:00
3d4e8477c1 improve UART clock calculation 2024-07-04 18:26:56 +02:00
e2e3cc7020 Merge pull request 'Improve HAL' (#11) from improve-hal into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #11
2024-07-04 18:16:11 +02:00
4f15cd7a31 another small link fix
Some checks are pending
Rust/va108xx-rs/pipeline/pr-main Build queued...
2024-07-04 18:15:55 +02:00
3c8c455c6f Update and improve HAL library and docs
All checks were successful
Rust/va108xx-rs/pipeline/pr-main This commit looks good
2024-07-04 18:07:34 +02:00
abb78c2682 Merge pull request 'update doc build settings' (#10) from update-doc-settings into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #10
2024-06-26 23:05:30 +02:00
51f21fee43 update doc build settings
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-06-26 22:58:12 +02:00
6fb3b0544f Merge pull request 'readme fix' (#9) from readme-fix into main
Some checks failed
Rust/va108xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #9
2024-06-25 20:18:48 +02:00
e3996d9166 Merge branch 'main' into readme-fix 2024-06-25 20:18:39 +02:00
deee5f6f46 readme fix
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-06-25 19:44:12 +02:00
5d6c7ebf5e Merge pull request 'doc fixes and improvements' (#8) from update-docs into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #8
2024-06-25 11:02:54 +02:00
6b3cdf74cc Merge branch 'main' into update-docs
Some checks are pending
Rust/va108xx-rs/pipeline/pr-main Build queued...
2024-06-25 11:02:31 +02:00
f4f378ba4f doc fixes and improvements
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
2024-06-25 10:47:33 +02:00
4224b14545 Merge pull request 'update docs' (#7) from update-docs into main
Some checks failed
Rust/va108xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #7
2024-06-25 10:26:10 +02:00
e3cdb19a35 Merge branch 'main' into update-docs 2024-06-25 10:26:02 +02:00
95e72cfea6 update docs
Some checks are pending
Rust/va108xx-rs/pipeline/head Build queued...
2024-06-25 10:25:39 +02:00
6854703c5d link corrections
Some checks failed
Rust/va108xx-rs/pipeline/head There was a failure building this commit
2024-06-23 22:05:11 +02:00
edc03d9e6e Merge pull request 'prepare BSP release' (#6) from prep-bsp-release into main
Some checks failed
Rust/va108xx-rs/pipeline/head There was a failure building this commit
Reviewed-on: #6
2024-06-16 19:31:16 +02:00
e434f2e301 prepare BSP release
Some checks are pending
Rust/va108xx-rs/pipeline/head Build started...
2024-06-16 19:30:04 +02:00
f62825a63e Merge pull request 'prepare HAL release' (#5) from prep-hal-release into main
All checks were successful
Rust/va108xx-rs/pipeline/head This commit looks good
Reviewed-on: #5
2024-06-16 19:17:33 +02:00
5e56e9bb12 prepare HAL release
Some checks are pending
Rust/va108xx-rs/pipeline/head Build started...
2024-06-16 19:16:52 +02:00
251 changed files with 5627 additions and 36748 deletions

View File

@ -4,10 +4,9 @@
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
# runner = "gdb-multiarch -q -x openocd.gdb"
# runner = "gdb -q -x openocd.gdb"
runner = "gdb-multiarch -q -x jlink.gdb"
# runner = "gdb-multiarch -q -x jlink.gdb"
# Probe-rs is currently problematic: https://github.com/probe-rs/probe-rs/issues/2567
# runner = "probe-rs run --chip VA108xx --chip-description-path ./scripts/VA108xx_Series.yaml"
runner = "probe-rs run --chip VA108xx_RAM --protocol jtag"
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
rustflags = [
@ -19,13 +18,12 @@ rustflags = [
"-C", "link-arg=-Tlink.x",
# knurling-rs tooling. If you want to use flip-link, ensure it is installed first.
# "-C", "linker=flip-link",
"-C", "linker=flip-link",
# Unfortunately, defmt is clunky to use without probe-rs..
# "-C", "link-arg=-Tdefmt.x",
"-C", "link-arg=-Tdefmt.x",
# Can be useful for debugging.
"-Clink-args=-Map=app.map"
# "-Clink-args=-Map=app.map"
]
[build]

View File

@ -10,8 +10,10 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "thumbv6m-none-eabi"
- run: cargo check --target thumbv6m-none-eabi --release
- run: cargo check --target thumbv6m-none-eabi --examples --release
- run: cargo check --target thumbv6m-none-eabi
- run: cargo check --target thumbv6m-none-eabi --examples
- run: cargo check -p va108xx --target thumbv6m-none-eabi --all-features
- run: cargo check -p va108xx-hal --target thumbv6m-none-eabi --features "defmt"
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 va108xx-hal
- run: cargo nextest run --all-features -p va108xx-hal --no-tests=pass
# 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: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx --all-features
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal --all-features
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1
clippy:
name: Clippy

2
.gitignore vendored
View File

@ -16,3 +16,5 @@ Cargo.lock
# JetBrains IDEs
/.idea
*.iml
/Embed.toml

View File

@ -1,15 +1,20 @@
[workspace]
resolver = "2"
members = [
"vorago-reb1",
"va108xx",
"va108xx-hal",
"vorago-reb1",
"va108xx",
"va108xx-hal",
"va108xx-embassy",
"examples/simple",
"examples/rtic",
"examples/embassy",
"board-tests",
"bootloader",
"flashloader",
]
exclude = [
"defmt-testapp",
"flashloader/slot-a-blinky",
"flashloader/slot-b-blinky",
]
[profile.dev]
@ -17,7 +22,8 @@ codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
opt-level = 'z' # <-
# 1 instead of 0, the flashloader is too larger otherwise..
# opt-level = 1 # <-
overflow-checks = true # <-
# cargo build/run --release
@ -29,3 +35,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.

12
Embed.toml.sample Normal file
View File

@ -0,0 +1,12 @@
[default.probe]
protocol = "Jtag"
[default.general]
chip = "VA108xx_RAM"
[default.rtt]
enabled = true
[default.gdb]
# Whether or not a GDB server should be opened after flashing.
enabled = false

101
README.md
View File

@ -14,14 +14,25 @@ This workspace contains the following released crates:
crate containing basic low-level register definition.
- The [`va108xx-hal`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/va108xx-hal)
HAL crate containing higher-level abstractions on top of the PAC register crate.
- The [`va108xx-embassy`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/va108xx-embassy)
crate containing support for running the embassy-rs RTOS.
- The [`vorago-reb1`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1)
BSP crate containing support for the REB1 development board.
It also contains the following helper crates:
- The `board-tests` contains an application which can be used to test the libraries on the
board.
- The `examples` crates contains various example applications for the HAL and the PAC.
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va108xx-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/va108xx-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 [`board-tests`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/board-tests)
contains an application which can be used to test the libraries on the board.
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples)
folder contains various example applications crates using the HAL and the PAC.
This folder also contains dedicated example applications using the
[`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy)
native Rust RTOSes.
## Using the `.cargo/config.toml` file
@ -49,14 +60,56 @@ 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.
### Pre-Requisites
### 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 VA108xx_RAM --protocol jtag target/thumbv6m-none-eabi/debug/examples/blinky
```
to flash and run the blinky program on the RAM. There is also a `VA108xx` 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.
### Using VS Code
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
Please make sure that [`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146)
are installed as well.
Some sample configuration files for VS code were provided and can be used by running
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
to automatically rebuild and flash your application.
If you would like to use a custom GDB application, you can specify the gdb binary in the following
configuration variables in your `settings.json`:
- `"cortex-debug.gdbPath"`
- `"cortex-debug.gdbPath.linux"`
- `"cortex-debug.gdbPath.windows"`
- `"cortex-debug.gdbPath.osx"`
The provided VS Code configurations also provide an integrated RTT logger, which you can access
via the terminal at `RTT Ch:0 console`. In order for the RTT block address detection to
work properly, `objdump-multiarch` and `nm-multiarch` need to be installed.
### Using CLI with GDB and Segger J-Link Tools
Install the following two tools first:
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar
cross-architecture debugger installed. All commands here assume `gdb-multiarch`.
### Using CLI
You can build the blinky example application with the following command
```sh
@ -70,13 +123,13 @@ is also run when running the `jlink-gdb.sh` script)
```sh
JLinkGDBServer -select USB -device Cortex-M0 -endian little -if JTAG-speed auto \
-LocalhostOnly
-LocalhostOnly -jtagconf -1,-1
```
After this, you can flash and debug the application with the following command
```sh
gdb-mutliarch -q -x jlink/jlink.gdb target/thumbv6m-none-eabihf/debug/examples/blinky
gdb-mutliarch -q -x jlink/jlink.gdb target/thumbv6m-none-eabihf/debug/examples/blinky -tui
```
Please note that you can automate all steps except starting the GDB server by using a cargo
@ -90,19 +143,27 @@ runner = "gdb-multiarch -q -x jlink/jlink.gdb"
After that, you can simply use `cargo run --example blinky` to flash the blinky
example.
### Using VS Code
### Using the RTT Viewer
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
The Segger RTT viewer can be used to display log messages received from the target. The base
address for the RTT block placement is 0x10000000. It is recommended to use a search range of
0x1000 around that base address when using the RTT viewer.
Some sample configuration files for VS code were provided and can be used by running
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
to automatically rebuild and flash your application.
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
If you would like to use a custom GDB application, you can specify the gdb binary in the following
configuration variables in your `settings.json`:
```sh
defmt-print -e <pathToElfFile> tcp
```
- `"cortex-debug.gdbPath"`
- `"cortex-debug.gdbPath.linux"`
- `"cortex-debug.gdbPath.windows"`
- `"cortex-debug.gdbPath.osx"`
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 """
cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1
"""
}
}

View File

@ -4,18 +4,16 @@ version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m-rtic = "1"
panic-halt = "0.2"
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
rtt-target = "0.5"
panic-rtt-target = "0.1.3"
panic-halt = "1"
rtt-target = "0.6"
panic-rtt-target = "0.2"
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
[dependencies.va108xx-hal]
version = "0.6"
path = "../va108xx-hal"
version = "0.11"
features = ["rt"]

View File

@ -6,10 +6,7 @@
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::{
delay::DelayNs,
digital::{InputPin, OutputPin, StatefulOutputPin},
};
use embedded_hal::delay::DelayNs;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{
@ -17,7 +14,7 @@ use va108xx_hal::{
pac::{self, interrupt},
prelude::*,
time::Hertz,
timer::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer, IrqCfg},
timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, InterruptConfig},
};
#[allow(dead_code)]
@ -44,8 +41,8 @@ fn main() -> ! {
rprintln!("-- VA108xx Test Application --");
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, dp.portb);
let mut led1 = pinsa.pa10.into_readable_push_pull_output();
let test_case = TestCase::DelayMs;
@ -67,113 +64,110 @@ fn main() -> ! {
TestCase::TestBasic => {
// Tie PORTA[0] to PORTA[1] for these tests!
let mut out = pinsa.pa0.into_readable_push_pull_output();
let mut input = pinsa.pa1.into_floating_input();
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.set_low().unwrap();
assert!(input.is_low().unwrap());
let input = pinsa.pa1.into_floating_input();
out.set_high();
assert!(input.is_high());
out.set_low();
assert!(input.is_low());
}
TestCase::TestPullup => {
// Tie PORTA[0] to PORTA[1] for these tests!
let mut input = pinsa.pa1.into_pull_up_input();
assert!(input.is_high().unwrap());
let input = pinsa.pa1.into_pull_up_input();
assert!(input.is_high());
let mut out = pinsa.pa0.into_readable_push_pull_output();
out.set_low().unwrap();
assert!(input.is_low().unwrap());
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.set_low();
assert!(input.is_low());
out.set_high();
assert!(input.is_high());
out.into_floating_input();
assert!(input.is_high().unwrap());
assert!(input.is_high());
}
TestCase::TestPulldown => {
// Tie PORTA[0] to PORTA[1] for these tests!
let mut input = pinsa.pa1.into_pull_down_input();
assert!(input.is_low().unwrap());
let input = pinsa.pa1.into_pull_down_input();
assert!(input.is_low());
let mut out = pinsa.pa0.into_push_pull_output();
out.set_low().unwrap();
assert!(input.is_low().unwrap());
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.set_low();
assert!(input.is_low());
out.set_high();
assert!(input.is_high());
out.into_floating_input();
assert!(input.is_low().unwrap());
assert!(input.is_low());
}
TestCase::TestMask => {
// Tie PORTA[0] to PORTA[1] for these tests!
let input = pinsa.pa1.into_pull_down_input().clear_datamask();
let mut input = pinsa.pa1.into_pull_down_input();
input.clear_datamask();
assert!(!input.datamask());
let mut out = pinsa.pa0.into_push_pull_output().clear_datamask();
let mut out = pinsa.pa0.into_push_pull_output();
out.clear_datamask();
assert!(input.is_low_masked().is_err());
assert!(out.set_high_masked().is_err());
}
TestCase::PortB => {
// Tie PORTB[22] to PORTB[23] for these tests!
let mut out = pinsb.pb22.into_readable_push_pull_output();
let mut input = pinsb.pb23.into_floating_input();
out.set_high().unwrap();
assert!(input.is_high().unwrap());
out.set_low().unwrap();
assert!(input.is_low().unwrap());
let input = pinsb.pb23.into_floating_input();
out.set_high();
assert!(input.is_high());
out.set_low();
assert!(input.is_low());
}
TestCase::Perid => {
assert_eq!(PinsA::get_perid(), 0x004007e1);
assert_eq!(PinsB::get_perid(), 0x004007e1);
}
TestCase::Pulse => {
let mut output_pulsed = pinsa
.pa0
.into_push_pull_output()
.pulse_mode(true, PinState::Low);
let mut output_pulsed = pinsa.pa0.into_push_pull_output();
output_pulsed.configure_pulse_mode(true, PinState::Low);
rprintln!("Pulsing high 10 times..");
output_pulsed.set_low().unwrap();
output_pulsed.set_low();
for _ in 0..10 {
output_pulsed.set_high().unwrap();
output_pulsed.set_high();
cortex_m::asm::delay(25_000_000);
}
let mut output_pulsed = output_pulsed.pulse_mode(true, PinState::High);
output_pulsed.configure_pulse_mode(true, PinState::High);
rprintln!("Pulsing low 10 times..");
for _ in 0..10 {
output_pulsed.set_low().unwrap();
output_pulsed.set_low();
cortex_m::asm::delay(25_000_000);
}
}
TestCase::DelayGpio => {
let mut out_0 = pinsa
.pa0
.into_readable_push_pull_output()
.delay(true, false);
let mut out_1 = pinsa
.pa1
.into_readable_push_pull_output()
.delay(false, true);
let mut out_2 = pinsa.pa3.into_readable_push_pull_output().delay(true, true);
let mut out_0 = pinsa.pa0.into_readable_push_pull_output();
out_0.configure_delay(true, false);
let mut out_1 = pinsa.pa1.into_readable_push_pull_output();
out_1.configure_delay(false, true);
let mut out_2 = pinsa.pa3.into_readable_push_pull_output();
out_2.configure_delay(true, true);
for _ in 0..20 {
out_0.toggle().unwrap();
out_1.toggle().unwrap();
out_2.toggle().unwrap();
out_0.toggle();
out_1.toggle();
out_2.toggle();
cortex_m::asm::delay(25_000_000);
}
}
TestCase::DelayMs => {
let mut ms_timer = set_up_ms_tick(
IrqCfg::new(pac::Interrupt::OC0, true, true),
InterruptConfig::new(pac::Interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
for _ in 0..5 {
led1.toggle().ok();
led1.toggle();
ms_timer.delay_ms(500);
led1.toggle().ok();
led1.toggle();
ms_timer.delay_ms(500);
}
let mut delay_timer = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
let mut pa0 = pinsa.pa0.into_readable_push_pull_output();
for _ in 0..5 {
led1.toggle().ok();
led1.toggle();
delay_timer.delay_ms(500);
led1.toggle().ok();
led1.toggle();
delay_timer.delay_ms(500);
}
let ahb_freq: Hertz = 50.MHz();
@ -181,13 +175,13 @@ fn main() -> ! {
// Test usecond delay using both TIM peripheral and SYST. Use the release image if you
// want to verify the timings!
loop {
pa0.toggle().ok();
pa0.toggle();
delay_timer.delay_us(50);
pa0.toggle().ok();
pa0.toggle();
delay_timer.delay_us(50);
pa0.toggle_with_toggle_reg();
pa0.toggle();
syst_delay.delay_us(50);
pa0.toggle_with_toggle_reg();
pa0.toggle();
syst_delay.delay_us(50);
}
}
@ -195,7 +189,7 @@ fn main() -> ! {
rprintln!("Test success");
loop {
led1.toggle().ok();
led1.toggle();
cortex_m::asm::delay(25_000_000);
}
}

28
bootloader/Cargo.toml Normal file
View File

@ -0,0 +1,28 @@
[package]
name = "bootloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
crc = "3"
num_enum = { version = "0.7", default-features = false }
static_assertions = "1"
[dependencies.va108xx-hal]
version = "0.11"
path = "../va108xx-hal"
features = ["defmt"]
[dependencies.vorago-reb1]
version = "0.8"
path = "../vorago-reb1"
[features]
default = []
rtt-panic = []

51
bootloader/README.md Normal file
View File

@ -0,0 +1,51 @@
VA108xx 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 0x2FFE bytes |
| 0x2FFE | Bootloader CRC | half-word |
| 0x3000 | App image A start | code up to 0xE7F4 (~59K) bytes |
| 0x117F8 | App image A CRC check length | word |
| 0x117FC | App image A CRC check value | word |
| 0x117FC | App image B start | code up to 0xE7F4 (~59K) bytes |
| 0x1FFF0 | App image B CRC check length | word |
| 0x1FFF4 | App image B CRC check value | word |
| 0x1FFF8 | Reserved section, contains boot select parameter | 8 bytes |
| 0x20000 | End of NVM | end |
## Additional Information
This bootloader was specifically written for the REB1 board, so it assumes a M95M01 ST EEPROM
is used to load the application code. The bootloader will also delay for a configurable amount
of time before booting. This allows to catch the RTT printout, but should probably be disabled
for production firmware.
This bootloader does not provide tools to flash the NVM memory by itself. Instead, you can use
the [flashloader](https://egit.irs.uni-stuttgart.de/rust/va108xx-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. Read the boot slot from a reserved section at the end of the EEPROM. If no valid value is read,
select boot slot A.
3. Check the checksum of the boot slot. If that checksum is valid, it will boot that slot. If not,
it will proceed to the next step.
4. Check the checksum of the other slot . If that checksum is valid, it will boot that slot. If
not, it will boot App A as the fallback image.
In your actual production application, a command to update the preferred boot slot could be exposed
to allow performing software updates in a safe way.
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.

10
bootloader/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
#![no_std]
use core::convert::Infallible;
/// Simple trait which makes swapping the NVM easier. NVMs only need to implement this interface.
pub trait NvmInterface {
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Infallible>;
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), Infallible>;
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, Infallible>;
}

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

@ -0,0 +1,339 @@
//! Vorago bootloader which can boot from two images.
#![no_main]
#![no_std]
use bootloader::NvmInterface;
use cortex_m_rt::entry;
use crc::{Crc, CRC_16_IBM_3740};
use embedded_hal::delay::DelayNs;
use num_enum::TryFromPrimitive;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{pac, spi::SpiClkConfig, time::Hertz, timer::CountdownTimer};
use vorago_reb1::m95m01::M95M01;
// Useful for debugging and see what the bootloader is doing. Enabled currently, because
// the binary stays small enough.
const DEFMT_PRINTOUT: bool = true;
const DEBUG_PRINTOUTS: bool = true;
// Small delay, allows RTT printout to catch up.
const BOOT_DELAY_MS: u32 = 2000;
// 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;
// Register definitions for Cortex-M0 SCB register.
pub const SCB_AIRCR_VECTKEY_POS: u32 = 16;
pub const SCB_AIRCR_VECTKEY_MSK: u32 = 0xFFFF << SCB_AIRCR_VECTKEY_POS;
pub const SCB_AIRCR_SYSRESETREQ_POS: u32 = 2;
pub const SCB_AIRCR_SYSRESETREQ_MSK: u32 = 1 << SCB_AIRCR_SYSRESETREQ_POS;
const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000);
// Important bootloader addresses and offsets, vector table information.
const NVM_SIZE: u32 = 0x20000;
const BOOTLOADER_START_ADDR: u32 = 0x0;
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
// This is also the maximum size of the bootloader.
const BOOTLOADER_END_ADDR: u32 = 0x3000;
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR;
// 0x117F8
const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
// 0x117FC
const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
// 0x11800
pub const APP_A_END_ADDR: u32 = APP_A_START_ADDR + APP_IMG_SZ;
// The actual size of the image which is relevant for CRC calculation.
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
// The actual size of the image which is relevant for CRC calculation.
// 0x1FFF8
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32.
// 0x1FFFC
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
// 0x20000. 8 bytes at end of EEPROM reserved for preferred image parameter. This reserved
// size should be a multiple of 8 due to alignment requirements.
pub const APP_B_END_ADDR: u32 = NVM_SIZE - 8;
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 = 0xC0;
pub const RESET_VECTOR_OFFSET: u32 = 0x4;
pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
const CRC_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, defmt::Format)]
#[repr(u8)]
enum AppSel {
A = 0,
B = 1,
}
pub struct NvmWrapper(pub M95M01);
// Newtype pattern. We could now more easily swap the used NVM type.
impl NvmInterface for NvmWrapper {
fn write(&mut self, address: usize, data: &[u8]) -> Result<(), core::convert::Infallible> {
self.0.write(address, data)
}
fn read(&mut self, address: usize, buf: &mut [u8]) -> Result<(), core::convert::Infallible> {
self.0.read(address, buf)
}
fn verify(&mut self, address: usize, data: &[u8]) -> Result<bool, core::convert::Infallible> {
self.0.verify(address, data)
}
}
#[entry]
fn main() -> ! {
if DEFMT_PRINTOUT {
defmt::println!("-- VA108xx bootloader --");
}
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let mut timer = CountdownTimer::new(dp.tim0, CLOCK_FREQ);
let clk_config = SpiClkConfig::new(2, 4);
let mut nvm = M95M01::new(dp.spic, clk_config);
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 - 6) as usize,
)
}
};
let mut digest = CRC_ALGO.digest();
digest.update(&first_four_bytes);
digest.update(bootloader_data);
let bootloader_crc = digest.finalize();
nvm.write(0x0, &first_four_bytes)
.expect("writing to NVM failed");
nvm.write(0x4, bootloader_data)
.expect("writing to NVM failed");
if let Err(e) = nvm.verify(0x0, &first_four_bytes) {
if DEFMT_PRINTOUT {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
if let Err(e) = nvm.verify(0x4, bootloader_data) {
if DEFMT_PRINTOUT {
defmt::error!("verification of self-flash to NVM failed: {:?}", e);
}
}
nvm.write(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes())
.expect("writing CRC failed");
if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR as usize, &bootloader_crc.to_be_bytes()) {
if DEFMT_PRINTOUT {
defmt::error!(
"error: CRC verification for bootloader self-flash failed: {:?}",
e
);
}
}
}
let mut nvm = NvmWrapper(nvm);
// Check bootloader's CRC (and write it if blank)
check_own_crc(&dp.sysconfig, &cp, &mut nvm, &mut timer);
let mut preferred_app_raw = [0; 1];
nvm.read(PREFERRED_SLOT_OFFSET as usize, &mut preferred_app_raw)
.expect("reading preferred slot failed");
let preferred_app = AppSel::try_from(preferred_app_raw[0]).unwrap_or(AppSel::A);
let other_app = if preferred_app == AppSel::A {
AppSel::B
} else {
AppSel::A
};
if check_app_crc(preferred_app) {
boot_app(&dp.sysconfig, &cp, preferred_app, &mut timer)
} else if check_app_crc(other_app) {
boot_app(&dp.sysconfig, &cp, other_app, &mut timer)
} else {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::error!("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(&dp.sysconfig, &cp, AppSel::A, &mut timer)
}
}
fn check_own_crc(
sysconfig: &pac::Sysconfig,
cp: &cortex_m::Peripherals,
nvm: &mut NvmWrapper,
timer: &mut CountdownTimer,
) {
let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() };
// 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 - 6) as usize,
)
});
let crc_calc = digest.finalize();
if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::info!("BL CRC blank - prog new CRC");
}
// Blank CRC, write it to NVM.
nvm.write(BOOTLOADER_CRC_ADDR as usize, &crc_calc.to_be_bytes())
.expect("writing CRC failed");
// 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_PRINTOUT {
defmt::warn!(
"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(sysconfig, cp, AppSel::A, timer);
}
}
// Reading from address 0x0 is problematic in Rust.
// See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/5.
// This solution falls back to assembler to deal with this.
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) -> bool {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
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)
} else {
check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR)
}
}
fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) -> bool {
let crc_exp = unsafe { (crc_addr as *const u16).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_PRINTOUT {
defmt::error!("detected invalid app size {}", image_size);
}
return false;
}
let crc_calc = CRC_ALGO.checksum(unsafe {
core::slice::from_raw_parts(start_addr as *const u8, image_size as usize)
});
if crc_calc == crc_exp {
return true;
}
false
}
// The boot works by copying the interrupt vector table (IVT) of the respective app to the
// base address in code RAM (0x0) and then performing a soft reset.
fn boot_app(
syscfg: &pac::Sysconfig,
cp: &cortex_m::Peripherals,
app_sel: AppSel,
timer: &mut CountdownTimer,
) -> ! {
if DEBUG_PRINTOUTS && DEFMT_PRINTOUT {
defmt::info!("booting app {:?}", app_sel);
}
timer.delay_ms(BOOT_DELAY_MS);
// Clear all interrupts set.
unsafe {
cp.NVIC.icer[0].write(0xFFFFFFFF);
cp.NVIC.icpr[0].write(0xFFFFFFFF);
}
// Disable ROM protection.
syscfg.rom_prot().write(|w| w.wren().set_bit());
let base_addr = if app_sel == AppSel::A {
APP_A_START_ADDR
} else {
APP_B_START_ADDR
};
unsafe {
// First 4 bytes done with inline assembly, writing to the physical address 0x0 can not
// be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2.
let first_four_bytes = core::ptr::read(base_addr as *const u32);
core::arch::asm!(
"str {0}, [{1}]",
in(reg) first_four_bytes, // Input: App vector table.
in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer
);
core::slice::from_raw_parts_mut(
(BOOTLOADER_START_ADDR + 4) as *mut u8,
(VECTOR_TABLE_LEN - 4) as usize,
)
.copy_from_slice(core::slice::from_raw_parts(
(base_addr + 4) as *const u8,
(VECTOR_TABLE_LEN - 4) as usize,
));
}
// Disable re-loading from FRAM/code ROM on soft reset
syscfg
.rst_cntl_rom()
.modify(|_, w| w.sysrstreq().clear_bit());
soft_reset(cp);
}
// Soft reset based on https://github.com/ARM-software/CMSIS_6/blob/5782d6f8057906d360f4b95ec08a2354afe5c9b9/CMSIS/Core/Include/core_cm0.h#L874.
fn soft_reset(cp: &cortex_m::Peripherals) -> ! {
// Ensure all outstanding memory accesses included buffered write are completed before reset.
cortex_m::asm::dsb();
unsafe {
cp.SCB
.aircr
.write((0x5FA << SCB_AIRCR_VECTKEY_POS) | SCB_AIRCR_SYSRESETREQ_MSK);
}
// Ensure completion of memory access.
cortex_m::asm::dsb();
// Loop until the reset occurs.
loop {
cortex_m::asm::nop();
}
}

View File

@ -1,48 +0,0 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
# runner = "gdb-multiarch -q -x openocd.gdb"
# runner = "gdb -q -x openocd.gdb"
runner = "gdb-multiarch -q -x jlink.gdb"
# Probe-rs is currently problematic: https://github.com/probe-rs/probe-rs/issues/2567
# runner = "probe-rs run --chip VA108xx --chip-description-path ./scripts/VA108xx_Series.yaml"
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
rustflags = [
# 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", "link-arg=--nmagic",
# LLD (shipped with the Rust toolchain) is used as the default linker
"-C", "link-arg=-Tlink.x",
# knurling-rs tooling. If you want to use flip-link, ensure it is installed first.
"-C", "linker=flip-link",
# Unfortunately, defmt is clunky to use without probe-rs..
"-C", "link-arg=-Tdefmt.x",
# Can be useful for debugging.
"-Clink-args=-Map=app.map"
]
[build]
# Pick ONE of these compilation targets
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)
[alias]
re = "run --example"
rb = "run --bin"
rrb = "run --release --bin"
ut = "test --target x86_64-unknown-linux-gnu"
[env]
DEFMT_LOG = "info"

View File

@ -1 +0,0 @@
/target

View File

@ -1,36 +0,0 @@
[package]
name = "defmt-testapp"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = {version = "0.7", features = ["critical-section-single-core"]}
panic-rtt-target = "0.1"
cortex-m-rt = "0.7"
rtt-target = "0.5"
rtic-sync = { version = "1.3", features = ["defmt-03"] }
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
cortex-m-semihosting = "0.5.0"
# Tricky without probe-rs.
defmt = "0.3"
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
panic-probe = { version = "0.3", features = ["print-defmt"] }
[dependencies.rtic]
version = "2"
features = ["thumbv6-backend"]
[dependencies.rtic-monotonics]
version = "1"
features = ["cortex-m-systick"]
[dependencies.va108xx-hal]
version = "0.6"
path = "../va108xx-hal"
features = ["rt", "defmt"]
[dependencies.va108xx]
version = "0.3"
path = "../va108xx"

View File

@ -1,9 +0,0 @@
defmt Testapp
======
`defmt` is clunky to use without probe-rs and requires special configuration inside the
`.cargo/config.toml` file.
`probe-rs` is currently problematic for usage with the VA108xx , so it is not the default tool
recommended and used for the whole workspace. This project contains an isolated, `defmt` compatible
configuration for testing with `defmt` (and `probe-rs`).

View File

@ -1,53 +0,0 @@
#![no_main]
#![no_std]
use cortex_m_semihosting::debug;
use defmt_brtt as _; // global logger
use va108xx_hal as _; // memory layout
use panic_probe as _;
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
// #[defmt::panic_handler]
/*
fn panic() -> ! {
cortex_m::asm::udf()
}
*/
/// Terminates the application and makes a semihosting-capable debug tool exit
/// with status code 0.
pub fn exit() -> ! {
loop {
debug::exit(debug::EXIT_SUCCESS);
}
}
/// Hardfault handler.
///
/// Terminates the application and makes a semihosting-capable debug tool exit
/// with an error. This seems better than the default, which is to spin in a
/// loop.
#[cortex_m_rt::exception]
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
loop {
debug::exit(debug::EXIT_FAILURE);
}
}
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
// once within a crate. the module can be in any file but there can only be at most
// one `#[tests]` module in this library crate
#[cfg(test)]
#[defmt_test::tests]
mod unit_tests {
use defmt::assert;
#[test]
fn it_works() {
assert!(true)
}
}

33
examples/README.md Normal file
View File

@ -0,0 +1,33 @@
VA108xx 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,39 @@
[package]
name = "embassy-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cfg-if = "1"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-async = "1"
embedded-io = "0.6"
embedded-io-async = "0.6"
heapless = "0.8"
static_cell = "2"
defmt = "1"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }
critical-section = "1"
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
embassy-sync = "0.6"
embassy-time = "0.4"
embassy-executor = { version = "0.7", features = [
"arch-cortex-m",
"executor-thread",
"executor-interrupt"
]}
va108xx-hal = { version = "0.11", path = "../../va108xx-hal", features = ["defmt"] }
va108xx-embassy = { version = "0.2" }
[features]
default = ["ticks-hz-1_000", "va108xx-embassy/irq-oc30-oc31"]
custom-irqs = []
ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"]
ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"]

View File

@ -0,0 +1,253 @@
//! This example demonstrates the usage of async GPIO operations on VA108xx.
//!
//! You need to tie the PA0 to the PA1 pin for this example to work. You can optionally tie the PB22 to PB23 pins well
//! and then set the `CHECK_PB22_TO_PB23` to true to also test async operations on Port B.
#![no_std]
#![no_main]
// This imports the logger and the panic handler.
use embassy_example as _;
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 va108xx_hal::gpio::asynch::{on_interrupt_for_async_gpio_for_port, InputPinAsync};
use va108xx_hal::gpio::{Input, Output, PinState, Port};
use va108xx_hal::pins::{PinsA, PinsB};
use va108xx_hal::{
pac::{self, interrupt},
prelude::*,
};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
const CHECK_PA0_TO_PA1: bool = true;
const CHECK_PB22_TO_PB23: bool = false;
// Can also be set to OC10 and works as well.
const PB22_TO_PB23_IRQ: pac::Interrupt = pac::Interrupt::OC11;
#[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,
}
// Declare a bounded channel of 3 u32s.
static CHANNEL_PA0_PA1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PB22_TO_PB23: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
#[embassy_executor::main]
async fn main(spawner: Spawner) {
defmt::println!("-- VA108xx Async GPIO Demo --");
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
let porta = PinsA::new(dp.porta);
let portb = PinsB::new(dp.portb);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let out_pa0 = Output::new(porta.pa0, PinState::Low);
let in_pa1 = Input::new_floating(porta.pa1);
let out_pb22 = Output::new(portb.pb22, PinState::Low);
let in_pb23 = Input::new_floating(portb.pb23);
let in_pa1_async = InputPinAsync::new(in_pa1, pac::Interrupt::OC10);
let in_pb23_async = InputPinAsync::new(in_pb23, PB22_TO_PB23_IRQ);
spawner
.spawn(output_task(
"PA0 to PA1",
out_pa0,
CHANNEL_PA0_PA1.receiver(),
))
.unwrap();
spawner
.spawn(output_task(
"PB22 to PB23",
out_pb22,
CHANNEL_PB22_TO_PB23.receiver(),
))
.unwrap();
if CHECK_PA0_TO_PA1 {
check_pin_to_pin_async_ops("PA0 to PA1", CHANNEL_PA0_PA1.sender(), in_pa1_async).await;
defmt::info!("Example PA0 to PA1 done");
}
if CHECK_PB22_TO_PB23 {
check_pin_to_pin_async_ops("PB22 to PB23", CHANNEL_PB22_TO_PB23.sender(), in_pb23_async)
.await;
defmt::info!("Example PB22 to PB23 done");
}
defmt::info!("Example done, toggling LED0");
loop {
led0.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()
);
}
#[embassy_executor::task(pool_size = 2)]
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();
}
}
}
}
// PB22 to PB23 can be handled by both OC10 and OC11 depending on configuration.
#[interrupt]
#[allow(non_snake_case)]
fn OC10() {
on_interrupt_for_async_gpio_for_port(Port::A);
on_interrupt_for_async_gpio_for_port(Port::B);
}
// This interrupt only handles PORT B interrupts.
#[interrupt]
#[allow(non_snake_case)]
fn OC11() {
on_interrupt_for_async_gpio_for_port(Port::B);
}

View File

@ -0,0 +1,169 @@
//! Asynchronous UART reception example application.
//!
//! This application receives data on two UARTs permanently using a ring buffer.
//! The ring buffer are read them asynchronously. UART A is received on ports PA8 and PA9.
//! UART B is received on ports PA2 and PA3.
//!
//! Instructions:
//!
//! 1. Tie a USB to UART converter with RX to PA9 and TX to PA8 for UART A.
//! Tie a USB to UART converter with RX to PA3 and TX to PA2 for UART B.
//! 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]
// This imports the logger and the panic handler.
use embassy_example as _;
use core::cell::RefCell;
use critical_section::Mutex;
use embassy_executor::Spawner;
use embassy_time::Instant;
use embedded_io::Write;
use embedded_io_async::Read;
use heapless::spsc::{Consumer, Producer, Queue};
use va108xx_hal::{
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsA,
prelude::*,
uart::{
self, on_interrupt_rx_overwriting,
rx_asynch::{on_interrupt_rx, RxAsync},
Bank, RxAsyncOverwriting, Tx,
},
InterruptConfig,
};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
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));
static QUEUE_UART_B: static_cell::ConstStaticCell<Queue<u8, 256>> =
static_cell::ConstStaticCell::new(Queue::new());
static PRODUCER_UART_B: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(RefCell::new(None));
static CONSUMER_UART_B: Mutex<RefCell<Option<Consumer<u8, 256>>>> = Mutex::new(RefCell::new(None));
// main is itself an async function.
#[embassy_executor::main]
async fn main(spawner: Spawner) {
defmt::println!("-- VA108xx Async UART RX Demo --");
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
let porta = PinsA::new(dp.porta);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let mut led1 = Output::new(porta.pa7, PinState::Low);
let mut led2 = Output::new(porta.pa6, PinState::Low);
let tx_uart_a = porta.pa9;
let rx_uart_a = porta.pa8;
let uarta = uart::Uart::new_with_interrupt(
dp.uarta,
tx_uart_a,
rx_uart_a,
50.MHz(),
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC2, true, true),
)
.unwrap();
let tx_uart_b = porta.pa3;
let rx_uart_b = porta.pa2;
let uartb = uart::Uart::new_with_interrupt(
dp.uartb,
tx_uart_b,
rx_uart_b,
50.MHz(),
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC3, true, true),
)
.unwrap();
let (mut tx_uart_a, rx_uart_a) = uarta.split();
let (tx_uart_b, rx_uart_b) = uartb.split();
let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();
// Pass the producer to the interrupt handler.
let (prod_uart_b, cons_uart_b) = QUEUE_UART_B.take().split();
critical_section::with(|cs| {
*PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod_uart_a);
*PRODUCER_UART_B.borrow(cs).borrow_mut() = Some(prod_uart_b);
*CONSUMER_UART_B.borrow(cs).borrow_mut() = Some(cons_uart_b);
});
let mut async_rx_uart_a = RxAsync::new(rx_uart_a, cons_uart_a);
let async_rx_uart_b = RxAsyncOverwriting::new(rx_uart_b, &CONSUMER_UART_B);
spawner
.spawn(uart_b_task(async_rx_uart_b, tx_uart_b))
.unwrap();
let mut buf = [0u8; 256];
loop {
defmt::info!("Current time UART A: {}", Instant::now().as_secs());
led0.toggle();
led1.toggle();
led2.toggle();
let read_bytes = async_rx_uart_a.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();
}
}
#[embassy_executor::task]
async fn uart_b_task(mut async_rx: RxAsyncOverwriting<256>, mut tx: Tx) {
let mut buf = [0u8; 256];
loop {
defmt::info!("Current time UART B: {}", Instant::now().as_secs());
// Infallible asynchronous operation.
let read_bytes = async_rx.read(&mut buf).await.unwrap();
let read_str = core::str::from_utf8(&buf[..read_bytes]).unwrap();
defmt::info!(
"Read {} bytes asynchronously on UART B: {:?}",
read_bytes,
read_str
);
tx.write_all(read_str.as_bytes()).unwrap();
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC2() {
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);
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC3() {
let mut prod =
critical_section::with(|cs| PRODUCER_UART_B.borrow(cs).borrow_mut().take().unwrap());
let errors = on_interrupt_rx_overwriting(Bank::Uart1, &mut prod, &CONSUMER_UART_B);
critical_section::with(|cs| *PRODUCER_UART_B.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 B errors: {:?}", errors);
}
}

View File

@ -0,0 +1,96 @@
//! Asynchronous UART transmission example application.
//!
//! This application receives sends 4 strings with different sizes permanently using UART A.
//! Ports PA8 and PA9 are used for this.
//!
//! Instructions:
//!
//! 1. Tie a USB to UART converter with RX to PA9 and TX to PA8 for UART A.
//! 2. Connect to the serial interface by using an application like Putty or picocom. You can
//! can verify the correctness of the sent strings.
#![no_std]
#![no_main]
// This imports the logger and the panic handler.
use embassy_example as _;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
use embedded_io_async::Write;
use va108xx_hal::{
gpio::{Output, PinState},
pac::{self, interrupt},
pins::PinsA,
prelude::*,
uart::{self, on_interrupt_tx, Bank, TxAsync},
InterruptConfig,
};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
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 mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
let porta = PinsA::new(dp.porta);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let mut led1 = Output::new(porta.pa7, PinState::Low);
let mut led2 = Output::new(porta.pa6, PinState::Low);
let tx = porta.pa9;
let rx = porta.pa8;
let uarta = uart::Uart::new_with_interrupt(
dp.uarta,
tx,
rx,
50.MHz(),
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC2, true, true),
)
.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::info!("Current time: {}", Instant::now().as_secs());
led0.toggle();
led1.toggle();
led2.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 OC2() {
on_interrupt_tx(Bank::Uart0);
}

View File

@ -0,0 +1,3 @@
#![no_std]
use defmt_rtt as _;
use panic_probe as _;

View File

@ -0,0 +1,64 @@
#![no_std]
#![no_main]
use embassy_example as _;
use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker};
cfg_if::cfg_if! {
if #[cfg(feature = "custom-irqs")] {
use va108xx_embassy::embassy_time_driver_irqs;
use va108xx_hal::pac::interrupt;
embassy_time_driver_irqs!(timekeeper_irq = OC23, alarm_irq = OC24);
}
}
use va108xx_hal::{
gpio::{Output, PinState},
pac,
pins::PinsA,
prelude::*,
};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
// main is itself an async function.
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
defmt::println!("-- VA108xx Embassy Demo --");
let mut dp = pac::Peripherals::take().unwrap();
// Safety: Only called once here.
cfg_if::cfg_if! {
if #[cfg(not(feature = "custom-irqs"))] {
va108xx_embassy::init(
&mut dp.sysconfig,
&dp.irqsel,
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
);
} else {
va108xx_embassy::init_with_custom_irqs(
SYSCLK_FREQ,
dp.tim23,
dp.tim22,
pac::Interrupt::OC23,
pac::Interrupt::OC24,
);
}
}
let porta = PinsA::new(dp.porta);
let mut led0 = Output::new(porta.pa10, PinState::Low);
let mut led1 = Output::new(porta.pa7, PinState::Low);
let mut led2 = Output::new(porta.pa6, PinState::Low);
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;
defmt::info!("Current time: {}", Instant::now().as_secs());
led0.toggle();
led1.toggle();
led2.toggle();
}
}

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

@ -0,0 +1,27 @@
[package]
name = "rtic-example"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-io = "0.6"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
# Even though we do not use this directly, we need to activate this feature explicitely
# so that RTIC compiles because thumv6 does not have CAS operations natively.
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]}
rtic = { version = "2", features = ["thumbv6-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
rtic-sync = { version = "1.3", features = ["defmt-03"] }
once_cell = {version = "1", default-features = false, features = ["critical-section"]}
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
va108xx-hal = { version = "0.11", path = "../../va108xx-hal" }
vorago-reb1 = { version = "0.8", path = "../../vorago-reb1" }

View File

@ -0,0 +1,98 @@
//! Blinky button application for the REB1 board using RTIC
#![no_main]
#![no_std]
#[rtic::app(device = pac)]
mod app {
use rtic_example::SYSCLK_FREQ;
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use va108xx_hal::{
clock::{set_clk_div_register, FilterClkSel},
gpio::{FilterType, InterruptEdge},
pac,
pins::PinsA,
timer::InterruptConfig,
};
use vorago_reb1::button::Button;
use vorago_reb1::leds::Leds;
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[derive(Debug, PartialEq, defmt::Format)]
pub enum PressMode {
Toggle,
Keep,
}
// You can change the press mode here
const DEFAULT_MODE: PressMode = PressMode::Toggle;
#[local]
struct Local {
leds: Leds,
button: Button,
mode: PressMode,
}
#[shared]
struct Shared {}
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago Button IRQ Example --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let mode = DEFAULT_MODE;
defmt::info!("Using {:?} mode", mode);
let mut dp = cx.device;
let pinsa = PinsA::new(dp.porta);
let edge_irq = match mode {
PressMode::Toggle => InterruptEdge::HighToLow,
PressMode::Keep => InterruptEdge::BothEdges,
};
// Configure an edge interrupt on the button and route it to interrupt vector 15
let mut button = Button::new(pinsa.pa11);
if mode == PressMode::Toggle {
// This filter debounces the switch for edge based interrupts
button.configure_filter_type(FilterType::FilterFourCycles, FilterClkSel::Clk1);
set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
}
button.configure_and_enable_edge_interrupt(
edge_irq,
InterruptConfig::new(pac::interrupt::OC15, true, true),
);
let mut leds = Leds::new(pinsa.pa10, pinsa.pa7, pinsa.pa6);
for led in leds.iter_mut() {
led.off();
}
(Shared {}, Local { leds, button, mode })
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
cortex_m::asm::nop();
}
}
#[task(binds = OC15, local=[button, leds, mode])]
fn button_task(cx: button_task::Context) {
let leds = cx.local.leds;
let button = cx.local.button;
let mode = cx.local.mode;
if *mode == PressMode::Toggle {
leds[0].toggle();
} else if button.released() {
leds[0].off();
} else {
leds[0].on();
}
}
}

View File

@ -2,10 +2,12 @@
#![no_main]
#![no_std]
use defmt_testapp as _;
#[rtic::app(device = pac)]
mod app {
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use va108xx_hal::pac;
#[local]

View File

@ -0,0 +1,133 @@
//! More complex UART application on UART PA8 (TX) and PA9 (RX).
//!
//! Uses the IRQ capabilities of the VA10820 peripheral and the RTIC framework to poll the UART in
//! a non-blocking way. All received data will be sent back to the sender.
#![no_main]
#![no_std]
use ringbuf::StaticRb;
// Larger buffer for TC to be able to hold the possibly large memory write packets.
const RX_RING_BUF_SIZE: usize = 1024;
#[rtic::app(device = pac, dispatchers = [OC4])]
mod app {
use super::*;
use embedded_io::Write;
use ringbuf::traits::{Consumer, Observer, Producer};
use rtic_example::SYSCLK_FREQ;
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use rtic_monotonics::Monotonic;
use va108xx_hal::{
pac,
pins::PinsA,
prelude::*,
uart::{self, RxWithInterrupt, Tx},
InterruptConfig,
};
#[local]
struct Local {
rx: RxWithInterrupt,
tx: Tx,
}
#[shared]
struct Shared {
rb: StaticRb<u8, RX_RING_BUF_SIZE>,
}
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- VA108xx UART Echo with IRQ example application--");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let dp = cx.device;
let gpioa = PinsA::new(dp.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
let irq_uart = uart::Uart::new_with_interrupt(
dp.uarta,
tx,
rx,
SYSCLK_FREQ,
115200.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC3, true, true),
)
.unwrap();
let (tx, rx) = irq_uart.split();
let mut rx = rx.into_rx_with_irq();
rx.start();
echo_handler::spawn().unwrap();
(
Shared {
rb: StaticRb::default(),
},
Local { rx, tx },
)
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
cortex_m::asm::nop();
}
}
#[task(
binds = OC3,
shared = [rb],
local = [
rx,
],
)]
fn reception_task(mut cx: reception_task::Context) {
let mut buf: [u8; 16] = [0; 16];
let mut ringbuf_full = false;
let result = cx.local.rx.on_interrupt(&mut buf);
if result.bytes_read > 0 && result.errors.is_none() {
cx.shared.rb.lock(|rb| {
if rb.vacant_len() < result.bytes_read {
ringbuf_full = true;
} else {
rb.push_slice(&buf[0..result.bytes_read]);
}
});
}
if ringbuf_full {
// Could also drop oldest data, but that would require the consumer to be shared.
defmt::println!("buffer full, data was dropped");
}
}
#[task(shared = [rb], local = [
buf: [u8; RX_RING_BUF_SIZE] = [0; RX_RING_BUF_SIZE],
tx
], priority=1)]
async fn echo_handler(mut cx: echo_handler::Context) {
loop {
cx.shared.rb.lock(|rb| {
let bytes_to_read = rb.occupied_len();
if bytes_to_read > 0 {
let actual_read_bytes = rb.pop_slice(&mut cx.local.buf[0..bytes_to_read]);
cx.local
.tx
.write_all(&cx.local.buf[0..actual_read_bytes])
.expect("Failed to write to TX");
}
});
Mono::delay(50.millis()).await;
}
}
}

4
examples/rtic/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
#![no_std]
use va108xx_hal::time::Hertz;
pub const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);

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

@ -0,0 +1,68 @@
//! RTIC minimal blinky
#![no_main]
#![no_std]
#[rtic::app(device = pac, dispatchers = [OC31, OC30, OC29])]
mod app {
use cortex_m::asm;
use rtic_example::SYSCLK_FREQ;
use rtic_monotonics::systick::prelude::*;
use rtic_monotonics::Monotonic;
// Import panic provider.
use panic_probe as _;
// Import global logger.
use defmt_rtt as _;
use va108xx_hal::{
gpio::{Output, PinState},
pac,
pins::PinsA,
};
#[local]
struct Local {
led0: Output,
led1: Output,
led2: Output,
}
#[shared]
struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago VA108xx RTIC template --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let porta = PinsA::new(cx.device.porta);
let led0 = Output::new(porta.pa10, PinState::Low);
let led1 = Output::new(porta.pa7, PinState::Low);
let led2 = Output::new(porta.pa6, PinState::Low);
blinky::spawn().ok();
(Shared {}, Local { led0, led1, led2 })
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
asm::nop();
}
}
#[task(
priority = 3,
local=[led0, led1, led2],
)]
async fn blinky(cx: blinky::Context) {
loop {
defmt::println!("toggling LEDs");
cx.local.led0.toggle();
cx.local.led1.toggle();
cx.local.led2.toggle();
Mono::delay(1000.millis()).await;
}
}
}

View File

@ -4,34 +4,24 @@ version = "0.1.0"
edition = "2021"
[dependencies]
panic-halt = "0.2"
cortex-m = {version = "0.7", features = ["critical-section-single-core"]}
panic-rtt-target = "0.1"
cortex-m-rt = "0.7"
rtt-target = "0.5"
rtic-sync = { version = "1.3", features = ["defmt-03"] }
panic-halt = "1"
critical-section = "1"
defmt-rtt = "0.4"
defmt = "1"
panic-probe = { version = "1", features = ["defmt"] }
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
cortex-m-semihosting = "0.5.0"
# I'd really like to use those, but it is tricky without probe-rs..
# defmt = "0.3"
# defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
# panic-probe = { version = "0.3", features = ["print-defmt"] }
[dependencies.rtic]
version = "2"
features = ["thumbv6-backend"]
[dependencies.rtic-monotonics]
version = "1"
features = ["cortex-m-systick"]
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
[dependencies.va108xx-hal]
version = "0.6"
version = "0.11"
path = "../../va108xx-hal"
features = ["rt", "defmt"]
features = ["defmt"]
[dependencies.va108xx]
version = "0.3"
path = "../../va108xx"
[dependencies.vorago-reb1]
path = "../../vorago-reb1"
version = "0.8"

View File

@ -8,7 +8,7 @@
use cortex_m_rt::entry;
use panic_halt as _;
use va108xx as pac;
use va108xx_hal::pac;
// REB LED pin definitions. All on port A
const LED_D2: u32 = 1 << 10;

View File

@ -7,58 +7,40 @@
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::{
delay::DelayNs,
digital::{OutputPin, StatefulOutputPin},
};
use embedded_hal::delay::DelayNs;
use panic_halt as _;
use va108xx_hal::{
gpio::PinsA,
pac::{self, interrupt},
gpio::{Output, PinState},
pac::{self},
pins::PinsA,
prelude::*,
pwm::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer},
timer::DelayMs,
IrqCfg,
timer::CountdownTimer,
};
#[entry]
fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap();
let mut delay_ms = DelayMs::new(set_up_ms_tick(
IrqCfg::new(interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
))
.unwrap();
let mut delay_tim1 = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
let mut led1 = porta.pa10.into_readable_push_pull_output();
let mut led2 = porta.pa7.into_readable_push_pull_output();
let mut led3 = porta.pa6.into_readable_push_pull_output();
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim1, 50.MHz());
let porta = PinsA::new(dp.porta);
let mut led1 = Output::new(porta.pa10, PinState::Low);
let mut led2 = Output::new(porta.pa7, PinState::Low);
let mut led3 = Output::new(porta.pa6, PinState::Low);
for _ in 0..10 {
led1.set_low().ok();
led2.set_low().ok();
led3.set_low().ok();
delay_ms.delay_ms(200);
led1.set_high().ok();
led2.set_high().ok();
led3.set_high().ok();
delay_tim1.delay_ms(200);
led1.set_low();
led2.set_low();
led3.set_low();
delay.delay_ms(200);
led1.set_high();
led2.set_high();
led3.set_high();
delay.delay_ms(200);
}
loop {
led1.toggle().ok();
delay_ms.delay_ms(200);
led2.toggle().ok();
delay_tim1.delay_ms(200);
led3.toggle().ok();
delay_ms.delay_ms(200);
led1.toggle();
delay.delay_ms(200);
led2.toggle();
delay.delay_ms(200);
led3.toggle();
delay.delay_ms(200);
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler()
}

View File

@ -6,92 +6,68 @@
#![no_std]
#![allow(non_snake_case)]
use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use embedded_hal::delay::DelayNs;
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 va108xx_hal::{
pac::{self, interrupt},
prelude::*,
timer::{
default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource,
CountDownTimer, Event, IrqCfg,
},
timer::{CascadeControl, CascadeSelect, CascadeSource, CountdownTimer, InterruptConfig},
};
static CSD_TGT_1: Mutex<RefCell<Option<CountDownTimer<pac::Tim4>>>> =
Mutex::new(RefCell::new(None));
static CSD_TGT_2: Mutex<RefCell<Option<CountDownTimer<pac::Tim5>>>> =
Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx Cascade example application--");
defmt::println!("-- VA108xx Cascade example application--");
let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
// Will be started periodically to trigger a cascade
let mut cascade_triggerer =
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim3).auto_disable(true);
cascade_triggerer.listen(
Event::TimeOut,
IrqCfg::new(va108xx::Interrupt::OC1, true, false),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
let mut cascade_triggerer = CountdownTimer::new(dp.tim3, 50.MHz());
cascade_triggerer.auto_disable(true);
cascade_triggerer.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC1, true, false));
cascade_triggerer.enable();
// First target for cascade
let mut cascade_target_1 =
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true);
let mut cascade_target_1 = CountdownTimer::new(dp.tim4, 50.MHz());
cascade_target_1.auto_deactivate(true);
cascade_target_1
.cascade_0_source(CascadeSource::TimBase, Some(3))
.expect("Configuring cascade source for TIM4 failed");
let mut csd_cfg = CascadeCtrl {
enb_start_src_csd0: true,
.cascade_source(CascadeSelect::Csd0, CascadeSource::Tim(3))
.unwrap();
let mut csd_cfg = CascadeControl {
enable_src_0: true,
trigger_mode_0: true,
..Default::default()
};
// Use trigger mode here
csd_cfg.trg_csd0 = true;
cascade_target_1.cascade_control(csd_cfg);
// Normally it should already be sufficient to activate IRQ in the CTRL
// register but a full interrupt is use here to display print output when
// the timer expires
cascade_target_1.listen(
Event::TimeOut,
IrqCfg::new(va108xx::Interrupt::OC2, true, false),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
cascade_target_1.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC2, true, false));
// The counter will only activate when the cascade signal is coming in so
// it is okay to call start here to set the reset value
cascade_target_1.start(1.Hz());
// Activated by first cascade target
let mut cascade_target_2 =
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true);
let mut cascade_target_2 = CountdownTimer::new(dp.tim5, 50.MHz());
cascade_target_2.auto_deactivate(true);
// Set TIM4 as cascade source
cascade_target_2
.cascade_1_source(CascadeSource::TimBase, Some(4))
.expect("Configuring cascade source for TIM5 failed");
.cascade_source(CascadeSelect::Csd1, CascadeSource::Tim(4))
.unwrap();
csd_cfg = CascadeCtrl::default();
csd_cfg.enb_start_src_csd1 = true;
csd_cfg = CascadeControl::default();
csd_cfg.enable_src_1 = true;
// Use trigger mode here
csd_cfg.trg_csd1 = true;
csd_cfg.trigger_mode_1 = true;
cascade_target_2.cascade_control(csd_cfg);
// Normally it should already be sufficient to activate IRQ in the CTRL
// register but a full interrupt is use here to display print output when
// the timer expires
cascade_target_2.listen(
Event::TimeOut,
IrqCfg::new(va108xx::Interrupt::OC3, true, false),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
cascade_target_2.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC3, true, false));
// The counter will only activate when the cascade signal is coming in so
// it is okay to call start here to set the reset value
cascade_target_2.start(1.Hz());
@ -103,40 +79,31 @@ fn main() -> ! {
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC2);
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC3);
}
// Make both cascade targets accessible from the IRQ handler with the Mutex dance
cortex_m::interrupt::free(|cs| {
CSD_TGT_1.borrow(cs).replace(Some(cascade_target_1));
CSD_TGT_2.borrow(cs).replace(Some(cascade_target_2));
});
loop {
rprintln!("-- Triggering cascade in 0.5 seconds --");
defmt::info!("-- Triggering cascade in 0.5 seconds --");
cascade_triggerer.start(2.Hz());
delay.delay_ms(5000);
}
}
#[interrupt]
fn OC0() {
default_ms_irq_handler()
}
#[interrupt]
fn OC1() {
static mut IDX: u32 = 0;
rprintln!("{}: Cascade triggered timed out", &IDX);
defmt::info!("{}: Cascade trigger timed out", &IDX);
*IDX += 1;
}
#[interrupt]
fn OC2() {
static mut IDX: u32 = 0;
rprintln!("{}: First cascade target timed out", &IDX);
defmt::info!("{}: First cascade target timed out", &IDX);
*IDX += 1;
}
#[interrupt]
fn OC3() {
static mut IDX: u32 = 0;
rprintln!("{}: Second cascade target timed out", &IDX);
defmt::info!("{}: Second cascade target timed out", &IDX);
*IDX += 1;
}

View File

@ -1,39 +1,36 @@
//! Simple PWM example
//!
//! Outputs a PWM waveform on pin PA3.
#![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 va108xx_hal::{
gpio::PinsA,
pac,
pins::PinsA,
prelude::*,
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
timer::set_up_ms_delay_provider,
pwm::{self, 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();
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
let mut pwm = pwm::PwmPin::new(
(pinsa.pa3.into_funsel_1(), dp.tim3),
50.MHz(),
&mut dp.sysconfig,
10.Hz(),
);
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
defmt::println!("-- VA108xx PWM example application--");
let dp = pac::Peripherals::take().unwrap();
let pinsa = PinsA::new(dp.porta);
let mut pwm = pwm::PwmPin::new(pinsa.pa3, dp.tim3, 50.MHz(), 10.Hz()).unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
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
@ -42,11 +39,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();
}
@ -55,7 +51,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 {
@ -64,9 +60,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,30 +0,0 @@
//! Empty RTIC project template
#![no_main]
#![no_std]
#[rtic::app(device = pac)]
mod app {
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_default};
use va108xx_hal::pac;
#[local]
struct Local {}
#[shared]
struct Shared {}
#[init]
fn init(_ctx: init::Context) -> (Shared, Local) {
rtt_init_default!();
rprintln!("-- Vorago RTIC template --");
(Shared {}, Local {})
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
#[allow(clippy::empty_loop)]
loop {}
}
}

View File

@ -1,19 +0,0 @@
//! Code to test RTT logger functionality
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use panic_halt as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx as _;
#[entry]
fn main() -> ! {
rtt_init_print!();
let mut counter = 0;
loop {
rprintln!("{}: Hello, world!", counter);
counter += 1;
cortex_m::asm::delay(25_000_000);
}
}

View File

@ -1,31 +1,28 @@
//! SPI example application
#![no_main]
#![no_std]
use core::cell::RefCell;
use cortex_m_rt::entry;
use embedded_hal::{
delay::DelayNs,
spi::{Mode, SpiBus, MODE_0},
};
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 va108xx_hal::{
gpio::{PinsA, PinsB},
pac::{self, interrupt},
pac,
pins::{PinsA, PinsB},
prelude::*,
pwm::{default_ms_irq_handler, set_up_ms_tick},
spi::{self, Spi, SpiBase, TransferConfig},
IrqCfg,
spi::{self, configure_pin_as_hw_cs_pin, Spi, SpiClkConfig, TransferConfig},
timer::CountdownTimer,
};
#[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
TestBuffer,
MosiMisoTiedTogetherManually,
}
#[derive(PartialEq, Debug)]
@ -44,21 +41,14 @@ const FILL_WORD: u8 = 0x0f;
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx SPI example application--");
let mut dp = pac::Peripherals::take().unwrap();
let mut delay = set_up_ms_tick(
IrqCfg::new(interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
defmt::println!("-- VA108xx SPI example application--");
let dp = pac::Peripherals::take().unwrap();
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz())
.expect("creating SPI clock config failed");
let pinsa = PinsA::new(dp.porta);
let pinsb = PinsB::new(dp.portb);
let mut spi_cfg = spi::SpiConfig::default();
if EXAMPLE_SEL == ExampleSelect::Loopback {
@ -66,179 +56,82 @@ fn main() -> ! {
}
// Set up the SPI peripheral
match SPI_BUS_SEL {
let mut spi = match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA => {
let (sck, mosi, miso) = (
pinsa.pa31.into_funsel_1(),
pinsa.pa30.into_funsel_1(),
pinsa.pa29.into_funsel_1(),
);
let mut spia = Spi::spia(
dp.spia,
(sck, miso, mosi),
50.MHz(),
spi_cfg,
Some(&mut dp.sysconfig),
None,
);
let (sck, mosi, miso) = (pinsa.pa31, pinsa.pa30, pinsa.pa29);
let mut spia = Spi::new(dp.spia, (sck, miso, mosi), spi_cfg).unwrap();
spia.set_fill_word(FILL_WORD);
spia_ref.borrow_mut().replace(spia.downgrade());
spia
}
SpiBusSelect::SpiAPortB => {
let (sck, mosi, miso) = (
pinsb.pb9.into_funsel_2(),
pinsb.pb8.into_funsel_2(),
pinsb.pb7.into_funsel_2(),
);
let mut spia = Spi::spia(
dp.spia,
(sck, miso, mosi),
50.MHz(),
spi_cfg,
Some(&mut dp.sysconfig),
None,
);
let (sck, mosi, miso) = (pinsb.pb9, pinsb.pb8, pinsb.pb7);
let mut spia = Spi::new(dp.spia, (sck, miso, mosi), spi_cfg).unwrap();
spia.set_fill_word(FILL_WORD);
spia_ref.borrow_mut().replace(spia.downgrade());
spia
}
SpiBusSelect::SpiBPortB => {
let (sck, mosi, miso) = (
pinsb.pb5.into_funsel_1(),
pinsb.pb4.into_funsel_1(),
pinsb.pb3.into_funsel_1(),
);
let mut spib = Spi::spib(
dp.spib,
(sck, miso, mosi),
50.MHz(),
spi_cfg,
Some(&mut dp.sysconfig),
None,
);
let (sck, mosi, miso) = (pinsb.pb5, pinsb.pb4, pinsb.pb3);
let mut spib = Spi::new(dp.spib, (sck, miso, mosi), spi_cfg).unwrap();
spib.set_fill_word(FILL_WORD);
spib_ref.borrow_mut().replace(spib.downgrade());
spib
}
}
};
// Configure transfer specific properties here
match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
let transfer_cfg =
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
spi.cfg_transfer(&transfer_cfg);
}
let transfer_cfg = TransferConfig {
clk_cfg: Some(spi_clk_cfg),
mode: Some(SPI_MODE),
sod: true,
blockmode: BLOCKMODE,
bmstall: true,
hw_cs: None,
};
spi.cfg_transfer(&transfer_cfg);
}
SpiBusSelect::SpiBPortB => {
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
let hw_cs_pin = pinsb.pb2.into_funsel_1();
let transfer_cfg = TransferConfig::new(
SPI_SPEED_KHZ.kHz(),
SPI_MODE,
Some(hw_cs_pin),
BLOCKMODE,
false,
);
spi.cfg_transfer(&transfer_cfg);
}
let hw_cs_pin = configure_pin_as_hw_cs_pin(pinsb.pb2);
let transfer_cfg = TransferConfig {
clk_cfg: Some(spi_clk_cfg),
mode: Some(SPI_MODE),
sod: false,
blockmode: BLOCKMODE,
bmstall: true,
hw_cs: Some(hw_cs_pin),
};
spi.cfg_transfer(&transfer_cfg);
}
}
// Application logic
loop {
let mut reply_buf: [u8; 8] = [0; 8];
match SPI_BUS_SEL {
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
if EXAMPLE_SEL == ExampleSelect::Loopback {
// Can't really verify correct reply here.
spi.write(&[0x42]).expect("write failed");
// Because of the loopback mode, we should get back the fill word here.
spi.read(&mut reply_buf[0..1]).unwrap();
assert_eq!(reply_buf[0], FILL_WORD);
delay.delay_ms(500_u32);
// Can't really verify correct reply here.
spi.write(&[0x42]).expect("write failed");
// Because of the loopback mode, we should get back the fill word here.
spi.read(&mut reply_buf[0..1]).unwrap();
assert_eq!(reply_buf[0], FILL_WORD);
delay.delay_ms(500_u32);
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
assert_eq!(tx_buf, reply_buf[0..3]);
rprintln!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(500_u32);
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
assert_eq!(tx_buf, reply_buf[0..3]);
defmt::info!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(500_u32);
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
rprintln!(
"Received reply: {}, {}, {}",
tx_rx_buf[0],
tx_rx_buf[1],
tx_rx_buf[2]
);
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
} else {
let send_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &send_buf).unwrap();
rprintln!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(1000_u32);
}
}
}
SpiBusSelect::SpiBPortB => {
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
if EXAMPLE_SEL == ExampleSelect::Loopback {
// Can't really verify correct reply here.
spi.write(&[0x42]).expect("write failed");
// Because of the loopback mode, we should get back the fill word here.
spi.read(&mut reply_buf[0..1]).unwrap();
assert_eq!(reply_buf[0], FILL_WORD);
delay.delay_ms(500_u32);
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
assert_eq!(tx_buf, reply_buf[0..3]);
rprintln!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(500_u32);
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
rprintln!(
"Received reply: {}, {}, {}",
tx_rx_buf[0],
tx_rx_buf[1],
tx_rx_buf[2]
);
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
} else {
let send_buf: [u8; 3] = [0x01, 0x02, 0x03];
spi.transfer(&mut reply_buf[0..3], &send_buf).unwrap();
rprintln!(
"Received reply: {}, {}, {}",
reply_buf[0],
reply_buf[1],
reply_buf[2]
);
delay.delay_ms(1000_u32);
}
}
}
}
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
defmt::info!(
"Received reply: {}, {}, {}",
tx_rx_buf[0],
tx_rx_buf[1],
tx_rx_buf[2]
);
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
}
}
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler()
}

View File

@ -2,17 +2,19 @@
#![no_main]
#![no_std]
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use embedded_hal::delay::DelayNs;
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use portable_atomic::AtomicU32;
use va108xx_hal::{
clock::{get_sys_clock, set_sys_clock},
pac::{self, interrupt},
prelude::*,
time::Hertz,
timer::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer, Event, IrqCfg, MS_COUNTER},
timer::{CountdownTimer, InterruptConfig},
};
#[allow(dead_code)]
@ -21,14 +23,15 @@ 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 delay = CountdownTimer::new(dp.tim2, 50.MHz());
let mut last_ms = 0;
rprintln!("-- Vorago system ticks using timers --");
defmt::info!("-- Vorago system ticks using timers --");
set_sys_clock(50.MHz());
let lib_type = LibType::Hal;
match lib_type {
@ -64,33 +67,24 @@ fn main() -> ! {
}
}
LibType::Hal => {
set_up_ms_tick(
IrqCfg::new(interrupt::OC0, true, true),
&mut dp.sysconfig,
Some(&mut dp.irqsel),
50.MHz(),
dp.tim0,
);
let mut second_timer =
CountDownTimer::new(&mut dp.sysconfig, get_sys_clock().unwrap(), dp.tim1);
second_timer.listen(
Event::TimeOut,
IrqCfg::new(interrupt::OC1, true, true),
Some(&mut dp.irqsel),
Some(&mut dp.sysconfig),
);
let mut ms_timer = CountdownTimer::new(dp.tim0, get_sys_clock().unwrap());
ms_timer.enable_interrupt(InterruptConfig::new(interrupt::OC0, true, true));
ms_timer.start(1.kHz());
let mut second_timer = CountdownTimer::new(dp.tim1, get_sys_clock().unwrap());
second_timer.enable_interrupt(InterruptConfig::new(interrupt::OC1, true, true));
second_timer.start(1.Hz());
}
}
loop {
let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get());
let current_ms = MS_COUNTER.load(portable_atomic::Ordering::Relaxed);
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);
// To prevent drift.
last_ms += 1000;
defmt::info!("MS counter: {}", current_ms);
let second = SEC_COUNTER.load(portable_atomic::Ordering::Relaxed);
defmt::info!("Second counter: {}", second);
}
cortex_m::asm::delay(10000);
delay.delay_ms(50);
}
}
@ -104,15 +98,11 @@ fn unmask_irqs() {
#[interrupt]
#[allow(non_snake_case)]
fn OC0() {
default_ms_irq_handler()
MS_COUNTER.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
#[interrupt]
#[allow(non_snake_case)]
fn OC1() {
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, portable_atomic::Ordering::Relaxed);
}

View File

@ -1,172 +0,0 @@
//! More complex UART application
//!
//! Uses the IRQ capabilities of the VA10820 peripheral and the RTIC framework to poll the UART in
//! a non-blocking way. You can send variably sized strings to the VA10820 which will be echoed
//! back to the sender.
//!
//! This script was tested with an Arduino Due. You can find the test script in the
//! [`/test/DueSerialTest`](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/test/DueSerialTest)
//! folder.
#![no_main]
#![no_std]
#[rtic::app(device = pac, dispatchers = [OC4])]
mod app {
use embedded_io::Write;
use panic_rtt_target as _;
use rtic_monotonics::systick::Systick;
use rtic_sync::make_channel;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{
gpio::PinsB,
pac,
prelude::*,
time::Hertz,
uart::{self, IrqCfg, IrqResult, UartWithIrqBase},
};
#[local]
struct Local {
rx_info_tx: rtic_sync::channel::Sender<'static, RxInfo, 3>,
rx_info_rx: rtic_sync::channel::Receiver<'static, RxInfo, 3>,
}
#[shared]
struct Shared {
irq_uart: UartWithIrqBase<pac::Uartb>,
rx_buf: [u8; 64],
}
#[derive(Debug, Copy, Clone)]
struct RxInfo {
pub bytes_read: usize,
pub end_idx: usize,
pub timeout: bool,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
rtt_init_print!();
//set_print_channel(channels.up.0);
rprintln!("-- VA108xx UART IRQ example application--");
// Initialize the systick interrupt & obtain the token to prove that we did
let systick_mono_token = rtic_monotonics::create_systick_token!();
Systick::start(
cx.core.SYST,
Hertz::from(50.MHz()).raw(),
systick_mono_token,
);
let mut dp = cx.device;
let gpiob = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
let tx = gpiob.pb21.into_funsel_1();
let rx = gpiob.pb20.into_funsel_1();
let irq_cfg = IrqCfg::new(pac::interrupt::OC3, true, true);
let (mut irq_uart, _) =
uart::Uart::uartb(dp.uartb, (tx, rx), 115200.Hz(), &mut dp.sysconfig, 50.MHz())
.into_uart_with_irq(irq_cfg, Some(&mut dp.sysconfig), Some(&mut dp.irqsel))
.downgrade();
irq_uart
.read_fixed_len_using_irq(64, true)
.expect("Read initialization failed");
let (rx_info_tx, rx_info_rx) = make_channel!(RxInfo, 3);
let rx_buf: [u8; 64] = [0; 64];
//reply_handler::spawn().expect("spawning reply handler failed");
(
Shared { irq_uart, rx_buf },
Local {
rx_info_tx,
rx_info_rx,
},
)
}
// `shared` cannot be accessed from this context
#[idle]
fn idle(_cx: idle::Context) -> ! {
loop {
cortex_m::asm::nop();
}
}
#[task(
binds = OC3,
shared = [irq_uart, rx_buf],
local = [cnt: u32 = 0, result: IrqResult = IrqResult::new(), rx_info_tx],
)]
fn reception_task(cx: reception_task::Context) {
let result = cx.local.result;
let cnt: &mut u32 = cx.local.cnt;
let irq_uart = cx.shared.irq_uart;
let rx_buf = cx.shared.rx_buf;
let (completed, end_idx) = (irq_uart, rx_buf).lock(|irq_uart, rx_buf| {
match irq_uart.irq_handler(result, rx_buf) {
Ok(_) => {
if result.complete() {
// Initiate next transfer immediately
irq_uart
.read_fixed_len_using_irq(64, true)
.expect("Read operation init failed");
let mut end_idx = 0;
for idx in 0..rx_buf.len() {
if (rx_buf[idx] as char) == '\n' {
end_idx = idx;
break;
}
}
(true, end_idx)
} else {
(false, 0)
}
}
Err(e) => {
rprintln!("reception error {:?}", e);
(false, 0)
}
}
});
if completed {
rprintln!("counter: {}", cnt);
cx.local
.rx_info_tx
.try_send(RxInfo {
bytes_read: result.bytes_read,
end_idx,
timeout: result.timeout(),
})
.expect("RX queue full");
}
*cnt += 1;
}
#[task(shared = [irq_uart, rx_buf], local = [rx_info_rx], priority=1)]
async fn reply_handler(cx: reply_handler::Context) {
let mut irq_uart = cx.shared.irq_uart;
let mut rx_buf = cx.shared.rx_buf;
loop {
match cx.local.rx_info_rx.recv().await {
Ok(rx_info) => {
rprintln!("reception success, {} bytes read", rx_info.bytes_read);
if rx_info.timeout {
rprintln!("timeout occurred");
}
rx_buf.lock(|rx_buf| {
let string = core::str::from_utf8(&rx_buf[0..rx_info.end_idx])
.expect("Invalid string format");
rprintln!("read string: {}", string);
irq_uart.lock(|uart| {
writeln!(uart.uart, "{}", string).expect("Sending reply failed");
});
});
}
Err(e) => {
rprintln!("error receiving RX info: {:?}", e);
}
}
}
}
}

View File

@ -13,23 +13,25 @@
use cortex_m_rt::entry;
use embedded_hal_nb::{nb, serial::Read};
use embedded_io::Write as _;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{gpio::PinsA, pac, prelude::*, uart};
// Import panic provider.
use panic_probe as _;
// Import logger.
use defmt_rtt as _;
use va108xx_hal::{pac, pins::PinsA, prelude::*, uart};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("-- VA108xx UART example application--");
defmt::println!("-- VA108xx UART example application--");
let mut dp = pac::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let gpioa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
let tx = gpioa.pa9.into_funsel_2();
let rx = gpioa.pa8.into_funsel_2();
let gpioa = PinsA::new(dp.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
let uart =
uart::Uart::new_without_interrupt(dp.uarta, tx, rx, 50.MHz(), 115200.Hz().into()).unwrap();
let uarta = uart::Uart::uarta(dp.uarta, (tx, rx), 115200.Hz(), &mut dp.sysconfig, 50.MHz());
let (mut tx, mut rx) = uarta.split();
let (mut tx, mut rx) = uart.split();
writeln!(tx, "Hello World\r").unwrap();
loop {
// Echo what is received on the serial link.
@ -39,9 +41,6 @@ fn main() -> ! {
.expect("TX send error");
}
Err(nb::Error::WouldBlock) => (),
Err(nb::Error::Other(uart_error)) => {
rprintln!("UART receive error {:?}", uart_error);
}
}
}
}

View File

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

1
flashloader/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/venv

36
flashloader/Cargo.toml Normal file
View File

@ -0,0 +1,36 @@
[package]
name = "flashloader"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1"
embedded-hal-nb = "1"
embedded-io = "0.6"
defmt = "1"
defmt-rtt = { version = "0.4" }
panic-probe = { version = "0.3", features = ["print-defmt"] }
num_enum = { version = "0.7", default-features = false }
crc = "3"
cobs = { version = "0.3", default-features = false }
satrs = { version = "0.2", default-features = false }
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
spacepackets = { version = "0.11", default-features = false, features = ["defmt"] }
# Even though we do not use this directly, we need to activate this feature explicitely
# so that RTIC compiles because thumv6 does not have CAS operations natively.
portable-atomic = {version = "1", features = ["unsafe-assume-single-core"]}
rtic = { version = "2", features = ["thumbv6-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
rtic-sync = {version = "1", features = ["defmt-03"]}
[dependencies.va108xx-hal]
version = "0.11"
path = "../va108xx-hal"
features = ["defmt"]
[dependencies.vorago-reb1]
version = "0.8"

75
flashloader/README.md Normal file
View File

@ -0,0 +1,75 @@
VA108xx 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/va108xx-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 with the Pins PA8 (RX) and PA9 (TX) interface of the VA108xx 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/thumbv6m-none-eabi/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 -s a
```
to select the Slot A as a boot slot. The boot slot is stored in a reserved section in EEPROM
and will be read and used by the bootloader to determine which slot to boot.
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.

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

@ -0,0 +1,476 @@
#!/usr/bin/env python3
from typing import List, Tuple
from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm
import toml
import struct
import logging
import argparse
import time
import enum
from com_interface import ComInterface
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 = 0x3000
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 2
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 2
APP_A_START_ADDR = 0x3000
APP_B_END_ADDR = 0x20000 - 8
IMG_SLOT_SIZE = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
APP_A_END_ADDR = APP_A_START_ADDR + IMG_SLOT_SIZE
# 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 = APP_A_END_ADDR
# 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
CHUNK_SIZE = 400
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_copletion("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_copletion("ping command")
def await_for_command_copletion(self, context: str):
done = False
now = time.time()
while time.time() - now < 2.0:
if not self.com_if.data_available():
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_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER:
_LOGGER.error("can not corrupt bootloader")
if target == Target.APP_A:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_A,
),
)
if target == Target.APP_B:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_B,
),
)
def handle_flash_cmd(self, target: Target, file_path: Path) -> int:
loadable_segments = []
_LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
loadable_segments, total_size = create_loadable_segments(target, file_path)
check_segments(target, total_size)
print_segments_info(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])
)
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-ccitt-false")
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 VA108XX 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(
"-s", "--sel", choices=["a", "b"], help="Set boot slot (Slot A or B)"
)
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
boot_sel = None
if args.sel:
if args.sel == "a":
boot_sel = AppSel.APP_A
elif args.sel == "b":
boot_sel = AppSel.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 args.sel and boot_sel is not None:
image_loader.handle_boot_sel_cmd(boot_sel)
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:
if 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 check_segments(
target: Target,
total_size: int,
):
# Set context string and perform basic sanity checks.
if target == Target.BOOTLOADER and total_size > BOOTLOADER_MAX_SIZE:
raise ValueError(
f"provided bootloader app larger than allowed {total_size} bytes"
)
elif target == Target.APP_A and total_size > APP_A_MAX_SIZE:
raise ValueError(f"provided App A larger than allowed {total_size} bytes")
elif target == Target.APP_B and total_size > APP_B_MAX_SIZE:
raise ValueError(f"provided App B larger than allowed {total_size} bytes")
def print_segments_info(
target: Target,
loadable_segments: List[LoadableSegment],
total_size: int,
file_path: Path,
):
# Set context string and perform basic sanity checks.
if target == Target.BOOTLOADER:
context_str = "Bootloader"
elif target == Target.APP_A:
context_str = "App Slot A"
elif target == Target.APP_B:
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/ttyUSB1"

View File

@ -0,0 +1,5 @@
spacepackets == 0.28
com-interface == 0.1
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"
va108xx-hal = { version = "0.10.0" }
[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,11 @@
/* Special linker script for application slot A with an offset at address 0x3000 */
MEMORY
{
FLASH : ORIGIN = 0x00003000, LENGTH = 0xE7FC
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 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 */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

View File

@ -0,0 +1,25 @@
//! Simple blinky example using the HAL
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::CountdownTimer};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA108xx HAL blinky example for App Slot A");
let mut dp = pac::Peripherals::take().unwrap();
let mut timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
let mut led1 = porta.pa10.into_readable_push_pull_output();
loop {
led1.toggle().ok();
timer.delay_ms(500);
}
}

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"
va108xx-hal = { version = "0.10.0" }
[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,11 @@
/* Special linker script for application slot B */
MEMORY
{
FLASH : ORIGIN = 0x000117FC, LENGTH = 0xE7FC
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 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 */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

View File

@ -0,0 +1,25 @@
//! Simple blinky example using the HAL
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_hal::{gpio::PinsA, pac, prelude::*, timer::CountdownTimer};
#[entry]
fn main() -> ! {
rtt_init_print!();
rprintln!("VA108xx HAL blinky example for App Slot B");
let mut dp = pac::Peripherals::take().unwrap();
let mut timer = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim0);
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
let mut led2 = porta.pa7.into_readable_push_pull_output();
loop {
led2.toggle().ok();
timer.delay_ms(1000);
}
}

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

@ -0,0 +1,463 @@
//! Vorago flashloader which can be used to flash image A and image B via a simple
//! low-level CCSDS memory interface via a UART interface.
#![no_main]
#![no_std]
use defmt_rtt as _; // global logger
use num_enum::TryFromPrimitive;
use panic_probe as _;
use ringbuf::{
traits::{Consumer, Observer, Producer},
StaticRb,
};
use va108xx_hal::prelude::*;
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
const MAX_TC_SIZE: usize = 524;
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
const MAX_TM_SIZE: usize = 128;
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200;
const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
pub enum ActionId {
CorruptImageA = 128,
CorruptImageB = 129,
SetBootSlot = 130,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, defmt::Format)]
#[repr(u8)]
enum AppSel {
A = 0,
B = 1,
}
// Larger buffer for TC to be able to hold the possibly large memory write packets.
const BUF_RB_SIZE_TC: usize = 1024;
const SIZES_RB_SIZE_TC: usize = 16;
const BUF_RB_SIZE_TM: usize = 256;
const SIZES_RB_SIZE_TM: usize = 16;
pub struct RingBufWrapper<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf: StaticRb<u8, BUF_SIZE>,
pub sizes: StaticRb<usize, SIZES_LEN>,
}
pub const APP_A_START_ADDR: u32 = 0x3000;
pub const APP_A_END_ADDR: u32 = 0x117FC;
pub const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
pub const APP_B_END_ADDR: u32 = 0x20000;
pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
#[rtic::app(device = pac, dispatchers = [OC20, OC21, OC22])]
mod app {
use super::*;
use cortex_m::asm;
use embedded_io::Write;
use rtic::Mutex;
use rtic_monotonics::systick::prelude::*;
use satrs::pus::verification::{FailParams, VerificationReportCreator};
use spacepackets::ecss::PusServiceId;
use spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use va108xx_hal::pins::PinsA;
use va108xx_hal::uart::IrqContextTimeoutOrMaxSize;
use va108xx_hal::{pac, uart, InterruptConfig};
use vorago_reb1::m95m01::M95M01;
#[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,
verif_reporter: VerificationReportCreator,
nvm: M95M01,
}
#[shared]
struct Shared {
// Having this shared allows multiple tasks to generate telemetry.
tm_rb: RingBufWrapper<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
tc_rb: RingBufWrapper<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
}
rtic_monotonics::systick_monotonic!(Mono, 1000);
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago flashloader --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
let mut dp = cx.device;
let nvm = M95M01::new(&mut dp.sysconfig, SYSCLK_FREQ, dp.spic);
let gpioa = PinsA::new(dp.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
let irq_uart = uart::Uart::new_with_interrupt(
dp.uarta,
tx,
rx,
SYSCLK_FREQ,
UART_BAUDRATE.Hz().into(),
InterruptConfig::new(pac::Interrupt::OC0, true, true),
)
.unwrap();
let (tx, rx) = irq_uart.split();
// Unwrap is okay, we explicitely set the interrupt ID.
let mut rx = rx.into_rx_with_irq();
let verif_reporter = VerificationReportCreator::new(0).unwrap();
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 {
tc_rb: RingBufWrapper {
buf: StaticRb::default(),
sizes: StaticRb::default(),
},
tm_rb: RingBufWrapper {
buf: StaticRb::default(),
sizes: StaticRb::default(),
},
},
Local {
uart_rx: rx,
uart_tx: tx,
rx_context,
verif_reporter,
nvm,
},
)
}
// `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 = OC0,
local = [
cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
rx_context,
uart_rx,
],
shared = [tc_rb]
)]
fn uart_rx_irq(mut 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::debug!("RX Info: {:?}", cx.local.rx_context);
defmt::debug!("RX Result: {:?}", result);
}
if result.complete() {
// Check frame validity (must have COBS format) and decode the frame.
// Currently, we expect a full frame or a frame received through a timeout
// to be one COBS frame. We could parse for multiple COBS packets in one
// frame, but the additional complexity is not necessary here..
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
let decoded_size =
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
if decoded_size.is_err() {
defmt::warn!("COBS decoding failed");
} else {
let decoded_size = decoded_size.unwrap();
let mut tc_rb_full = false;
cx.shared.tc_rb.lock(|rb| {
if rb.sizes.vacant_len() >= 1 && rb.buf.vacant_len() >= decoded_size
{
rb.sizes.try_push(decoded_size).unwrap();
rb.buf.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
} else {
tc_rb_full = true;
}
});
if tc_rb_full {
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],
readback_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
src_data_buf: [u8; 16] = [0; 16],
verif_buf: [u8; 32] = [0; 32],
nvm,
verif_reporter
],
shared=[tm_rb, tc_rb]
)]
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.shared.tc_rb.lock(|rb| rb.sizes.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);
let popped_packet_len = cx
.shared
.tc_rb
.lock(|rb| rb.buf.pop_slice(&mut cx.local.tc_buf[0..packet_len]));
assert_eq!(popped_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_rb.lock(|prod| {
prod.sizes.try_push(tm.len_written()).unwrap();
prod.buf.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| {
let mut buf = [0u8; 4];
cx.local
.nvm
.read(base_addr as usize + 32, &mut buf)
.expect("reading from NVM failed");
buf[0] += 1;
cx.local
.nvm
.write(base_addr as usize + 32, &buf)
.expect("writing to NVM failed");
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.subservice() == ActionId::SetBootSlot as u8 {
if pus_tc.app_data().is_empty() {
defmt::warn!("App data for preferred image command too short");
}
let app_sel_result = AppSel::try_from(pus_tc.app_data()[0]);
if app_sel_result.is_err() {
defmt::warn!("Invalid app selection value: {}", pus_tc.app_data()[0]);
}
defmt::info!(
"received boot selection command with app select: {:?}",
app_sel_result.unwrap()
);
cx.local
.nvm
.write(PREFERRED_SLOT_OFFSET as usize, &[pus_tc.app_data()[0]])
.expect("writing to NVM failed");
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.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);
cx.local
.nvm
.write(offset as usize, data)
.expect("writing to NVM failed");
let tm = if !cx
.local
.nvm
.verify(offset as usize, data)
.expect("NVM verification failed")
{
defmt::warn!("verification of data written to NVM failed");
cx.local
.verif_reporter
.completion_failure(
cx.local.src_data_buf,
started_token,
0,
0,
FailParams::new(&[], &EcssEnumU8::new(0), &[]),
)
.expect("completion success failed")
} else {
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,
],
shared=[tm_rb]
)]
async fn pus_tm_tx_handler(mut cx: pus_tm_tx_handler::Context) {
loop {
let mut occupied_len = cx.shared.tm_rb.lock(|rb| rb.sizes.occupied_len());
while occupied_len > 0 {
let next_size = cx.shared.tm_rb.lock(|rb| {
let next_size = rb.sizes.try_pop().unwrap();
rb.buf.pop_slice(&mut cx.local.read_buf[0..next_size]);
next_size
});
cx.local.encoded_buf[0] = 0;
let send_size = cobs::encode(
&cx.local.read_buf[0..next_size],
&mut cx.local.encoded_buf[1..],
);
cx.local.encoded_buf[send_size + 1] = 0;
cx.local
.uart_tx
.write_all(&cx.local.encoded_buf[0..send_size + 2])
.unwrap();
occupied_len -= 1;
Mono::delay(2.millis()).await;
}
Mono::delay(50.millis()).await;
}
}
}

View File

@ -1,3 +1,3 @@
#!/bin/bash
JLinkGDBServer -select USB -device Cortex-M0 -endian little -if JTAG-speed auto \
-LocalhostOnly
JLinkGDBServer -select USB -device VA10820 -endian little -if JTAG -speed auto \
-LocalhostOnly -jtagconf -1,-1

View File

@ -9,27 +9,56 @@ variants:
core_access_options: !Arm
ap: 0
psel: 0x0
jtag_tap: 1
memory_map:
- !Ram
name: IRAM1
name: DRAM
range:
start: 0x10000000
end: 0x10008000
cores:
- main
- !Nvm
name: IROM1
name: NVM
range:
start: 0x0
end: 0x20000
is_boot_memory: true
cores:
- main
access:
write: false
boot: true
flash_algorithms:
- va108xx_fm25v20a_fram_128kb_prog
- va108xx_m95m01_128kb_prog
- va108xx_mr25h10_1mb_prog
- va108xx_ttflash_prog
- name: VA108xx_RAM
cores:
- name: main
type: armv6m
core_access_options: !Arm
ap: 0
psel: 0x0
jtag_tap: 1
memory_map:
- !Ram
name: DRAM
range:
start: 0x10000000
end: 0x10008000
cores:
- main
- !Ram
name: IRAM
range:
start: 0x0
end: 0x20000
cores:
- main
access:
write: false
boot: true
flash_algorithms:
- name: va108xx_fm25v20a_fram_128kb_prog
description: VA108_FM25V20A_FRAM_128KB

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
telnet localhost 19021 | defmt-print -e "$BINARY"

10
scripts/memory_app_a.x Normal file
View File

@ -0,0 +1,10 @@
MEMORY
{
FLASH : ORIGIN = 0x00003000, LENGTH = 0xE7F8 /* (128k - 12k) / 2 - 8 */
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 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 */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

10
scripts/memory_app_b.x Normal file
View File

@ -0,0 +1,10 @@
MEMORY
{
FLASH : ORIGIN = 0x00011800, LENGTH = 0xE7F8 /* (128k - 12k) / 2 - 8 */
RAM : ORIGIN = 0x10000000, LENGTH = 0x08000 /* 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 */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
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.2.1] 2025-03-07
- Bumped allowed va108xx-hal to v0.11
## [v0.2.0] 2025-02-17
- Bumped va108xx-hal to v0.10.0
- Remove `embassy` module, expose public functions in library root directly
## [v0.1.2] and [v0.1.1] 2025-02-13
Docs patch
## [v0.1.0] 2025-02-13
Initial release
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.2.1...HEAD
[v0.2.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.2.0...va10xx-embassy-v0.2.1
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-embassy-v0.1.2...va10xx-embassy-v0.2.0

View File

@ -0,0 +1,27 @@
[package]
name = "va108xx-embassy"
version = "0.2.1"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Embassy-rs support for the Vorago VA108xx family of microcontrollers"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va108xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va108xx-rs"
license = "Apache-2.0"
keywords = ["no-std", "hal", "cortex-m", "vorago", "va108xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
[dependencies]
vorago-shared-periphs = { version = "0.1", path = "../../vorago-shared-periphs" }
va108xx-hal = { path = "../va108xx-hal" }
[features]
default = ["irq-oc30-oc31"]
irqs-in-lib = []
# This determines the reserved interrupt functions for the embassy time drivers. Only one
# is allowed to be selected!
irq-oc28-oc29 = ["irqs-in-lib"]
irq-oc29-oc30 = ["irqs-in-lib"]
irq-oc30-oc31 = ["irqs-in-lib"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

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

@ -0,0 +1,10 @@
[![Crates.io](https://img.shields.io/crates/v/va108xx-embassy)](https://crates.io/crates/va108xx-embassy)
[![docs.rs](https://img.shields.io/docsrs/va108xx-embassy)](https://docs.rs/va108xx-embassy)
# Embassy-rs support for the Vorago VA108xx MCU family
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM
peripherals provided by the VA108xx family for this purpose.
The documentation contains more information on how to use this crate.

3
va108xx-embassy/docs.sh Executable file
View File

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

109
va108xx-embassy/src/lib.rs Normal file
View File

@ -0,0 +1,109 @@
//! # Embassy-rs support for the Vorago VA108xx MCU family
//!
//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for
//! the VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses
//! the TIM peripherals provided by the VA108xx family for this purpose.
//!
//! ## Usage
//!
//! This library exposes the [init] or the [init_with_custom_irqs] functions which set 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_with_custom_irqs] and [init] method.
//!
//! 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-oc30-oc31` feature flag. This library exposes three combinations:
//!
//! - `irq-oc30-oc31`: Uses [pac::Interrupt::OC30] and [pac::Interrupt::OC31]
//! - `irq-oc29-oc30`: Uses [pac::Interrupt::OC29] and [pac::Interrupt::OC30]
//! - `irq-oc28-oc29`: Uses [pac::Interrupt::OC28] and [pac::Interrupt::OC20]
//!
//! 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, [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))]
#[cfg(feature = "irqs-in-lib")]
use va108xx_hal::pac::{self, interrupt};
use va108xx_hal::time::Hertz;
use va108xx_hal::timer::TimMarker;
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-oc30-oc31")]
embassy_time_driver_irqs!(timekeeper_irq = OC31, alarm_irq = OC30);
#[cfg(feature = "irq-oc29-oc30")]
embassy_time_driver_irqs!(timekeeper_irq = OC30, alarm_irq = OC29);
#[cfg(feature = "irq-oc28-oc29")]
embassy_time_driver_irqs!(timekeeper_irq = OC29, alarm_irq = OC28);
/// Initialization method for embassy.
///
/// This should be used if the interrupt handler is provided by the library, which is the
/// default case.
#[cfg(feature = "irqs-in-lib")]
pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
sysclk: Hertz,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
) {
time_driver().__init(sysclk, timekeeper_tim, alarm_tim, TIMEKEEPER_IRQ, ALARM_IRQ)
}
/// Initialization method for embassy when using custom IRQ handlers.
///
/// Requires an explicit [pac::Interrupt] argument for the timekeeper and alarm IRQs.
pub fn init_with_custom_irqs<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
sysclk: Hertz,
timekeeper_tim: TimekeeperTim,
alarm_tim: AlarmTim,
timekeeper_irq: pac::Interrupt,
alarm_irq: pac::Interrupt,
) {
time_driver().__init(sysclk, timekeeper_tim, alarm_tim, timekeeper_irq, alarm_irq)
}

View File

@ -6,19 +6,144 @@ 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]
## 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.
## [v0.11.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.11.0] 2025-03-07
## Changed
- 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.10.0] 2025-02-17
## Added
- A lot of missing `defmt::Format` implementations.
## Changed
- Larger refactoring of GPIO library. The edge and level interrupt configurator functions do not
enable interrupts anymore. Instead, there are explicit `enbable_interrupt` and
`disable_interrupt` methods
- Renamed GPIO `DynGroup` to `Port`
- Rename generic GPIO interrupt handler into `on_interrupt_for_asynch_gpio`
into `on_interrupt_for_async_gpio_for_port` which expects a Port argument
## Fixed
- Bug in async GPIO interrupt handler where all enabled interrupts, even the ones which might
be unrelated to the pin, were disabled.
## [v0.9.0] 2025-02-13
## Fixed
- Important bugfix for UART driver which causes UART B drivers not to work.
## Removed
- Deleted some HAL re-exports in the PWM module
## 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`.
- `PinsA` and `PinsB` constructor do not expect an optional IOCONFIG argument anymore.
- 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`
## [v0.8.0] 2024-09-30
## Changed
- Improves `CascardSource` handling and general API when chosing cascade sources.
- Replaced `utility::unmask_irq` by `enable_interrupt` and `disable_interrupt` API.
- 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.
- Removed complete `timer` module re-export in `pwm` module
- `CountDownTimer` renamed to `CountdownTimer`
## Fixes
- Fixes for SPI peripheral: Flush implementation was incorrect and should now flush properly.
## [v0.7.0] 2024-07-04
- Replace `uarta` and `uartb` `Uart` constructors by `new` constructor
- Replace SPI `spia`, `spib` and `spic` constructors by `new` constructor
- Replace I2C `i2ca`, `i2cb` constructors by `new` constructor. Update constructor
to fail on invalid fast I2C speed system clock values
- Renamed `gpio::pins` to `gpio::pin` and `gpio::dynpins` to `gpio::dynpin`
- Simplify UART clock divider calculations and remove `libm` dependency consequently
## [v0.6.0] 2024-06-16
- Updated `embedded-hal` to v1
- Added optional `defmt` v0.3 feature and support.
## [v0.5.2] 2024-06-16
## v0.5.2 2024-06-16
## Fixed
- Replaced usage to `ptr::write_volatile` in UART module which is denied on more recent Rust
compilers.
## [v0.5.1]
## v0.5.1
### Changes
@ -27,7 +152,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `once_cell` to 1.12.0
- Other dependencies: Only revision has changed
## [v0.5.0]
## v0.5.0
### Added
@ -40,14 +165,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Bugfix in UART code where RX and TX could not be enabled or disabled independently
## [v0.4.3]
## v0.4.3
- Various smaller fixes for READMEs, update of links in documentation
- Simplified CI for github, do not use `cross`
- New `blinky-pac` example
- Use HAL delay in `blinky` example
## [v0.4.2]
## v0.4.2
### Added
@ -57,24 +182,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Clear TX and RX FIFO in SPI transfer function
## [v0.4.1]
## v0.4.1
### Fixed
- Initial blockmode setting was not set in SPI constructor
## [v0.4.0]
## v0.4.0
### Changed
- Replaced `Hertz` by `impl Into<Hertz>` completely and removed
`+ Copy` where not necessary
## [v0.3.1]
## v0.3.1
- Updated all links to point to new repository
## [v0.3.0]
## v0.3.0
### Added
@ -86,7 +211,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Primary repository now hosted on IRS external git: https://egit.irs.uni-stuttgart.de/rust/va108xx-hal
- Relicensed as Apache-2.0
## [0.2.3]
## v0.2.3
### Added
@ -98,7 +223,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Improved Timer API. It is now possible to simply use `new` on `CountDownTimer`
## [0.2.2]
## v0.2.2
### Added
@ -110,7 +235,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- API which expects values in Hertz now uses `impl Into<Hertz>` as input parameter
## [0.2.1]
## v0.2.1
### Added
@ -124,7 +249,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Moved the `FilterClkSel` struct to the `clock` module, re-exporting in `gpio`
- Clearing output state at initialization of Output pins
## [0.2.0]
## v0.2.0
### Changed
@ -139,7 +264,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Some bugfixes for GPIO implementation
- Rust edition updated to 2021
## [0.1.0]
## v0.1.0
### Added
@ -148,3 +273,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- RTT example application
- Added basic test binary in form of an example
- README with basic instructions how to set up own binary crate
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.11.0...HEAD
[v0.11.1]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.11.0...va108xx-hal-v0.11.1
[v0.11.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.10.0...va108xx-hal-v0.11.0
[v0.10.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.9.0...va108xx-hal-v0.10.0
[v0.9.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.8.0...va108xx-hal-v0.9.0
[v0.8.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.7.0...va108xx-hal-v0.8.0
[v0.7.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/va108xx-hal-v0.6.0...va108xx-hal-v0.7.0
[v0.6.0]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/tag/va108xx-hal-v0.6.0

View File

@ -1,11 +1,11 @@
[package]
name = "va108xx-hal"
version = "0.6.0"
version = "0.11.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "HAL for the Vorago VA108xx family of microcontrollers"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va108xx-hal"
repository = "https://egit.irs.uni-stuttgart.de/rust/va108xx-hal"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va108xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va108xx-rs"
license = "Apache-2.0"
keywords = ["no-std", "hal", "cortex-m", "vorago", "va108xx"]
categories = ["aerospace", "embedded", "no-std", "hardware-support"]
@ -15,35 +15,37 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7"
nb = "1"
paste = "1"
#vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor1x"] }
vorago-shared-periphs = { path = "../../vorago-shared-periphs", features = ["vor1x"] }
embedded-hal = "1"
embedded-hal-async = "1"
embedded-hal-nb = "1"
libm = "0.2"
embedded-io = "0.6"
embedded-io-async = "0.6"
fugit = "0.3"
typenum = "1"
critical-section = "1"
delegate = ">=0.12, <=0.13"
heapless = "0.8"
static_cell = "2"
thiserror = { version = "2", default-features = false }
void = { version = "1", default-features = false }
once_cell = { version = "1", default-features = false }
va108xx = { version = "0.5", default-features = false, features = ["critical-section", "defmt"] }
embassy-sync = "0.6"
defmt = { version = "0.3", optional = true }
delegate = "0.12"
[dependencies.va108xx]
version = "0.3.0"
path = "../va108xx"
default-features = false
features = ["critical-section"]
[dependencies.embedded-hal]
version = "1"
[dependencies.void]
version = "1"
default-features = false
[dependencies.once_cell]
version = "1.14"
default-features = false
[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies]
portable-atomic = "1"
[features]
default = ["rt"]
rt = ["va108xx/rt"]
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03", "vorago-shared-periphs/defmt"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
rustdoc-args = ["--generate-link-to-definition"]

View File

@ -4,7 +4,7 @@
# HAL for the Vorago VA108xx MCU family
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va108xx).
hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/va108xx).
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
@ -25,12 +25,6 @@ rustup target add thumbv6m-none-eabi
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
@ -39,7 +33,7 @@ your custom board.
The hello world of embedded development is usually to blinky a LED. This example
is contained within the
[examples folder](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/blinky.rs).
[examples folder](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/blinky.rs).
1. Set up your Rust cross-compiler if you have not done so yet. See more in the [build chapter](#Building)
2. Create a new binary crate with `cargo init`
@ -65,3 +59,11 @@ is contained within the
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
va108xx-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 --all-features --open

View File

@ -2,26 +2,14 @@
//!
//! This also includes functionality to enable the peripheral clocks
use crate::time::Hertz;
use crate::PeripheralSelect;
use cortex_m::interrupt::{self, Mutex};
use once_cell::unsync::OnceCell;
pub use vorago_shared_periphs::gpio::FilterClkSel;
pub use vorago_shared_periphs::sysconfig::{disable_peripheral_clock, enable_peripheral_clock};
static SYS_CLOCK: Mutex<OnceCell<Hertz>> = Mutex::new(OnceCell::new());
pub type PeripheralClocks = PeripheralSelect;
#[derive(Debug, PartialEq, Eq)]
pub enum FilterClkSel {
SysClk = 0,
Clk1 = 1,
Clk2 = 2,
Clk3 = 3,
Clk4 = 4,
Clk5 = 5,
Clk6 = 6,
Clk7 = 7,
}
/// The Vorago in powered by an external clock which might have different frequencies.
/// The clock can be set here so it can be used by other software components as well.
/// The clock can be set exactly once
@ -39,26 +27,26 @@ pub fn get_sys_clock() -> Option<Hertz> {
pub fn set_clk_div_register(syscfg: &mut va108xx::Sysconfig, clk_sel: FilterClkSel, div: u32) {
match clk_sel {
FilterClkSel::SysClk => (),
FilterClkSel::Clk1 => syscfg.ioconfig_clkdiv1().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk2 => syscfg.ioconfig_clkdiv2().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk3 => syscfg.ioconfig_clkdiv3().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk4 => syscfg.ioconfig_clkdiv4().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk5 => syscfg.ioconfig_clkdiv5().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk6 => syscfg.ioconfig_clkdiv6().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk7 => syscfg.ioconfig_clkdiv7().write(|w| unsafe { w.bits(div) }),
FilterClkSel::Clk1 => {
syscfg.ioconfig_clkdiv1().write(|w| unsafe { w.bits(div) });
}
FilterClkSel::Clk2 => {
syscfg.ioconfig_clkdiv2().write(|w| unsafe { w.bits(div) });
}
FilterClkSel::Clk3 => {
syscfg.ioconfig_clkdiv3().write(|w| unsafe { w.bits(div) });
}
FilterClkSel::Clk4 => {
syscfg.ioconfig_clkdiv4().write(|w| unsafe { w.bits(div) });
}
FilterClkSel::Clk5 => {
syscfg.ioconfig_clkdiv5().write(|w| unsafe { w.bits(div) });
}
FilterClkSel::Clk6 => {
syscfg.ioconfig_clkdiv6().write(|w| unsafe { w.bits(div) });
}
FilterClkSel::Clk7 => {
syscfg.ioconfig_clkdiv7().write(|w| unsafe { w.bits(div) });
}
}
}
#[inline]
pub fn enable_peripheral_clock(syscfg: &mut va108xx::Sysconfig, clock: PeripheralClocks) {
syscfg
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
}
#[inline]
pub fn disable_peripheral_clock(syscfg: &mut va108xx::Sysconfig, clock: PeripheralClocks) {
syscfg
.peripheral_clk_enable()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
}

View File

@ -1,517 +0,0 @@
//! # Type-erased, value-level module for GPIO pins
//!
//! Although the type-level API is generally preferred, it is not suitable in
//! all cases. Because each pin is represented by a distinct type, it is not
//! possible to store multiple pins in a homogeneous data structure. The
//! value-level API solves this problem by erasing the type information and
//! tracking the pin at run-time.
//!
//! Value-level pins are represented by the [`DynPin`] type. [`DynPin`] has two
//! fields, `id` and `mode` with types [`DynPinId`] and [`DynPinMode`]
//! respectively. The implementation of these types closely mirrors the
//! type-level API.
//!
//! Instances of [`DynPin`] cannot be created directly. Rather, they must be
//! created from their type-level equivalents using [`From`]/[`Into`].
//!
//! ```
//! // Move a pin out of the Pins struct and convert to a DynPin
//! let pa0: DynPin = pins.pa0.into();
//! ```
//!
//! Conversions between pin modes use a value-level version of the type-level
//! API.
//!
//! ```
//! // Use one of the literal function names
//! pa0.into_floating_input();
//! // Use a method and a DynPinMode variant
//! pa0.into_mode(DYN_FLOATING_INPUT);
//! ```
//!
//! Because the pin state cannot be tracked at compile-time, many [`DynPin`]
//! operations become fallible. Run-time checks are inserted to ensure that
//! users don't try to, for example, set the output level of an input pin.
//!
//! Users may try to convert value-level pins back to their type-level
//! equivalents. However, this option is fallible, because the compiler cannot
//! guarantee the pin has the correct ID or is in the correct mode at
//! compile-time. Use [`TryFrom`](core::convert::TryFrom)/
//! [`TryInto`](core::convert::TryInto) for this conversion.
//!
//! ```
//! // Convert to a `DynPin`
//! let pa0: DynPin = pins.pa0.into();
//! // Change pin mode
//! pa0.into_floating_input();
//! // Convert back to a `Pin`
//! let pa0: Pin<PA0, FloatingInput> = pa0.try_into().unwrap();
//! ```
//!
//! # Embedded HAL traits
//!
//! This module implements all of the embedded HAL GPIO traits for [`DynPin`].
//! However, whereas the type-level API uses
//! `Error = core::convert::Infallible`, the value-level API can return a real
//! error. If the [`DynPin`] is not in the correct [`DynPinMode`] for the
//! operation, the trait functions will return
//! [`InvalidPinType`](PinError::InvalidPinType).
use super::{
pins::{FilterType, InterruptEdge, InterruptLevel, Pin, PinId, PinMode, PinState},
reg::RegisterInterface,
};
use crate::{clock::FilterClkSel, pac, FunSel, IrqCfg};
//==================================================================================================
// 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 = FunSel;
//==============================================================================
// 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, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidPinTypeError;
impl embedded_hal::digital::Error for InvalidPinTypeError {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}
//==================================================================================================
// 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,
}
/// 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 }
}
}
//==================================================================================================
// 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,
irq_cfg: IrqCfg,
syscfg: Option<&mut pac::Sysconfig>,
irqsel: Option<&mut pac::Irqsel>,
) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.regs.interrupt_edge(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
Ok(self)
}
_ => Err(InvalidPinTypeError),
}
}
pub fn interrupt_level(
mut self,
level_type: InterruptLevel,
irq_cfg: IrqCfg,
syscfg: Option<&mut pac::Sysconfig>,
irqsel: Option<&mut pac::Irqsel>,
) -> Result<Self, InvalidPinTypeError> {
match self.mode {
DynPinMode::Input(_) | DynPinMode::Output(_) => {
self.regs.interrupt_level(level_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
Ok(self)
}
_ => Err(InvalidPinTypeError),
}
}
#[inline]
pub fn toggle_with_toggle_reg(&mut self) -> Result<(), InvalidPinTypeError> {
match self.mode {
DynPinMode::Output(_) => {
self.regs.toggle();
Ok(())
}
_ => 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: PinId, M: PinMode> From<Pin<I, M>> for DynPin {
/// 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: PinId, M: PinMode> TryFrom<DynPin> for Pin<I, M> {
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 traits
//==================================================================================================
impl embedded_hal::digital::ErrorType for DynPin {
type Error = InvalidPinTypeError;
}
impl embedded_hal::digital::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 embedded_hal::digital::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 embedded_hal::digital::StatefulOutputPin for DynPin {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
self._is_high()
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
self._is_low()
}
}

View File

@ -1,111 +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, [`mod@pins`] and [`dynpins`],
//! representing two different ways to handle GPIO pins. The default, [`mod@pins`],
//! is a type-level API that tracks the state of each pin at compile-time. The
//! alternative, [`dynpins`] 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, [`dynpins`] 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
//!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/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,
irq_cfg: crate::IrqCfg,
syscfg: Option<&mut va108xx::Sysconfig>,
irqsel: Option<&mut va108xx::Irqsel>,
) {
if syscfg.is_some() {
crate::clock::enable_peripheral_clock(
syscfg.unwrap(),
crate::clock::PeripheralClocks::Irqsel,
);
}
self.regs.enable_irq();
if let Some(irqsel) = irqsel {
if irq_cfg.route {
match self.regs.id().group {
// Set the correct interrupt number in the IRQSEL register
DynGroup::A => {
irqsel
.porta0(self.regs.id().num as usize)
.write(|w| unsafe { w.bits(irq_cfg.irq as u32) });
}
DynGroup::B => {
irqsel
.portb0(self.regs.id().num as usize)
.write(|w| unsafe { w.bits(irq_cfg.irq as u32) });
}
}
}
}
}
);
};
}
pub mod dynpins;
pub use dynpins::*;
pub mod pins;
pub use pins::*;
mod reg;
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
pub use vorago_shared_periphs::gpio::*;

View File

@ -1,890 +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, B, C or D) and pin number. Each
//! `PinId` instance is named according to its datasheet identifier, e.g.
//! [`PA02`].
//!
//! A `PinMode` represents the various pin modes. The available `PinMode`
//! variants are [`Disabled`], [`Input`], [`Interrupt`], [`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 [`Pins`]
//! struct.
//!
//! To create the [`Pins`] struct, users must supply the PAC
//! [`PORT`](crate::pac::PORT) peripheral. The [`Pins`] struct takes
//! ownership of the [`PORT`] and provides the corresponding pins. Each [`Pin`]
//! within the [`Pins`] struct can be moved out and used individually.
//!
//!
//! ```
//! let mut peripherals = Peripherals::take().unwrap();
//! let pins = Pins::new(peripherals.PORT);
//! ```
//!
//! Pins can be converted between modes using several different methods.
//!
//! ```
//! // Use one of the literal function names
//! let pa27 = pins.pa27.into_floating_input();
//! // Use a generic method and one of the `PinMode` variant types
//! let pa27 = pins.pa27.into_mode::<FloatingInput>();
//! // Specify the target type and use `From`/`Into`
//! let pa27: Pin<PA27, FloatingInput> = pins.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`],
//! [`ToggleableOutputPin`] and [`StatefulOutputPin`].
//!
//! For example, you can control the logic level of an `OutputPin` like so
//!
//! ```
//! use atsamd_hal::pac::Peripherals;
//! use atsamd_hal::gpio::Pins;
//! use crate::ehal_02::digital::v2::OutputPin;
//!
//! let mut peripherals = Peripherals::take().unwrap();
//! let mut pins = Pins::new(peripherals.PORT);
//! pins.pa27.set_high();
//! ```
//!
//! # Type-level features
//!
//! This module also provides additional, type-level tools to work with GPIO
//! pins.
//!
//! The [`OptionalPinId`] and [`OptionalPin`] traits use the [`OptionalKind`]
//! pattern to act as type-level versions of [`Option`] for `PinId` and `Pin`
//! respectively. And the [`AnyPin`] trait defines an [`AnyKind`] type class
//! for all `Pin` types.
//!
//! [type classes]: crate::typelevel#type-classes
//! [type-level enum]: crate::typelevel#type-level-enum
//! [`OptionalKind`]: crate::typelevel#optionalkind-trait-pattern
//! [`AnyKind`]: crate::typelevel#anykind-trait-pattern
use super::dynpins::{DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode};
use super::reg::RegisterInterface;
use crate::{
pac::{Irqsel, Porta, Portb, Sysconfig},
typelevel::Sealed,
IrqCfg,
};
use core::convert::Infallible;
use core::marker::PhantomData;
use core::mem::transmute;
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
use paste::paste;
//==================================================================================================
// 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`](super::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,
}
pub use crate::clock::FilterClkSel;
//==================================================================================================
// 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`](super::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`](super::DynPinId)
const DYN: DynPinId;
}
macro_rules! pin_id {
($Group:ident, $Id:ident, $NUM:literal) => {
// Need paste macro to use ident in doc attribute
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 _toggle_with_toggle_reg(&mut self) {
self.regs.toggle();
}
#[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,
irq_cfg: IrqCfg,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) -> Self {
self.regs.interrupt_edge(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
self
}
pub fn interrupt_level(
mut self,
level_type: InterruptLevel,
irq_cfg: IrqCfg,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) -> Self {
self.regs.interrupt_level(level_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
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
}
#[inline]
pub fn toggle_with_toggle_reg(&mut self) {
self._toggle_with_toggle_reg()
}
/// 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,
irq_cfg: IrqCfg,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) -> Self {
self.regs.interrupt_edge(edge_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
self
}
pub fn interrupt_level(
mut self,
level_type: InterruptLevel,
irq_cfg: IrqCfg,
syscfg: Option<&mut Sysconfig>,
irqsel: Option<&mut Irqsel>,
) -> Self {
self.regs.interrupt_level(level_type);
self.irq_enb(irq_cfg, syscfg, irqsel);
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> embedded_hal::digital::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!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName {
iocfg: Option<va108xx::Ioconfig>,
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 va108xx::Sysconfig,
iocfg: Option<va108xx::Ioconfig>,
port: $Port
) -> $PinsName {
syscfg.peripheral_clk_enable().modify(|_, w| {
w.[<$Port:lower>]().set_bit();
w.gpio().set_bit();
w.ioconfig().set_bit()
});
$PinsName {
iocfg,
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) -> (Option<va108xx::Ioconfig>, $Port) {
(self.iocfg, 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),
(PA16, 16),
(PA17, 17),
(PA18, 18),
(PA19, 19),
(PA20, 20),
(PA21, 21),
(PA22, 22),
(PA23, 23),
(PA24, 24),
(PA25, 25),
(PA26, 26),
(PA27, 27),
(PA28, 28),
(PA29, 29),
(PA30, 30),
(PA31, 31),
]
);
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),
(PB16, 16),
(PB17, 17),
(PB18, 18),
(PB19, 19),
(PB20, 20),
(PB21, 21),
(PB22, 22),
(PB23, 23),
]
);

View File

@ -1,382 +0,0 @@
use super::dynpins::{self, DynGroup, DynPinId, DynPinMode};
use super::pins::{FilterType, InterruptEdge, InterruptLevel, PinState};
use super::IsMaskedError;
use crate::clock::FilterClkSel;
use va108xx::{ioconfig, porta};
/// 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 dynpins::DynInput::*;
fields.dir = false;
match config {
Floating => (),
PullUp => {
fields.pull_en = true;
fields.pull_dir = true;
}
PullDown => {
fields.pull_en = true;
}
}
}
Output(config) => {
use dynpins::DynOutput::*;
fields.dir = true;
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
}
}
//==================================================================================================
// Register Interface
//==================================================================================================
pub type PortReg = ioconfig::Porta;
/*
pub type IocfgPort = ioconfig::Porta;
#[repr(C)]
pub(super) struct IocfgPortGroup {
port: [IocfgPort; 32],
}
*/
/// Provide a safe register interface for pin objects
///
/// [`PORTA`] and [`PORTB`], 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 = va108xx::Porta::ptr();
const PORTB: *const PortRegisterBlock = va108xx::Portb::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) },
}
}
fn iocfg_port(&self) -> &PortReg {
let ioconfig = unsafe { va108xx::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),
}
}
#[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(())
}
}
}
/// Toggle the logic level of an output pin
#[inline(always)]
fn toggle(&mut self) {
// Safety: TOGOUT is a "mask" register, and we only write the bit for
// this pin ID
unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
}
/// 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

@ -2,877 +2,5 @@
//!
//! ## Examples
//!
//! - [REB1 I2C temperature sensor example](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/adt75-temp-sensor.rs)
use crate::{
clock::{enable_peripheral_clock, PeripheralClocks},
pac,
time::Hertz,
typelevel::Sealed,
};
use core::marker::PhantomData;
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
//==================================================================================================
// Defintions
//==================================================================================================
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum FifoEmptyMode {
Stall = 0,
EndTransaction = 1,
}
#[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,
WrongAddrMode,
}
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::WrongAddrMode
| Error::InsufficientDataReceived
| Error::InvalidTimingParams => embedded_hal::i2c::ErrorKind::Other,
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
enum I2cCmd {
Start = 0b00,
Stop = 0b10,
StartWithStop = 0b11,
Cancel = 0b100,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum I2cSpeed {
Regular100khz = 0,
Fast400khz = 1,
}
#[derive(Debug, PartialEq, Eq)]
pub enum I2cDirection {
Send = 0,
Read = 1,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum I2cAddress {
Regular(u8),
TenBit(u16),
}
//==================================================================================================
// 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,
sys_clk: 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),
}
}
}
macro_rules! i2c_base {
($($I2CX:path: ($i2cx:ident, $clk_enb:path),)+) => {
$(
impl I2cBase<$I2CX> {
pub fn $i2cx(
i2c: $I2CX,
sys_clk: impl Into<Hertz>,
speed_mode: I2cSpeed,
ms_cfg: Option<&MasterConfig>,
sl_cfg: Option<&SlaveConfig>,
sys_cfg: Option<&mut va108xx::Sysconfig>,
) -> Self {
if let Some(sys_cfg) = sys_cfg {
enable_peripheral_clock(sys_cfg, $clk_enb);
}
let mut i2c_base = I2cBase {
i2c,
sys_clk: sys_clk.into(),
};
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);
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) -> u8 {
if speed_mode == I2cSpeed::Regular100khz {
((self.sys_clk.raw() / (u32::pow(10, 5) * 20)) - 1) as u8
} else {
(((10 * self.sys_clk.raw()) / u32::pow(10, 8)) - 1) as u8
}
}
/// Configures the clock scale for a given speed mode setting
pub fn cfg_clk_scale(&mut self, speed_mode: I2cSpeed) {
self.i2c.clkscale().write(|w| unsafe {
w.bits((speed_mode as u32) << 31 | self.calc_clk_div(speed_mode) as u32)
});
}
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) });
}
}
)+
}
}
// Unique mode to use the loopback functionality
// pub struct I2cLoopback<I2C> {
// i2c_base: I2cBase<I2C>,
// master_cfg: MasterConfig,
// slave_cfg: SlaveConfig,
// }
i2c_base!(
pac::I2ca: (i2ca, PeripheralClocks::I2c0),
pac::I2cb: (i2cb, PeripheralClocks::I2c1),
);
//==================================================================================================
// I2C Master
//==================================================================================================
pub struct I2cMaster<I2C, ADDR = SevenBitAddress> {
i2c_base: I2cBase<I2C>,
_addr: PhantomData<ADDR>,
}
macro_rules! i2c_master {
($($I2CX:path: ($i2cx:ident, $clk_enb:path),)+) => {
$(
impl<ADDR> I2cMaster<$I2CX, ADDR> {
pub fn $i2cx(
i2c: $I2CX,
cfg: MasterConfig,
sys_clk: impl Into<Hertz> + Copy,
speed_mode: I2cSpeed,
sys_cfg: Option<&mut pac::Sysconfig>,
) -> Self {
I2cMaster {
i2c_base: I2cBase::$i2cx(
i2c,
sys_clk,
speed_mode,
Some(&cfg),
None,
sys_cfg
),
_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::<$I2CX>::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 embedded_hal::i2c::ErrorType for I2cMaster<$I2CX, SevenBitAddress> {
type Error = Error;
}
impl embedded_hal::i2c::I2c for I2cMaster<$I2CX, 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 embedded_hal::i2c::ErrorType for I2cMaster<$I2CX, TenBitAddress> {
type Error = Error;
}
impl embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<$I2CX, 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_master!(
pac::I2ca: (i2ca, PeripheralClocks::I2c0),
pac::I2cb: (i2cb, PeripheralClocks::I2c1),
);
//==================================================================================================
// I2C Slave
//==================================================================================================
pub struct I2cSlave<I2C, ADDR = SevenBitAddress> {
i2c_base: I2cBase<I2C>,
_addr: PhantomData<ADDR>,
}
macro_rules! i2c_slave {
($($I2CX:path: ($i2cx:ident, $i2cx_slave:ident),)+) => {
$(
impl<ADDR> I2cSlave<$I2CX, ADDR> {
fn $i2cx_slave(
i2c: $I2CX,
cfg: SlaveConfig,
sys_clk: impl Into<Hertz>,
speed_mode: I2cSpeed,
sys_cfg: Option<&mut pac::Sysconfig>,
) -> Self {
I2cSlave {
i2c_base: I2cBase::$i2cx(
i2c,
sys_clk,
speed_mode,
None,
Some(&cfg),
sys_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 I2cSlave<$I2CX, SevenBitAddress> {
/// Create a new I2C slave for seven bit addresses
///
/// Returns a [`Error::WrongAddrMode`] error if a ten bit address is passed
pub fn i2ca(
i2c: $I2CX,
cfg: SlaveConfig,
sys_clk: impl Into<Hertz>,
speed_mode: I2cSpeed,
sys_cfg: Option<&mut pac::Sysconfig>,
) -> Result<Self, Error> {
if let I2cAddress::TenBit(_) = cfg.addr {
return Err(Error::WrongAddrMode);
}
Ok(Self::$i2cx_slave(i2c, cfg, sys_clk, speed_mode, sys_cfg))
}
}
impl I2cSlave<$I2CX, TenBitAddress> {
pub fn $i2cx(
i2c: $I2CX,
cfg: SlaveConfig,
sys_clk: impl Into<Hertz>,
speed_mode: I2cSpeed,
sys_cfg: Option<&mut pac::Sysconfig>,
) -> Self {
Self::$i2cx_slave(i2c, cfg, sys_clk, speed_mode, sys_cfg)
}
}
)+
}
}
i2c_slave!(pac::I2ca: (i2ca, i2ca_slave), pac::I2cb: (i2cb, i2cb_slave),);
//! - [REB1 I2C temperature sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs)
pub use vorago_shared_periphs::i2c::*;

View File

@ -1,100 +1,59 @@
#![no_std]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use gpio::Port;
pub use va108xx;
pub use va108xx as pac;
pub mod clock;
pub mod gpio;
pub mod i2c;
pub mod pins;
pub mod prelude;
pub mod pwm;
pub mod spi;
pub mod sysconfig;
pub mod time;
pub mod timer;
pub mod typelevel;
pub mod uart;
pub mod utility;
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
pub enum FunSel {
Sel1 = 0b01,
Sel2 = 0b10,
Sel3 = 0b11,
}
pub use vorago_shared_periphs::{
disable_nvic_interrupt, enable_nvic_interrupt, FunSel, InterruptConfig, PeripheralSelect,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PortSel {
PortA,
PortB,
}
/// This is the NONE destination reigster value for the IRQSEL peripheral.
pub const IRQ_DST_NONE: u32 = 0xffffffff;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum PeripheralSelect {
PortA = 0,
PortB = 1,
Spi0 = 4,
Spi1 = 5,
Spi2 = 6,
Uart0 = 8,
Uart1 = 9,
I2c0 = 16,
I2c1 = 17,
Irqsel = 21,
Ioconfig = 22,
Utility = 23,
Gpio = 24,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid pin with number {0}")]
pub struct InvalidPinError(u8);
/// Generic IRQ config which can be used to specify whether the HAL driver will
/// use the IRQSEL register to route an interrupt, and whether the IRQ will be unmasked in the
/// Cortex-M0 NVIC. Both are generally necessary for IRQs to work, but the user might perform
/// this steps themselves
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct IrqCfg {
/// Interrupt target vector. Should always be set, might be required for disabling IRQs
pub irq: pac::Interrupt,
/// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral
pub route: bool,
/// Specify whether the IRQ is unmasked in the Cortex-M NVIC
pub enable: bool,
}
impl IrqCfg {
pub fn new(irq: pac::Interrupt, route: bool, enable: bool) -> Self {
IrqCfg { irq, route, enable }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidPin(pub(crate) ());
/// Can be used to manually manipulate the function select of port pins
pub fn port_mux(
/// Can be used to manually manipulate the function select of port pins.
///
/// The function selection table can be found on p.36 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.
pub fn port_function_select(
ioconfig: &mut pac::Ioconfig,
port: PortSel,
port: Port,
pin: u8,
funsel: FunSel,
) -> Result<(), InvalidPin> {
match port {
PortSel::PortA => {
if pin > 31 {
return Err(InvalidPin(()));
}
ioconfig
.porta(pin as usize)
.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
Ok(())
}
PortSel::PortB => {
if pin > 23 {
return Err(InvalidPin(()));
}
ioconfig
.portb0(pin as usize)
.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
Ok(())
}
) -> Result<(), InvalidPinError> {
if (port == Port::A && pin >= 32) || (port == Port::B && pin >= 24) {
return Err(InvalidPinError(pin));
}
let reg_block = match port {
Port::A => ioconfig.porta(pin as usize),
Port::B => ioconfig.portb0(pin as usize),
};
reg_block.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
Ok(())
}
#[allow(dead_code)]
pub(crate) mod sealed {
pub trait Sealed {}
}

6
va108xx-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,3 +1,5 @@
//! Prelude
pub use fugit::ExtU32 as _;
pub use fugit::RateExtU32 as _;
pub use crate::time::*;

View File

@ -4,384 +4,5 @@
//!
//! ## Examples
//!
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/pwm.rs)
use core::convert::Infallible;
use core::marker::PhantomData;
use crate::pac;
use crate::{clock::enable_peripheral_clock, gpio::DynPinId};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX;
pub struct PwmBase {
sys_clk: 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.sys_clk.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(
vtp: (Pin, Tim),
sys_clk: impl Into<Hertz> + Copy,
sys_cfg: &mut pac::Sysconfig,
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,
sys_clk: sys_clk.into(),
},
reg: unsafe { TimAndPinRegister::new(vtp.0, vtp.1) },
mode: PhantomData,
};
enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Gpio);
enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Ioconfig);
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(
vtp: (Pin, Tim),
sys_clk: impl Into<Hertz> + Copy,
sys_cfg: &mut pac::Sysconfig,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmA> = Self::new(vtp, sys_clk, sys_cfg, 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(
vtp: (Pin, Tim),
sys_clk: impl Into<Hertz> + Copy,
sys_cfg: &mut pac::Sysconfig,
initial_period: impl Into<Hertz> + Copy,
) -> Self {
let mut pin: PwmPin<Pin, Tim, PwmB> = Self::new(vtp, sys_clk, sys_cfg, 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
}
}
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
pub use vorago_shared_periphs::pwm::*;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
//! API for the SPI peripheral.
//!
//! The main abstraction provided by this module is the [Spi] an structure.
//! It provides the [embedded_hal::spi] traits, but also offer a low level interface
//! via the [SpiLowLevel] trait.
//!
//! ## Examples
//!
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/spi.rs)
//! - [REB1 ADC example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/max11519-adc.rs)
//! - [REB1 EEPROM library](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/src/m95m01.rs)
pub use vorago_shared_periphs::spi::*;

View File

@ -1,56 +1,43 @@
use crate::{pac, PeripheralSelect};
#[derive(PartialEq, Eq, Debug)]
pub struct InvalidounterResetVal(pub(crate) ());
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidCounterResetVal(pub(crate) ());
/// Enable scrubbing for the ROM
///
/// Returns [`UtilityError::InvalidCounterResetVal`] if the scrub rate is 0
/// Returns [InvalidCounterResetVal] if the scrub rate is 0
/// (equivalent to disabling) or larger than 24 bits
pub fn enable_rom_scrubbing(
syscfg: &mut pac::Sysconfig,
scrub_rate: u32,
) -> Result<(), InvalidounterResetVal> {
pub fn enable_rom_scrubbing(scrub_rate: u32) -> Result<(), InvalidCounterResetVal> {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
if scrub_rate == 0 || scrub_rate > u32::pow(2, 24) {
return Err(InvalidounterResetVal(()));
return Err(InvalidCounterResetVal(()));
}
syscfg.rom_scrub().write(|w| unsafe { w.bits(scrub_rate) });
Ok(())
}
pub fn disable_rom_scrubbing(syscfg: &mut pac::Sysconfig) {
syscfg.rom_scrub().write(|w| unsafe { w.bits(0) })
pub fn disable_rom_scrubbing() {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
syscfg.rom_scrub().write(|w| unsafe { w.bits(0) });
}
/// Enable scrubbing for the RAM
///
/// Returns [`UtilityError::InvalidCounterResetVal`] if the scrub rate is 0
/// Returns [InvalidCounterResetVal] if the scrub rate is 0
/// (equivalent to disabling) or larger than 24 bits
pub fn enable_ram_scrubbing(
syscfg: &mut pac::Sysconfig,
scrub_rate: u32,
) -> Result<(), InvalidounterResetVal> {
pub fn enable_ram_scrubbing(scrub_rate: u32) -> Result<(), InvalidCounterResetVal> {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
if scrub_rate == 0 || scrub_rate > u32::pow(2, 24) {
return Err(InvalidounterResetVal(()));
return Err(InvalidCounterResetVal(()));
}
syscfg.ram_scrub().write(|w| unsafe { w.bits(scrub_rate) });
Ok(())
}
pub fn disable_ram_scrubbing(syscfg: &mut pac::Sysconfig) {
syscfg.ram_scrub().write(|w| unsafe { w.bits(0) })
pub fn disable_ram_scrubbing() {
let syscfg = unsafe { va108xx::Sysconfig::steal() };
syscfg.ram_scrub().write(|w| unsafe { w.bits(0) });
}
/// Clear the reset bit. This register is active low, so doing this will hold the peripheral
/// in a reset state
pub fn clear_reset_bit(syscfg: &mut pac::Sysconfig, periph_sel: PeripheralSelect) {
syscfg
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph_sel as u8)) });
}
pub fn set_reset_bit(syscfg: &mut pac::Sysconfig, periph_sel: PeripheralSelect) {
syscfg
.peripheral_reset()
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph_sel as u8)) });
}
pub use vorago_shared_periphs::sysconfig::{
assert_peripheral_reset, disable_peripheral_clock, enable_peripheral_clock,
};

View File

@ -1,26 +1,2 @@
//! Time units
// Frequency based
/// Hertz
pub type Hertz = fugit::HertzU32;
/// KiloHertz
pub type KiloHertz = fugit::KilohertzU32;
/// MegaHertz
pub type MegaHertz = fugit::MegahertzU32;
// Period based
/// Seconds
pub type Seconds = fugit::SecsDurationU32;
/// Milliseconds
pub type Milliseconds = fugit::MillisDurationU32;
/// Microseconds
pub type Microseconds = fugit::MicrosDurationU32;
/// Nanoseconds
pub type Nanoseconds = fugit::NanosDurationU32;
pub use vorago_shared_periphs::time::*;

View File

@ -2,789 +2,6 @@
//!
//! ## Examples
//!
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/timer-ticks.rs)
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/cascade.rs)
pub use crate::IrqCfg;
use crate::{
clock::{enable_peripheral_clock, PeripheralClocks},
gpio::{
AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
PA15, PA2, PA24, PA25, PA26, PA27, PA28, PA29, PA3, PA30, PA31, PA4, PA5, PA6, PA7, PA8,
PA9, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB16, PB17, PB18, PB19, PB2, PB20, PB21,
PB22, PB23, PB3, PB4, PB5, PB6,
},
pac::{self, tim0},
time::Hertz,
timer,
typelevel::Sealed,
utility::unmask_irq,
};
use core::cell::Cell;
use cortex_m::interrupt::Mutex;
use fugit::RateExtU32;
const IRQ_DST_NONE: u32 = 0xffffffff;
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)]
pub enum CascadeSel {
Csd0 = 0,
Csd1 = 1,
Csd2 = 2,
}
/// The numbers are the base numbers for bundles like PORTA, PORTB or TIM
#[derive(Debug, PartialEq, Eq)]
pub enum CascadeSource {
PortABase = 0,
PortBBase = 32,
TimBase = 64,
RamSbe = 96,
RamMbe = 97,
RomSbe = 98,
RomMbe = 99,
Txev = 100,
ClockDividerBase = 120,
}
#[derive(Debug, PartialEq, Eq)]
pub enum TimerErrors {
Canceled,
/// Invalid input for Cascade source
InvalidCsdSourceInput,
}
//==================================================================================================
// 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;
}
macro_rules! tim_marker {
($TIMX:path, $ID:expr) => {
impl ValidTim for $TIMX {
const TIM_ID: u8 = $ID;
}
};
}
tim_marker!(pac::Tim0, 0);
tim_marker!(pac::Tim1, 1);
tim_marker!(pac::Tim2, 2);
tim_marker!(pac::Tim3, 3);
tim_marker!(pac::Tim4, 4);
tim_marker!(pac::Tim5, 5);
tim_marker!(pac::Tim6, 6);
tim_marker!(pac::Tim7, 7);
tim_marker!(pac::Tim8, 8);
tim_marker!(pac::Tim9, 9);
tim_marker!(pac::Tim10, 10);
tim_marker!(pac::Tim11, 11);
tim_marker!(pac::Tim12, 12);
tim_marker!(pac::Tim13, 13);
tim_marker!(pac::Tim14, 14);
tim_marker!(pac::Tim15, 15);
tim_marker!(pac::Tim16, 16);
tim_marker!(pac::Tim17, 17);
tim_marker!(pac::Tim18, 18);
tim_marker!(pac::Tim19, 19);
tim_marker!(pac::Tim20, 20);
tim_marker!(pac::Tim21, 21);
tim_marker!(pac::Tim22, 22);
tim_marker!(pac::Tim23, 23);
pub trait ValidTimAndPin<PIN: TimPin, TIM: ValidTim>: Sealed {}
macro_rules! pin_and_tim {
($PAX:ident, $ALTFUNC:ident, $ID:expr, $TIMX:path) => {
impl TimPin for Pin<$PAX, $ALTFUNC>
where
$PAX: PinId,
{
const DYN: DynPinId = $PAX::DYN;
}
impl<PIN: TimPin, TIM: ValidTim> ValidTimAndPin<PIN, TIM> for (Pin<$PAX, $ALTFUNC>, $TIMX)
where
Pin<$PAX, $ALTFUNC>: TimPin,
$PAX: PinId,
{
}
impl Sealed for (Pin<$PAX, $ALTFUNC>, $TIMX) {}
};
}
pin_and_tim!(PA31, AltFunc2, 23, pac::Tim23);
pin_and_tim!(PA30, AltFunc2, 22, pac::Tim22);
pin_and_tim!(PA29, AltFunc2, 21, pac::Tim21);
pin_and_tim!(PA28, AltFunc2, 20, pac::Tim20);
pin_and_tim!(PA27, AltFunc2, 19, pac::Tim19);
pin_and_tim!(PA26, AltFunc2, 18, pac::Tim18);
pin_and_tim!(PA25, AltFunc2, 17, pac::Tim17);
pin_and_tim!(PA24, AltFunc2, 16, pac::Tim16);
pin_and_tim!(PA15, AltFunc1, 15, pac::Tim15);
pin_and_tim!(PA14, AltFunc1, 14, pac::Tim14);
pin_and_tim!(PA13, AltFunc1, 13, pac::Tim13);
pin_and_tim!(PA12, AltFunc1, 12, pac::Tim12);
pin_and_tim!(PA11, AltFunc1, 11, pac::Tim11);
pin_and_tim!(PA10, AltFunc1, 10, pac::Tim10);
pin_and_tim!(PA9, AltFunc1, 9, pac::Tim9);
pin_and_tim!(PA8, AltFunc1, 8, pac::Tim8);
pin_and_tim!(PA7, AltFunc1, 7, pac::Tim7);
pin_and_tim!(PA6, AltFunc1, 6, pac::Tim6);
pin_and_tim!(PA5, AltFunc1, 5, pac::Tim5);
pin_and_tim!(PA4, AltFunc1, 4, pac::Tim4);
pin_and_tim!(PA3, AltFunc1, 3, pac::Tim3);
pin_and_tim!(PA2, AltFunc1, 2, pac::Tim2);
pin_and_tim!(PA1, AltFunc1, 1, pac::Tim1);
pin_and_tim!(PA0, AltFunc1, 0, pac::Tim0);
pin_and_tim!(PB23, AltFunc3, 23, pac::Tim23);
pin_and_tim!(PB22, AltFunc3, 22, pac::Tim22);
pin_and_tim!(PB21, AltFunc3, 21, pac::Tim21);
pin_and_tim!(PB20, AltFunc3, 20, pac::Tim20);
pin_and_tim!(PB19, AltFunc3, 19, pac::Tim19);
pin_and_tim!(PB18, AltFunc3, 18, pac::Tim18);
pin_and_tim!(PB17, AltFunc3, 17, pac::Tim17);
pin_and_tim!(PB16, AltFunc3, 16, pac::Tim16);
pin_and_tim!(PB15, AltFunc3, 15, pac::Tim15);
pin_and_tim!(PB14, AltFunc3, 14, pac::Tim14);
pin_and_tim!(PB13, AltFunc3, 13, pac::Tim13);
pin_and_tim!(PB12, AltFunc3, 12, pac::Tim12);
pin_and_tim!(PB11, AltFunc3, 11, pac::Tim11);
pin_and_tim!(PB10, AltFunc3, 10, pac::Tim10);
pin_and_tim!(PB6, AltFunc3, 6, pac::Tim6);
pin_and_tim!(PB5, AltFunc3, 5, pac::Tim5);
pin_and_tim!(PB4, AltFunc3, 4, pac::Tim4);
pin_and_tim!(PB3, AltFunc3, 3, pac::Tim3);
pin_and_tim!(PB2, AltFunc3, 2, pac::Tim2);
pin_and_tim!(PB1, AltFunc3, 1, pac::Tim1);
pin_and_tim!(PB0, AltFunc3, 0, pac::Tim0);
//==================================================================================================
// Register Interface for TIM registers and TIM pins
//==================================================================================================
pub type TimRegBlock = 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 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 clear_tim_reset_bit(&self) {
unsafe {
va108xx::Peripherals::steal()
.sysconfig
.tim_reset()
.modify(|r, w| w.bits(r.bits() & !self.mask_32()))
}
}
#[inline]
#[allow(dead_code)]
fn set_tim_reset_bit(&self) {
unsafe {
va108xx::Peripherals::steal()
.sysconfig
.tim_reset()
.modify(|r, w| w.bits(r.bits() | self.mask_32()))
}
}
}
/// 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> {
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,
irq_cfg: Option<IrqCfg>,
sys_clk: Hertz,
rst_val: u32,
last_cnt: u32,
listening: bool,
}
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> {
fn tim_id(&self) -> u8 {
TIM::TIM_ID
}
}
macro_rules! csd_sel {
($func_name:ident, $csd_reg:ident) => {
/// Configure the Cascade sources
pub fn $func_name(
&mut self,
src: CascadeSource,
id: Option<u8>,
) -> Result<(), TimerErrors> {
let mut id_num = 0;
if let CascadeSource::PortABase
| CascadeSource::PortBBase
| CascadeSource::ClockDividerBase
| CascadeSource::TimBase = src
{
if id.is_none() {
return Err(TimerErrors::InvalidCsdSourceInput);
}
}
if id.is_some() {
id_num = id.unwrap();
}
match src {
CascadeSource::PortABase => {
if id_num > 55 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().$csd_reg().write(|w| unsafe {
w.cassel().bits(CascadeSource::PortABase as u8 + id_num)
});
Ok(())
}
CascadeSource::PortBBase => {
if id_num > 23 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().$csd_reg().write(|w| unsafe {
w.cassel().bits(CascadeSource::PortBBase as u8 + id_num)
});
Ok(())
}
CascadeSource::TimBase => {
if id_num > 23 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().$csd_reg().write(|w| unsafe {
w.cassel().bits(CascadeSource::TimBase as u8 + id_num)
});
Ok(())
}
CascadeSource::ClockDividerBase => {
if id_num > 7 {
return Err(TimerErrors::InvalidCsdSourceInput);
}
self.tim.reg().cascade0().write(|w| unsafe {
w.cassel()
.bits(CascadeSource::ClockDividerBase as u8 + id_num)
});
Ok(())
}
_ => {
self.tim
.reg()
.$csd_reg()
.write(|w| unsafe { w.cassel().bits(src as u8) });
Ok(())
}
}
}
};
}
impl<TIM: ValidTim> CountDownTimer<TIM> {
/// Configures a TIM peripheral as a periodic count down timer
pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into<Hertz>, tim: TIM) -> Self {
enable_tim_clk(syscfg, TIM::TIM_ID);
let cd_timer = CountDownTimer {
tim: unsafe { TimRegister::new(tim) },
sys_clk: sys_clk.into(),
irq_cfg: None,
rst_val: 0,
curr_freq: 0.Hz(),
listening: false,
last_cnt: 0,
};
cd_timer
.tim
.reg()
.ctrl()
.modify(|_, w| w.enable().set_bit());
cd_timer
}
/// 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
pub fn listen(
&mut self,
event: Event,
irq_cfg: IrqCfg,
irq_sel: Option<&mut pac::Irqsel>,
sys_cfg: Option<&mut pac::Sysconfig>,
) {
match event {
Event::TimeOut => {
cortex_m::peripheral::NVIC::mask(irq_cfg.irq);
self.irq_cfg = Some(irq_cfg);
if irq_cfg.route {
if let Some(sys_cfg) = sys_cfg {
enable_peripheral_clock(sys_cfg, PeripheralClocks::Irqsel);
}
if let Some(irq_sel) = irq_sel {
irq_sel
.tim0(TIM::TIM_ID as usize)
.write(|w| unsafe { w.bits(irq_cfg.irq as u32) });
}
}
self.listening = true;
}
}
}
pub fn unlisten(
&mut self,
event: Event,
syscfg: &mut pac::Sysconfig,
irqsel: &mut pac::Irqsel,
) {
match event {
Event::TimeOut => {
enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel);
irqsel
.tim0(TIM::TIM_ID as usize)
.write(|w| unsafe { w.bits(IRQ_DST_NONE) });
self.disable_interrupt();
self.listening = false;
}
}
}
#[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());
}
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.sys_clk.raw() / self.curr_freq.raw();
self.set_reload(self.rst_val);
self.set_count(self.rst_val);
}
#[inline(always)]
pub fn set_reload(&mut self, val: u32) {
self.tim.reg().rst_value().write(|w| unsafe { w.bits(val) });
}
#[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());
if let Some(irq_cfg) = self.irq_cfg {
self.enable_interrupt();
if irq_cfg.enable {
unmask_irq(irq_cfg.irq);
}
}
}
#[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
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
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
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)
});
}
csd_sel!(cascade_0_source, cascade0);
csd_sel!(cascade_1_source, cascade1);
csd_sel!(cascade_2_source, cascade2);
pub fn curr_freq(&self) -> Hertz {
self.curr_freq
}
pub fn listening(&self) -> bool {
self.listening
}
}
/// CountDown implementation for TIMx
impl<TIM: ValidTim> CountDownTimer<TIM> {
#[inline]
pub fn start<T>(&mut self, timeout: T)
where
T: Into<Hertz>,
{
self.load(timeout);
self.enable();
}
/// 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)
}
}
pub fn cancel(&mut self) -> Result<(), TimerErrors> {
if !self.tim.reg().ctrl().read().enable().bit_is_set() {
return Err(TimerErrors::Canceled);
}
self.tim.reg().ctrl().write(|w| w.enable().clear_bit());
Ok(())
}
}
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.sys_clk.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();
}
}
// 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>(
irq_cfg: IrqCfg,
sys_cfg: &mut pac::Sysconfig,
irq_sel: Option<&mut pac::Irqsel>,
sys_clk: impl Into<Hertz>,
tim0: TIM,
) -> CountDownTimer<TIM> {
let mut ms_timer = CountDownTimer::new(sys_cfg, sys_clk, tim0);
ms_timer.listen(timer::Event::TimeOut, irq_cfg, irq_sel, Some(sys_cfg));
ms_timer.start(1000.Hz());
ms_timer
}
pub fn set_up_ms_delay_provider<TIM: ValidTim>(
sys_cfg: &mut pac::Sysconfig,
sys_clk: impl Into<Hertz>,
tim: TIM,
) -> CountDownTimer<TIM> {
let mut provider = CountDownTimer::new(sys_cfg, sys_clk, tim);
provider.start(1000.Hz());
provider
}
/// 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())
}
//==================================================================================================
// Delay implementations
//==================================================================================================
pub struct DelayMs(CountDownTimer<pac::Tim0>);
impl DelayMs {
pub fn new(timer: CountDownTimer<pac::Tim0>) -> 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 in TIM0 as a system tick
/// with [`set_up_ms_delay_provider`]
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();
}
}
}
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
pub use vorago_shared_periphs::timer::*;

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/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs)
//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-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/va108xx-rs/src/branch/main/flashloader)
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
pub use vorago_shared_periphs::uart::*;

View File

@ -1,16 +0,0 @@
//! # API for utility functions like the Error Detection and Correction (EDAC) block
//!
//! Some more information about the recommended scrub rates can be found on the
//! [Vorago White Paper website](https://www.voragotech.com/resources) in the
//! application note AN1212
use crate::pac;
/// Unmask and enable an IRQ with the given interrupt number
///
/// ## Safety
///
/// The unmask function can break mask-based critical sections
#[inline]
pub(crate) fn unmask_irq(irq: pac::Interrupt) {
unsafe { cortex_m::peripheral::NVIC::unmask(irq) };
}

View File

@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.5.0] 2025-02-17
- Re-generated PAC with `svd2rust` v0.35.0 and added optional `defmt` and `Debug` implementations
## [v0.4.0] 2025-02-12
- Re-generated PAC with `svd2rust` v0.35.0
## [v0.3.0] 2024-06-16
- Re-generated PAC with `svd2rust` v0.33.3

View File

@ -1,11 +1,11 @@
[package]
name = "va108xx"
version = "0.3.0"
version = "0.5.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "PAC for the Vorago VA108xx family of microcontrollers"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va108xx"
repository = "https://egit.irs.uni-stuttgart.de/rust/va108xx"
homepage = "https://egit.irs.uni-stuttgart.de/rust/va108xx-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/va108xx-rs"
license = "Apache-2.0"
keywords = ["no-std", "arm", "cortex-m", "vorago", "va108xx"]
categories = ["embedded", "no-std", "hardware-support"]
@ -13,6 +13,7 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-m = "0.7"
vcell = "0.1.3"
defmt = { version = "1", optional = true }
critical-section = { version = "1", optional = true }
[dependencies.cortex-m-rt]
@ -21,7 +22,9 @@ version = ">=0.6.15,<0.8"
[features]
rt = ["cortex-m-rt/device"]
# Adds Debug implementation
debug = []
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
rustdoc-args = ["--generate-link-to-definition"]

View File

@ -24,7 +24,13 @@ features = ["rt"]
The `rt` feature is optional and recommended. It brings in support for `cortex-m-rt`.
For full details on the autgenerated API, please see the
[svd2rust documentation](https://docs.rs/svd2rust/0.19.0/svd2rust/#peripheral-api).
[svd2rust documentation](https://docs.rs/svd2rust/latest/svd2rust/#peripheral-api).
## Optional Features
- [`defmt`](https://defmt.ferrous-systems.com/): Add support for `defmt` by adding the
[`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types.
- `debug`: Add `Debug` derives for various structures
## Regenerating the PAC

3
va108xx/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 --all-features --open

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# Use installed tool by default
svd2rust_bin="svd2rust"
@ -30,7 +30,7 @@ fi
svdtools patch svd/va108xx-patch.yml
# See https://github.com/rust-embedded/svd2rust/issues/830 for required re-export.
${svd2rust_bin} --reexport-interrupt -i svd/va108xx.svd.patched
${svd2rust_bin} --reexport-interrupt --impl-defmt defmt --impl-debug-feature debug -i svd/va108xx.svd.patched
result=$?
if [ $result -ne 0 ]; then

View File

@ -82,169 +82,6 @@ pub trait Resettable: RegisterSpec {
Self::RESET_VALUE
}
}
#[doc = " This structure provides volatile access to registers."]
#[repr(transparent)]
pub struct Reg<REG: RegisterSpec> {
register: vcell::VolatileCell<REG::Ux>,
_marker: marker::PhantomData<REG>,
}
unsafe impl<REG: RegisterSpec> Send for Reg<REG> where REG::Ux: Send {}
impl<REG: RegisterSpec> Reg<REG> {
#[doc = " Returns the underlying memory address of register."]
#[doc = ""]
#[doc = " ```ignore"]
#[doc = " let reg_ptr = periph.reg.as_ptr();"]
#[doc = " ```"]
#[inline(always)]
pub fn as_ptr(&self) -> *mut REG::Ux {
self.register.as_ptr()
}
}
impl<REG: Readable> Reg<REG> {
#[doc = " Reads the contents of a `Readable` register."]
#[doc = ""]
#[doc = " You can read the raw contents of a register by using `bits`:"]
#[doc = " ```ignore"]
#[doc = " let bits = periph.reg.read().bits();"]
#[doc = " ```"]
#[doc = " or get the content of a particular field of a register:"]
#[doc = " ```ignore"]
#[doc = " let reader = periph.reg.read();"]
#[doc = " let bits = reader.field1().bits();"]
#[doc = " let flag = reader.field2().bit_is_set();"]
#[doc = " ```"]
#[inline(always)]
pub fn read(&self) -> R<REG> {
R {
bits: self.register.get(),
_reg: marker::PhantomData,
}
}
}
impl<REG: Resettable + Writable> Reg<REG> {
#[doc = " Writes the reset value to `Writable` register."]
#[doc = ""]
#[doc = " Resets the register to its initial state."]
#[inline(always)]
pub fn reset(&self) {
self.register.set(REG::RESET_VALUE)
}
#[doc = " Writes bits to a `Writable` register."]
#[doc = ""]
#[doc = " You can write raw bits into a register:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write(|w| unsafe { w.bits(rawbits) });"]
#[doc = " ```"]
#[doc = " or write only the fields you need:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write(|w| w"]
#[doc = " .field1().bits(newfield1bits)"]
#[doc = " .field2().set_bit()"]
#[doc = " .field3().variant(VARIANT)"]
#[doc = " );"]
#[doc = " ```"]
#[doc = " or an alternative way of saying the same:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write(|w| {"]
#[doc = " w.field1().bits(newfield1bits);"]
#[doc = " w.field2().set_bit();"]
#[doc = " w.field3().variant(VARIANT)"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " In the latter case, other fields will be set to their reset value."]
#[inline(always)]
pub fn write<F>(&self, f: F)
where
F: FnOnce(&mut W<REG>) -> &mut W<REG>,
{
self.register.set(
f(&mut W {
bits: REG::RESET_VALUE & !REG::ONE_TO_MODIFY_FIELDS_BITMAP
| REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
})
.bits,
);
}
}
impl<REG: Writable> Reg<REG> {
#[doc = " Writes 0 to a `Writable` register."]
#[doc = ""]
#[doc = " Similar to `write`, but unused bits will contain 0."]
#[doc = ""]
#[doc = " # Safety"]
#[doc = ""]
#[doc = " Unsafe to use with registers which don't allow to write 0."]
#[inline(always)]
pub unsafe fn write_with_zero<F>(&self, f: F)
where
F: FnOnce(&mut W<REG>) -> &mut W<REG>,
{
self.register.set(
f(&mut W {
bits: REG::Ux::default(),
_reg: marker::PhantomData,
})
.bits,
);
}
}
impl<REG: Readable + Writable> Reg<REG> {
#[doc = " Modifies the contents of the register by reading and then writing it."]
#[doc = ""]
#[doc = " E.g. to do a read-modify-write sequence to change parts of a register:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|r, w| unsafe { w.bits("]
#[doc = " r.bits() | 3"]
#[doc = " ) });"]
#[doc = " ```"]
#[doc = " or"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|_, w| w"]
#[doc = " .field1().bits(newfield1bits)"]
#[doc = " .field2().set_bit()"]
#[doc = " .field3().variant(VARIANT)"]
#[doc = " );"]
#[doc = " ```"]
#[doc = " or an alternative way of saying the same:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|_, w| {"]
#[doc = " w.field1().bits(newfield1bits);"]
#[doc = " w.field2().set_bit();"]
#[doc = " w.field3().variant(VARIANT)"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " Other fields will have the value they had before the call to `modify`."]
#[inline(always)]
pub fn modify<F>(&self, f: F)
where
for<'w> F: FnOnce(&R<REG>, &'w mut W<REG>) -> &'w mut W<REG>,
{
let bits = self.register.get();
self.register.set(
f(
&R {
bits,
_reg: marker::PhantomData,
},
&mut W {
bits: bits & !REG::ONE_TO_MODIFY_FIELDS_BITMAP
| REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
},
)
.bits,
);
}
}
impl<REG: Readable> core::fmt::Debug for crate::generic::Reg<REG>
where
R<REG>: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.read(), f)
}
}
#[doc(hidden)]
pub mod raw;
#[doc = " Register reader."]
@ -369,7 +206,7 @@ pub struct RangeTo<const MAX: u64>;
#[doc = " Write field Proxy"]
pub type FieldWriter<'a, REG, const WI: u8, FI = u8, Safety = Unsafe> =
raw::FieldWriter<'a, REG, WI, FI, Safety>;
impl<'a, REG, const WI: u8, FI, Safety> FieldWriter<'a, REG, WI, FI, Safety>
impl<REG, const WI: u8, FI, Safety> FieldWriter<'_, REG, WI, FI, Safety>
where
REG: Writable + RegisterSpec,
FI: FieldSpec,
@ -616,3 +453,278 @@ where
self.w
}
}
#[doc = " This structure provides volatile access to registers."]
#[repr(transparent)]
pub struct Reg<REG: RegisterSpec> {
register: vcell::VolatileCell<REG::Ux>,
_marker: marker::PhantomData<REG>,
}
unsafe impl<REG: RegisterSpec> Send for Reg<REG> where REG::Ux: Send {}
impl<REG: RegisterSpec> Reg<REG> {
#[doc = " Returns the underlying memory address of register."]
#[doc = ""]
#[doc = " ```ignore"]
#[doc = " let reg_ptr = periph.reg.as_ptr();"]
#[doc = " ```"]
#[inline(always)]
pub fn as_ptr(&self) -> *mut REG::Ux {
self.register.as_ptr()
}
}
impl<REG: Readable> Reg<REG> {
#[doc = " Reads the contents of a `Readable` register."]
#[doc = ""]
#[doc = " You can read the raw contents of a register by using `bits`:"]
#[doc = " ```ignore"]
#[doc = " let bits = periph.reg.read().bits();"]
#[doc = " ```"]
#[doc = " or get the content of a particular field of a register:"]
#[doc = " ```ignore"]
#[doc = " let reader = periph.reg.read();"]
#[doc = " let bits = reader.field1().bits();"]
#[doc = " let flag = reader.field2().bit_is_set();"]
#[doc = " ```"]
#[inline(always)]
pub fn read(&self) -> R<REG> {
R {
bits: self.register.get(),
_reg: marker::PhantomData,
}
}
}
impl<REG: Resettable + Writable> Reg<REG> {
#[doc = " Writes the reset value to `Writable` register."]
#[doc = ""]
#[doc = " Resets the register to its initial state."]
#[inline(always)]
pub fn reset(&self) {
self.register.set(REG::RESET_VALUE)
}
#[doc = " Writes bits to a `Writable` register."]
#[doc = ""]
#[doc = " You can write raw bits into a register:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write(|w| unsafe { w.bits(rawbits) });"]
#[doc = " ```"]
#[doc = " or write only the fields you need:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write(|w| w"]
#[doc = " .field1().bits(newfield1bits)"]
#[doc = " .field2().set_bit()"]
#[doc = " .field3().variant(VARIANT)"]
#[doc = " );"]
#[doc = " ```"]
#[doc = " or an alternative way of saying the same:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write(|w| {"]
#[doc = " w.field1().bits(newfield1bits);"]
#[doc = " w.field2().set_bit();"]
#[doc = " w.field3().variant(VARIANT)"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " In the latter case, other fields will be set to their reset value."]
#[inline(always)]
pub fn write<F>(&self, f: F) -> REG::Ux
where
F: FnOnce(&mut W<REG>) -> &mut W<REG>,
{
let value = f(&mut W {
bits: REG::RESET_VALUE & !REG::ONE_TO_MODIFY_FIELDS_BITMAP
| REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
})
.bits;
self.register.set(value);
value
}
#[doc = " Writes bits to a `Writable` register and produce a value."]
#[doc = ""]
#[doc = " You can write raw bits into a register:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write_and(|w| unsafe { w.bits(rawbits); });"]
#[doc = " ```"]
#[doc = " or write only the fields you need:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write_and(|w| {"]
#[doc = " w.field1().bits(newfield1bits)"]
#[doc = " .field2().set_bit()"]
#[doc = " .field3().variant(VARIANT);"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " or an alternative way of saying the same:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.write_and(|w| {"]
#[doc = " w.field1().bits(newfield1bits);"]
#[doc = " w.field2().set_bit();"]
#[doc = " w.field3().variant(VARIANT);"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " In the latter case, other fields will be set to their reset value."]
#[doc = ""]
#[doc = " Values can be returned from the closure:"]
#[doc = " ```ignore"]
#[doc = " let state = periph.reg.write_and(|w| State::set(w.field1()));"]
#[doc = " ```"]
#[inline(always)]
pub fn from_write<F, T>(&self, f: F) -> T
where
F: FnOnce(&mut W<REG>) -> T,
{
let mut writer = W {
bits: REG::RESET_VALUE & !REG::ONE_TO_MODIFY_FIELDS_BITMAP
| REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
};
let result = f(&mut writer);
self.register.set(writer.bits);
result
}
}
impl<REG: Writable> Reg<REG> {
#[doc = " Writes 0 to a `Writable` register."]
#[doc = ""]
#[doc = " Similar to `write`, but unused bits will contain 0."]
#[doc = ""]
#[doc = " # Safety"]
#[doc = ""]
#[doc = " Unsafe to use with registers which don't allow to write 0."]
#[inline(always)]
pub unsafe fn write_with_zero<F>(&self, f: F) -> REG::Ux
where
F: FnOnce(&mut W<REG>) -> &mut W<REG>,
{
let value = f(&mut W {
bits: REG::Ux::default(),
_reg: marker::PhantomData,
})
.bits;
self.register.set(value);
value
}
#[doc = " Writes 0 to a `Writable` register and produces a value."]
#[doc = ""]
#[doc = " Similar to `write`, but unused bits will contain 0."]
#[doc = ""]
#[doc = " # Safety"]
#[doc = ""]
#[doc = " Unsafe to use with registers which don't allow to write 0."]
#[inline(always)]
pub unsafe fn from_write_with_zero<F, T>(&self, f: F) -> T
where
F: FnOnce(&mut W<REG>) -> T,
{
let mut writer = W {
bits: REG::Ux::default(),
_reg: marker::PhantomData,
};
let result = f(&mut writer);
self.register.set(writer.bits);
result
}
}
impl<REG: Readable + Writable> Reg<REG> {
#[doc = " Modifies the contents of the register by reading and then writing it."]
#[doc = ""]
#[doc = " E.g. to do a read-modify-write sequence to change parts of a register:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|r, w| unsafe { w.bits("]
#[doc = " r.bits() | 3"]
#[doc = " ) });"]
#[doc = " ```"]
#[doc = " or"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|_, w| w"]
#[doc = " .field1().bits(newfield1bits)"]
#[doc = " .field2().set_bit()"]
#[doc = " .field3().variant(VARIANT)"]
#[doc = " );"]
#[doc = " ```"]
#[doc = " or an alternative way of saying the same:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|_, w| {"]
#[doc = " w.field1().bits(newfield1bits);"]
#[doc = " w.field2().set_bit();"]
#[doc = " w.field3().variant(VARIANT)"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " Other fields will have the value they had before the call to `modify`."]
#[inline(always)]
pub fn modify<F>(&self, f: F) -> REG::Ux
where
for<'w> F: FnOnce(&R<REG>, &'w mut W<REG>) -> &'w mut W<REG>,
{
let bits = self.register.get();
let value = f(
&R {
bits,
_reg: marker::PhantomData,
},
&mut W {
bits: bits & !REG::ONE_TO_MODIFY_FIELDS_BITMAP | REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
},
)
.bits;
self.register.set(value);
value
}
#[doc = " Modifies the contents of the register by reading and then writing it"]
#[doc = " and produces a value."]
#[doc = ""]
#[doc = " E.g. to do a read-modify-write sequence to change parts of a register:"]
#[doc = " ```ignore"]
#[doc = " let bits = periph.reg.modify(|r, w| {"]
#[doc = " let new_bits = r.bits() | 3;"]
#[doc = " unsafe {"]
#[doc = " w.bits(new_bits);"]
#[doc = " }"]
#[doc = ""]
#[doc = " new_bits"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " or"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|_, w| {"]
#[doc = " w.field1().bits(newfield1bits)"]
#[doc = " .field2().set_bit()"]
#[doc = " .field3().variant(VARIANT);"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " or an alternative way of saying the same:"]
#[doc = " ```ignore"]
#[doc = " periph.reg.modify(|_, w| {"]
#[doc = " w.field1().bits(newfield1bits);"]
#[doc = " w.field2().set_bit();"]
#[doc = " w.field3().variant(VARIANT);"]
#[doc = " });"]
#[doc = " ```"]
#[doc = " Other fields will have the value they had before the call to `modify`."]
#[inline(always)]
pub fn from_modify<F, T>(&self, f: F) -> T
where
for<'w> F: FnOnce(&R<REG>, &'w mut W<REG>) -> T,
{
let bits = self.register.get();
let mut writer = W {
bits: bits & !REG::ONE_TO_MODIFY_FIELDS_BITMAP | REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
};
let result = f(
&R {
bits,
_reg: marker::PhantomData,
},
&mut writer,
);
self.register.set(writer.bits);
result
}
}
impl<REG: Readable> core::fmt::Debug for crate::generic::Reg<REG>
where
R<REG>: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.read(), f)
}
}

View File

@ -41,6 +41,7 @@ impl<FI> BitReader<FI> {
}
}
}
#[must_use = "after creating `FieldWriter` you need to call field value setting method"]
pub struct FieldWriter<'a, REG, const WI: u8, FI = u8, Safety = Unsafe>
where
REG: Writable + RegisterSpec,
@ -66,6 +67,7 @@ where
}
}
}
#[must_use = "after creating `BitWriter` you need to call bit setting method"]
pub struct BitWriter<'a, REG, FI = bool, M = BitM>
where
REG: Writable + RegisterSpec,

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