Compare commits
146 Commits
vorago-reb
...
adee40d953
Author | SHA1 | Date | |
---|---|---|---|
adee40d953 | |||
5b2ded51f9
|
|||
bab979ece3
|
|||
0515ca5eaa
|
|||
86ac7428bb | |||
686b689a91 | |||
5daa85269f | |||
3bc2ee4343 | |||
1ec66e826c
|
|||
606d6a43b4
|
|||
a83d9cca16
|
|||
9eeaae76f7
|
|||
39aaea086a | |||
a6ef0954cb | |||
102d11114a
|
|||
8cdee8b733
|
|||
d419325312 | |||
561a8419c5
|
|||
9bd8efcada | |||
b401085f32 | |||
c693530ab7 | |||
45ee5ad726 | |||
16591346e5 | |||
9ccd147ff6 | |||
521c07460a | |||
5bf7793e6b | |||
75e6d98e44 | |||
80eea170ef | |||
f9d1233d3f | |||
41f7f9d25b | |||
47e754433d | |||
e5e010a276 | |||
caf54e5a70 | |||
31b25b0211 | |||
8b55d0923f | |||
a65f4039ee | |||
97da2a0752
|
|||
c43d1f8861 | |||
181c2bdc7b | |||
2bca96b5db | |||
bf41b59a24 | |||
6cbba8414c | |||
fe04a3e7cd
|
|||
e25fb20b08 | |||
67af1bb9b5 | |||
1a83f932b5 | |||
cdc4807686 | |||
62a4123f82 | |||
17f13fc4dc
|
|||
b4f1512463 | |||
e9ec01fc60 | |||
d57cd383cd | |||
df4e943f48 | |||
93b67a4795
|
|||
a9f2e6dcee
|
|||
271c853df1 | |||
107189b166 | |||
872944bebf | |||
d077bb6210 | |||
bd286bdb2a | |||
d3cc00a4a5
|
|||
1018a65447 | |||
0a31b637e6 | |||
6e1ae70054 | |||
8ae2d6189a
|
|||
549a98dbaf | |||
e24fc608a3
|
|||
7b74312013 | |||
417f5b7f67 | |||
3e796ef22b | |||
b145047b95 | |||
82b4c16f8e | |||
189ac2d256
|
|||
c5543d8606 | |||
691911d087
|
|||
3953897c48 | |||
6e0d417a5c | |||
4edba63b02 | |||
bcd79f0f20 | |||
77608da74e | |||
066d91aee5 | |||
e869355960 | |||
99631dbd03 | |||
698ed3a700 | |||
f3d840ace7
|
|||
f781505ec5 | |||
c6e840a991 | |||
454635a473 | |||
67ddba9c42 | |||
6efc902e02 | |||
b6e9a7f68e
|
|||
6842e06bc6 | |||
5b614e1280 | |||
16e5a5f197 | |||
da1f2902b2 | |||
b2d17e10ed | |||
1412e1b7d1
|
|||
88ee85a4cd | |||
4b318ecc76
|
|||
badeea8071 | |||
c95558ff55 | |||
cd222fd1e1 | |||
f438e7e40f
|
|||
9e547668c2
|
|||
cf55fe1504 | |||
74eebdcc03 | |||
35527f092a | |||
c6314f48d7 | |||
7189cb246b | |||
39b8633065 | |||
df0760da98 | |||
8ed26db6a7
|
|||
307174b938 | |||
46df7f1007 | |||
48dd00661f | |||
e98ef8501e
|
|||
b753a465bf | |||
d6f69d4a54 | |||
e2a55e7309 | |||
e971e8dc0d | |||
501d1c973e | |||
acb8b67ae7 | |||
405cc089c3
|
|||
f48ee8231a
|
|||
4fb19fe234
|
|||
652af5cb3c
|
|||
6e231e2553
|
|||
79b7d7b4c2 | |||
3196d74a34
|
|||
d7c27446e0
|
|||
3d4e8477c1
|
|||
e2e3cc7020 | |||
4f15cd7a31
|
|||
3c8c455c6f | |||
abb78c2682 | |||
51f21fee43
|
|||
6fb3b0544f | |||
e3996d9166 | |||
deee5f6f46 | |||
5d6c7ebf5e | |||
6b3cdf74cc | |||
f4f378ba4f
|
|||
4224b14545 | |||
e3cdb19a35 | |||
95e72cfea6
|
|||
6854703c5d |
@ -4,10 +4,9 @@
|
|||||||
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
||||||
# runner = "gdb-multiarch -q -x openocd.gdb"
|
# runner = "gdb-multiarch -q -x openocd.gdb"
|
||||||
# runner = "gdb -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_RAM --protocol jtag"
|
||||||
# runner = "probe-rs run --chip VA108xx --chip-description-path ./scripts/VA108xx_Series.yaml"
|
|
||||||
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
|
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
|
||||||
|
|
||||||
rustflags = [
|
rustflags = [
|
||||||
@ -19,13 +18,12 @@ rustflags = [
|
|||||||
"-C", "link-arg=-Tlink.x",
|
"-C", "link-arg=-Tlink.x",
|
||||||
|
|
||||||
# knurling-rs tooling. If you want to use flip-link, ensure it is installed first.
|
# 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..
|
# Unfortunately, defmt is clunky to use without probe-rs..
|
||||||
# "-C", "link-arg=-Tdefmt.x",
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
|
||||||
# Can be useful for debugging.
|
# Can be useful for debugging.
|
||||||
"-Clink-args=-Map=app.map"
|
# "-Clink-args=-Map=app.map"
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -10,8 +10,10 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: "thumbv6m-none-eabi"
|
targets: "thumbv6m-none-eabi"
|
||||||
- run: cargo check --target thumbv6m-none-eabi --release
|
- run: cargo check --target thumbv6m-none-eabi
|
||||||
- run: cargo check --target thumbv6m-none-eabi --examples --release
|
- 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:
|
test:
|
||||||
name: Run Tests
|
name: Run Tests
|
||||||
@ -21,7 +23,7 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@nextest
|
uses: taiki-e/install-action@nextest
|
||||||
- run: cargo nextest run --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..
|
# I think we can skip those on an embedded crate..
|
||||||
# - run: cargo test --doc -p va108xx-hal
|
# - run: cargo test --doc -p va108xx-hal
|
||||||
|
|
||||||
@ -39,7 +41,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
- 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:
|
clippy:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,3 +16,5 @@ Cargo.lock
|
|||||||
# JetBrains IDEs
|
# JetBrains IDEs
|
||||||
/.idea
|
/.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
|
/Embed.toml
|
||||||
|
21
Cargo.toml
21
Cargo.toml
@ -4,12 +4,17 @@ members = [
|
|||||||
"vorago-reb1",
|
"vorago-reb1",
|
||||||
"va108xx",
|
"va108xx",
|
||||||
"va108xx-hal",
|
"va108xx-hal",
|
||||||
|
"va108xx-embassy",
|
||||||
"examples/simple",
|
"examples/simple",
|
||||||
|
"examples/rtic",
|
||||||
|
"examples/embassy",
|
||||||
"board-tests",
|
"board-tests",
|
||||||
|
"bootloader",
|
||||||
|
"flashloader",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"defmt-testapp",
|
"flashloader/slot-a-blinky",
|
||||||
|
"flashloader/slot-b-blinky",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
@ -17,7 +22,8 @@ codegen-units = 1
|
|||||||
debug = 2
|
debug = 2
|
||||||
debug-assertions = true # <-
|
debug-assertions = true # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
opt-level = 'z' # <-
|
# 1 instead of 0, the flashloader is too larger otherwise..
|
||||||
|
# opt-level = 1 # <-
|
||||||
overflow-checks = true # <-
|
overflow-checks = true # <-
|
||||||
|
|
||||||
# cargo build/run --release
|
# cargo build/run --release
|
||||||
@ -29,3 +35,12 @@ incremental = false
|
|||||||
lto = 'fat'
|
lto = 'fat'
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = false # <-
|
overflow-checks = false # <-
|
||||||
|
|
||||||
|
[profile.small]
|
||||||
|
inherits = "release"
|
||||||
|
codegen-units = 1
|
||||||
|
debug-assertions = false # <-
|
||||||
|
lto = true
|
||||||
|
opt-level = 'z' # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
strip = true # Automatically strip symbols from the binary.
|
||||||
|
12
Embed.toml.sample
Normal file
12
Embed.toml.sample
Normal 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
101
README.md
@ -14,14 +14,25 @@ This workspace contains the following released crates:
|
|||||||
crate containing basic low-level register definition.
|
crate containing basic low-level register definition.
|
||||||
- The [`va108xx-hal`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/va108xx-hal)
|
- 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.
|
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)
|
- 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.
|
BSP crate containing support for the REB1 development board.
|
||||||
|
|
||||||
It also contains the following helper crates:
|
It also contains the following helper crates:
|
||||||
|
|
||||||
- The `board-tests` contains an application which can be used to test the libraries on the
|
- The [`bootloader`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/bootloader)
|
||||||
board.
|
crate contains a sample bootloader strongly based on the one provided by Vorago.
|
||||||
- The `examples` crates contains various example applications for the HAL and the PAC.
|
- 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
|
## 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
|
You can use CLI or VS Code for flashing, running and debugging. In any case, take
|
||||||
care of installing the pre-requisites first.
|
care of installing the pre-requisites first.
|
||||||
|
|
||||||
### 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
|
1. [SEGGER J-Link tools](https://www.segger.com/downloads/jlink/) installed
|
||||||
2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar
|
2. [gdb-multiarch](https://packages.debian.org/sid/gdb-multiarch) or similar
|
||||||
cross-architecture debugger installed. All commands here assume `gdb-multiarch`.
|
cross-architecture debugger installed. All commands here assume `gdb-multiarch`.
|
||||||
|
|
||||||
### Using CLI
|
|
||||||
|
|
||||||
You can build the blinky example application with the following command
|
You can build the blinky example application with the following command
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@ -70,13 +123,13 @@ is also run when running the `jlink-gdb.sh` script)
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
JLinkGDBServer -select USB -device Cortex-M0 -endian little -if JTAG-speed auto \
|
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
|
After this, you can flash and debug the application with the following command
|
||||||
|
|
||||||
```sh
|
```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
|
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
|
After that, you can simply use `cargo run --example blinky` to flash the blinky
|
||||||
example.
|
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 Segger RTT viewer can be used to display log messages received from the target. The base
|
||||||
the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
|
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
|
The RTT viewer will not be able to process `defmt` printouts. However, you can view the defmt
|
||||||
`cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug`
|
logs by [installing defmt-print](https://crates.io/crates/defmt-print) first and then running
|
||||||
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
|
```sh
|
||||||
configuration variables in your `settings.json`:
|
defmt-print -e <pathToElfFile> tcp
|
||||||
|
```
|
||||||
|
|
||||||
- `"cortex-debug.gdbPath"`
|
The path of the ELF file which is being debugged needs to be specified for this to work.
|
||||||
- `"cortex-debug.gdbPath.linux"`
|
|
||||||
- `"cortex-debug.gdbPath.windows"`
|
## Learning (Embedded) Rust
|
||||||
- `"cortex-debug.gdbPath.osx"`
|
|
||||||
|
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)
|
||||||
|
4
automation/Jenkinsfile
vendored
4
automation/Jenkinsfile
vendored
@ -25,7 +25,9 @@ pipeline {
|
|||||||
stage('Docs') {
|
stage('Docs') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
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
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,16 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m-rtic = "1"
|
|
||||||
panic-halt = "0.2"
|
|
||||||
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
rtt-target = "0.5"
|
panic-halt = "1"
|
||||||
panic-rtt-target = "0.1.3"
|
rtt-target = "0.6"
|
||||||
|
panic-rtt-target = "0.2"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-hal-nb = "1"
|
embedded-hal-nb = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
|
|
||||||
[dependencies.va108xx-hal]
|
[dependencies.va108xx-hal]
|
||||||
version = "0.6"
|
version = "0.11"
|
||||||
path = "../va108xx-hal"
|
|
||||||
features = ["rt"]
|
features = ["rt"]
|
||||||
|
|
||||||
|
@ -6,10 +6,7 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::{
|
use embedded_hal::delay::DelayNs;
|
||||||
delay::DelayNs,
|
|
||||||
digital::{InputPin, OutputPin, StatefulOutputPin},
|
|
||||||
};
|
|
||||||
use panic_rtt_target as _;
|
use panic_rtt_target as _;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
@ -17,7 +14,7 @@ use va108xx_hal::{
|
|||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
time::Hertz,
|
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)]
|
#[allow(dead_code)]
|
||||||
@ -44,8 +41,8 @@ fn main() -> ! {
|
|||||||
rprintln!("-- VA108xx Test Application --");
|
rprintln!("-- VA108xx Test Application --");
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
let cp = cortex_m::Peripherals::take().unwrap();
|
let cp = cortex_m::Peripherals::take().unwrap();
|
||||||
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
|
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
|
||||||
let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
|
let pinsb = PinsB::new(&mut dp.sysconfig, dp.portb);
|
||||||
let mut led1 = pinsa.pa10.into_readable_push_pull_output();
|
let mut led1 = pinsa.pa10.into_readable_push_pull_output();
|
||||||
let test_case = TestCase::DelayMs;
|
let test_case = TestCase::DelayMs;
|
||||||
|
|
||||||
@ -67,113 +64,110 @@ fn main() -> ! {
|
|||||||
TestCase::TestBasic => {
|
TestCase::TestBasic => {
|
||||||
// Tie PORTA[0] to PORTA[1] for these tests!
|
// Tie PORTA[0] to PORTA[1] for these tests!
|
||||||
let mut out = pinsa.pa0.into_readable_push_pull_output();
|
let mut out = pinsa.pa0.into_readable_push_pull_output();
|
||||||
let mut input = pinsa.pa1.into_floating_input();
|
let input = pinsa.pa1.into_floating_input();
|
||||||
out.set_high().unwrap();
|
out.set_high();
|
||||||
assert!(input.is_high().unwrap());
|
assert!(input.is_high());
|
||||||
out.set_low().unwrap();
|
out.set_low();
|
||||||
assert!(input.is_low().unwrap());
|
assert!(input.is_low());
|
||||||
}
|
}
|
||||||
TestCase::TestPullup => {
|
TestCase::TestPullup => {
|
||||||
// Tie PORTA[0] to PORTA[1] for these tests!
|
// Tie PORTA[0] to PORTA[1] for these tests!
|
||||||
let mut input = pinsa.pa1.into_pull_up_input();
|
let input = pinsa.pa1.into_pull_up_input();
|
||||||
assert!(input.is_high().unwrap());
|
assert!(input.is_high());
|
||||||
let mut out = pinsa.pa0.into_readable_push_pull_output();
|
let mut out = pinsa.pa0.into_readable_push_pull_output();
|
||||||
out.set_low().unwrap();
|
out.set_low();
|
||||||
assert!(input.is_low().unwrap());
|
assert!(input.is_low());
|
||||||
out.set_high().unwrap();
|
out.set_high();
|
||||||
assert!(input.is_high().unwrap());
|
assert!(input.is_high());
|
||||||
out.into_floating_input();
|
out.into_floating_input();
|
||||||
assert!(input.is_high().unwrap());
|
assert!(input.is_high());
|
||||||
}
|
}
|
||||||
TestCase::TestPulldown => {
|
TestCase::TestPulldown => {
|
||||||
// Tie PORTA[0] to PORTA[1] for these tests!
|
// Tie PORTA[0] to PORTA[1] for these tests!
|
||||||
let mut input = pinsa.pa1.into_pull_down_input();
|
let input = pinsa.pa1.into_pull_down_input();
|
||||||
assert!(input.is_low().unwrap());
|
assert!(input.is_low());
|
||||||
let mut out = pinsa.pa0.into_push_pull_output();
|
let mut out = pinsa.pa0.into_push_pull_output();
|
||||||
out.set_low().unwrap();
|
out.set_low();
|
||||||
assert!(input.is_low().unwrap());
|
assert!(input.is_low());
|
||||||
out.set_high().unwrap();
|
out.set_high();
|
||||||
assert!(input.is_high().unwrap());
|
assert!(input.is_high());
|
||||||
out.into_floating_input();
|
out.into_floating_input();
|
||||||
assert!(input.is_low().unwrap());
|
assert!(input.is_low());
|
||||||
}
|
}
|
||||||
TestCase::TestMask => {
|
TestCase::TestMask => {
|
||||||
// Tie PORTA[0] to PORTA[1] for these tests!
|
// 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());
|
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!(input.is_low_masked().is_err());
|
||||||
assert!(out.set_high_masked().is_err());
|
assert!(out.set_high_masked().is_err());
|
||||||
}
|
}
|
||||||
TestCase::PortB => {
|
TestCase::PortB => {
|
||||||
// Tie PORTB[22] to PORTB[23] for these tests!
|
// Tie PORTB[22] to PORTB[23] for these tests!
|
||||||
let mut out = pinsb.pb22.into_readable_push_pull_output();
|
let mut out = pinsb.pb22.into_readable_push_pull_output();
|
||||||
let mut input = pinsb.pb23.into_floating_input();
|
let input = pinsb.pb23.into_floating_input();
|
||||||
out.set_high().unwrap();
|
out.set_high();
|
||||||
assert!(input.is_high().unwrap());
|
assert!(input.is_high());
|
||||||
out.set_low().unwrap();
|
out.set_low();
|
||||||
assert!(input.is_low().unwrap());
|
assert!(input.is_low());
|
||||||
}
|
}
|
||||||
TestCase::Perid => {
|
TestCase::Perid => {
|
||||||
assert_eq!(PinsA::get_perid(), 0x004007e1);
|
assert_eq!(PinsA::get_perid(), 0x004007e1);
|
||||||
assert_eq!(PinsB::get_perid(), 0x004007e1);
|
assert_eq!(PinsB::get_perid(), 0x004007e1);
|
||||||
}
|
}
|
||||||
TestCase::Pulse => {
|
TestCase::Pulse => {
|
||||||
let mut output_pulsed = pinsa
|
let mut output_pulsed = pinsa.pa0.into_push_pull_output();
|
||||||
.pa0
|
output_pulsed.configure_pulse_mode(true, PinState::Low);
|
||||||
.into_push_pull_output()
|
|
||||||
.pulse_mode(true, PinState::Low);
|
|
||||||
rprintln!("Pulsing high 10 times..");
|
rprintln!("Pulsing high 10 times..");
|
||||||
output_pulsed.set_low().unwrap();
|
output_pulsed.set_low();
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
output_pulsed.set_high().unwrap();
|
output_pulsed.set_high();
|
||||||
cortex_m::asm::delay(25_000_000);
|
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..");
|
rprintln!("Pulsing low 10 times..");
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
output_pulsed.set_low().unwrap();
|
output_pulsed.set_low();
|
||||||
cortex_m::asm::delay(25_000_000);
|
cortex_m::asm::delay(25_000_000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TestCase::DelayGpio => {
|
TestCase::DelayGpio => {
|
||||||
let mut out_0 = pinsa
|
let mut out_0 = pinsa.pa0.into_readable_push_pull_output();
|
||||||
.pa0
|
out_0.configure_delay(true, false);
|
||||||
.into_readable_push_pull_output()
|
let mut out_1 = pinsa.pa1.into_readable_push_pull_output();
|
||||||
.delay(true, false);
|
out_1.configure_delay(false, true);
|
||||||
let mut out_1 = pinsa
|
let mut out_2 = pinsa.pa3.into_readable_push_pull_output();
|
||||||
.pa1
|
out_2.configure_delay(true, true);
|
||||||
.into_readable_push_pull_output()
|
|
||||||
.delay(false, true);
|
|
||||||
let mut out_2 = pinsa.pa3.into_readable_push_pull_output().delay(true, true);
|
|
||||||
for _ in 0..20 {
|
for _ in 0..20 {
|
||||||
out_0.toggle().unwrap();
|
out_0.toggle();
|
||||||
out_1.toggle().unwrap();
|
out_1.toggle();
|
||||||
out_2.toggle().unwrap();
|
out_2.toggle();
|
||||||
cortex_m::asm::delay(25_000_000);
|
cortex_m::asm::delay(25_000_000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TestCase::DelayMs => {
|
TestCase::DelayMs => {
|
||||||
let mut ms_timer = set_up_ms_tick(
|
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,
|
&mut dp.sysconfig,
|
||||||
Some(&mut dp.irqsel),
|
Some(&mut dp.irqsel),
|
||||||
50.MHz(),
|
50.MHz(),
|
||||||
dp.tim0,
|
dp.tim0,
|
||||||
);
|
);
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
led1.toggle().ok();
|
led1.toggle();
|
||||||
ms_timer.delay_ms(500);
|
ms_timer.delay_ms(500);
|
||||||
led1.toggle().ok();
|
led1.toggle();
|
||||||
ms_timer.delay_ms(500);
|
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();
|
let mut pa0 = pinsa.pa0.into_readable_push_pull_output();
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
led1.toggle().ok();
|
led1.toggle();
|
||||||
delay_timer.delay_ms(500);
|
delay_timer.delay_ms(500);
|
||||||
led1.toggle().ok();
|
led1.toggle();
|
||||||
delay_timer.delay_ms(500);
|
delay_timer.delay_ms(500);
|
||||||
}
|
}
|
||||||
let ahb_freq: Hertz = 50.MHz();
|
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
|
// Test usecond delay using both TIM peripheral and SYST. Use the release image if you
|
||||||
// want to verify the timings!
|
// want to verify the timings!
|
||||||
loop {
|
loop {
|
||||||
pa0.toggle().ok();
|
pa0.toggle();
|
||||||
delay_timer.delay_us(50);
|
delay_timer.delay_us(50);
|
||||||
pa0.toggle().ok();
|
pa0.toggle();
|
||||||
delay_timer.delay_us(50);
|
delay_timer.delay_us(50);
|
||||||
pa0.toggle_with_toggle_reg();
|
pa0.toggle();
|
||||||
syst_delay.delay_us(50);
|
syst_delay.delay_us(50);
|
||||||
pa0.toggle_with_toggle_reg();
|
pa0.toggle();
|
||||||
syst_delay.delay_us(50);
|
syst_delay.delay_us(50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +189,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
rprintln!("Test success");
|
rprintln!("Test success");
|
||||||
loop {
|
loop {
|
||||||
led1.toggle().ok();
|
led1.toggle();
|
||||||
cortex_m::asm::delay(25_000_000);
|
cortex_m::asm::delay(25_000_000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
bootloader/Cargo.toml
Normal file
28
bootloader/Cargo.toml
Normal 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
51
bootloader/README.md
Normal 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
10
bootloader/src/lib.rs
Normal 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
339
bootloader/src/main.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
|
1
defmt-testapp/.gitignore
vendored
1
defmt-testapp/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/target
|
|
@ -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"
|
|
@ -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`).
|
|
@ -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
33
examples/README.md
Normal 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
|
||||||
|
```
|
39
examples/embassy/Cargo.toml
Normal file
39
examples/embassy/Cargo.toml
Normal 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"]
|
253
examples/embassy/src/bin/async-gpio.rs
Normal file
253
examples/embassy/src/bin/async-gpio.rs
Normal 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);
|
||||||
|
}
|
169
examples/embassy/src/bin/async-uart-rx.rs
Normal file
169
examples/embassy/src/bin/async-uart-rx.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
96
examples/embassy/src/bin/async-uart-tx.rs
Normal file
96
examples/embassy/src/bin/async-uart-tx.rs
Normal 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);
|
||||||
|
}
|
3
examples/embassy/src/lib.rs
Normal file
3
examples/embassy/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#![no_std]
|
||||||
|
use defmt_rtt as _;
|
||||||
|
use panic_probe as _;
|
64
examples/embassy/src/main.rs
Normal file
64
examples/embassy/src/main.rs
Normal 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
27
examples/rtic/Cargo.toml
Normal 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" }
|
98
examples/rtic/src/bin/blinky-button-rtic.rs
Normal file
98
examples/rtic/src/bin/blinky-button-rtic.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,12 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use defmt_testapp as _;
|
|
||||||
|
|
||||||
#[rtic::app(device = pac)]
|
#[rtic::app(device = pac)]
|
||||||
mod app {
|
mod app {
|
||||||
|
// Import panic provider.
|
||||||
|
use panic_probe as _;
|
||||||
|
// Import global logger.
|
||||||
|
use defmt_rtt as _;
|
||||||
use va108xx_hal::pac;
|
use va108xx_hal::pac;
|
||||||
|
|
||||||
#[local]
|
#[local]
|
133
examples/rtic/src/bin/uart-echo-rtic.rs
Normal file
133
examples/rtic/src/bin/uart-echo-rtic.rs
Normal 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
4
examples/rtic/src/lib.rs
Normal 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
68
examples/rtic/src/main.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,30 +4,24 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
panic-halt = "0.2"
|
|
||||||
cortex-m = {version = "0.7", features = ["critical-section-single-core"]}
|
cortex-m = {version = "0.7", features = ["critical-section-single-core"]}
|
||||||
panic-rtt-target = "0.1"
|
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
rtt-target = "0.5"
|
panic-halt = "1"
|
||||||
rtic-sync = { version = "1.3", features = ["defmt-03"] }
|
critical-section = "1"
|
||||||
|
defmt-rtt = "0.4"
|
||||||
|
defmt = "1"
|
||||||
|
panic-probe = { version = "1", features = ["defmt"] }
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-hal-nb = "1"
|
embedded-hal-nb = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
cortex-m-semihosting = "0.5.0"
|
cortex-m-semihosting = "0.5.0"
|
||||||
# I'd really like to use those, but it is tricky without probe-rs..
|
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
|
||||||
# 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]
|
[dependencies.va108xx-hal]
|
||||||
version = "0.6"
|
version = "0.11"
|
||||||
path = "../../va108xx-hal"
|
path = "../../va108xx-hal"
|
||||||
features = ["rt", "defmt"]
|
features = ["defmt"]
|
||||||
|
|
||||||
|
[dependencies.vorago-reb1]
|
||||||
|
path = "../../vorago-reb1"
|
||||||
|
version = "0.8"
|
||||||
|
@ -7,58 +7,40 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::{
|
use embedded_hal::delay::DelayNs;
|
||||||
delay::DelayNs,
|
|
||||||
digital::{OutputPin, StatefulOutputPin},
|
|
||||||
};
|
|
||||||
use panic_halt as _;
|
use panic_halt as _;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
gpio::PinsA,
|
gpio::{Output, PinState},
|
||||||
pac::{self, interrupt},
|
pac::{self},
|
||||||
|
pins::PinsA,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer},
|
timer::CountdownTimer,
|
||||||
timer::DelayMs,
|
|
||||||
IrqCfg,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
let mut delay_ms = DelayMs::new(set_up_ms_tick(
|
let mut delay = CountdownTimer::new(dp.tim1, 50.MHz());
|
||||||
IrqCfg::new(interrupt::OC0, true, true),
|
let porta = PinsA::new(dp.porta);
|
||||||
&mut dp.sysconfig,
|
let mut led1 = Output::new(porta.pa10, PinState::Low);
|
||||||
Some(&mut dp.irqsel),
|
let mut led2 = Output::new(porta.pa7, PinState::Low);
|
||||||
50.MHz(),
|
let mut led3 = Output::new(porta.pa6, PinState::Low);
|
||||||
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();
|
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
led1.set_low().ok();
|
led1.set_low();
|
||||||
led2.set_low().ok();
|
led2.set_low();
|
||||||
led3.set_low().ok();
|
led3.set_low();
|
||||||
delay_ms.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
led1.set_high().ok();
|
led1.set_high();
|
||||||
led2.set_high().ok();
|
led2.set_high();
|
||||||
led3.set_high().ok();
|
led3.set_high();
|
||||||
delay_tim1.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
led1.toggle().ok();
|
led1.toggle();
|
||||||
delay_ms.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
led2.toggle().ok();
|
led2.toggle();
|
||||||
delay_tim1.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
led3.toggle().ok();
|
led3.toggle();
|
||||||
delay_ms.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn OC0() {
|
|
||||||
default_ms_irq_handler()
|
|
||||||
}
|
|
||||||
|
@ -6,92 +6,68 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use core::cell::RefCell;
|
|
||||||
use cortex_m::interrupt::Mutex;
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
use panic_rtt_target as _;
|
// Import panic provider.
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use panic_probe as _;
|
||||||
|
// Import logger.
|
||||||
|
use defmt_rtt as _;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
timer::{
|
timer::{CascadeControl, CascadeSelect, CascadeSource, CountdownTimer, InterruptConfig},
|
||||||
default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource,
|
|
||||||
CountDownTimer, Event, IrqCfg,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
defmt::println!("-- VA108xx Cascade example application--");
|
||||||
rprintln!("-- VA108xx Cascade example application--");
|
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
|
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
|
||||||
|
|
||||||
// Will be started periodically to trigger a cascade
|
// Will be started periodically to trigger a cascade
|
||||||
let mut cascade_triggerer =
|
let mut cascade_triggerer = CountdownTimer::new(dp.tim3, 50.MHz());
|
||||||
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim3).auto_disable(true);
|
cascade_triggerer.auto_disable(true);
|
||||||
cascade_triggerer.listen(
|
cascade_triggerer.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC1, true, false));
|
||||||
Event::TimeOut,
|
cascade_triggerer.enable();
|
||||||
IrqCfg::new(pac::Interrupt::OC1, true, false),
|
|
||||||
Some(&mut dp.irqsel),
|
|
||||||
Some(&mut dp.sysconfig),
|
|
||||||
);
|
|
||||||
|
|
||||||
// First target for cascade
|
// First target for cascade
|
||||||
let mut cascade_target_1 =
|
let mut cascade_target_1 = CountdownTimer::new(dp.tim4, 50.MHz());
|
||||||
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true);
|
cascade_target_1.auto_deactivate(true);
|
||||||
cascade_target_1
|
cascade_target_1
|
||||||
.cascade_0_source(CascadeSource::TimBase, Some(3))
|
.cascade_source(CascadeSelect::Csd0, CascadeSource::Tim(3))
|
||||||
.expect("Configuring cascade source for TIM4 failed");
|
.unwrap();
|
||||||
let mut csd_cfg = CascadeCtrl {
|
let mut csd_cfg = CascadeControl {
|
||||||
enb_start_src_csd0: true,
|
enable_src_0: true,
|
||||||
|
trigger_mode_0: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
// Use trigger mode here
|
|
||||||
csd_cfg.trg_csd0 = true;
|
|
||||||
cascade_target_1.cascade_control(csd_cfg);
|
cascade_target_1.cascade_control(csd_cfg);
|
||||||
// Normally it should already be sufficient to activate IRQ in the CTRL
|
// 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
|
// register but a full interrupt is use here to display print output when
|
||||||
// the timer expires
|
// the timer expires
|
||||||
cascade_target_1.listen(
|
cascade_target_1.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC2, true, false));
|
||||||
Event::TimeOut,
|
|
||||||
IrqCfg::new(pac::Interrupt::OC2, true, false),
|
|
||||||
Some(&mut dp.irqsel),
|
|
||||||
Some(&mut dp.sysconfig),
|
|
||||||
);
|
|
||||||
// The counter will only activate when the cascade signal is coming in so
|
// 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
|
// it is okay to call start here to set the reset value
|
||||||
cascade_target_1.start(1.Hz());
|
cascade_target_1.start(1.Hz());
|
||||||
|
|
||||||
// Activated by first cascade target
|
// Activated by first cascade target
|
||||||
let mut cascade_target_2 =
|
let mut cascade_target_2 = CountdownTimer::new(dp.tim5, 50.MHz());
|
||||||
CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true);
|
cascade_target_2.auto_deactivate(true);
|
||||||
// Set TIM4 as cascade source
|
// Set TIM4 as cascade source
|
||||||
cascade_target_2
|
cascade_target_2
|
||||||
.cascade_1_source(CascadeSource::TimBase, Some(4))
|
.cascade_source(CascadeSelect::Csd1, CascadeSource::Tim(4))
|
||||||
.expect("Configuring cascade source for TIM5 failed");
|
.unwrap();
|
||||||
|
|
||||||
csd_cfg = CascadeCtrl::default();
|
csd_cfg = CascadeControl::default();
|
||||||
csd_cfg.enb_start_src_csd1 = true;
|
csd_cfg.enable_src_1 = true;
|
||||||
// Use trigger mode here
|
// Use trigger mode here
|
||||||
csd_cfg.trg_csd1 = true;
|
csd_cfg.trigger_mode_1 = true;
|
||||||
cascade_target_2.cascade_control(csd_cfg);
|
cascade_target_2.cascade_control(csd_cfg);
|
||||||
// Normally it should already be sufficient to activate IRQ in the CTRL
|
// 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
|
// register but a full interrupt is use here to display print output when
|
||||||
// the timer expires
|
// the timer expires
|
||||||
cascade_target_2.listen(
|
cascade_target_2.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC3, true, false));
|
||||||
Event::TimeOut,
|
|
||||||
IrqCfg::new(pac::Interrupt::OC3, true, false),
|
|
||||||
Some(&mut dp.irqsel),
|
|
||||||
Some(&mut dp.sysconfig),
|
|
||||||
);
|
|
||||||
// The counter will only activate when the cascade signal is coming in so
|
// 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
|
// it is okay to call start here to set the reset value
|
||||||
cascade_target_2.start(1.Hz());
|
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::OC2);
|
||||||
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC3);
|
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 {
|
loop {
|
||||||
rprintln!("-- Triggering cascade in 0.5 seconds --");
|
defmt::info!("-- Triggering cascade in 0.5 seconds --");
|
||||||
cascade_triggerer.start(2.Hz());
|
cascade_triggerer.start(2.Hz());
|
||||||
delay.delay_ms(5000);
|
delay.delay_ms(5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
fn OC0() {
|
|
||||||
default_ms_irq_handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
fn OC1() {
|
fn OC1() {
|
||||||
static mut IDX: u32 = 0;
|
static mut IDX: u32 = 0;
|
||||||
rprintln!("{}: Cascade triggered timed out", &IDX);
|
defmt::info!("{}: Cascade trigger timed out", &IDX);
|
||||||
*IDX += 1;
|
*IDX += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
fn OC2() {
|
fn OC2() {
|
||||||
static mut IDX: u32 = 0;
|
static mut IDX: u32 = 0;
|
||||||
rprintln!("{}: First cascade target timed out", &IDX);
|
defmt::info!("{}: First cascade target timed out", &IDX);
|
||||||
*IDX += 1;
|
*IDX += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
fn OC3() {
|
fn OC3() {
|
||||||
static mut IDX: u32 = 0;
|
static mut IDX: u32 = 0;
|
||||||
rprintln!("{}: Second cascade target timed out", &IDX);
|
defmt::info!("{}: Second cascade target timed out", &IDX);
|
||||||
*IDX += 1;
|
*IDX += 1;
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,36 @@
|
|||||||
//! Simple PWM example
|
//! Simple PWM example
|
||||||
|
//!
|
||||||
|
//! Outputs a PWM waveform on pin PA3.
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::{delay::DelayNs, pwm::SetDutyCycle};
|
use embedded_hal::{delay::DelayNs, pwm::SetDutyCycle};
|
||||||
use panic_rtt_target as _;
|
// Import panic provider.
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use panic_probe as _;
|
||||||
|
// Import logger.
|
||||||
|
use defmt_rtt as _;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
gpio::PinsA,
|
|
||||||
pac,
|
pac,
|
||||||
|
pins::PinsA,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
|
pwm::{self, get_duty_from_percent, PwmA, PwmB, PwmPin},
|
||||||
timer::set_up_ms_delay_provider,
|
timer::CountdownTimer,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
defmt::println!("-- VA108xx PWM example application--");
|
||||||
rprintln!("-- VA108xx PWM example application--");
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let pinsa = PinsA::new(dp.porta);
|
||||||
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
|
let mut pwm = pwm::PwmPin::new(pinsa.pa3, dp.tim3, 50.MHz(), 10.Hz()).unwrap();
|
||||||
let mut pwm = pwm::PwmPin::new(
|
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
|
||||||
(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);
|
|
||||||
let mut current_duty_cycle = 0.0;
|
let mut current_duty_cycle = 0.0;
|
||||||
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
pwm.enable();
|
pwm.enable();
|
||||||
|
|
||||||
// Delete type information, increased code readibility for the rest of the code
|
// Delete type information, increased code readibility for the rest of the code
|
||||||
let mut reduced_pin = ReducedPwmPin::from(pwm);
|
|
||||||
loop {
|
loop {
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
// Increase duty cycle continuously
|
// Increase duty cycle continuously
|
||||||
@ -42,11 +39,10 @@ fn main() -> ! {
|
|||||||
current_duty_cycle += 0.02;
|
current_duty_cycle += 0.02;
|
||||||
counter += 1;
|
counter += 1;
|
||||||
if counter % 10 == 0 {
|
if counter % 10 == 0 {
|
||||||
rprintln!("current duty cycle: {}", current_duty_cycle);
|
defmt::info!("current duty cycle: {}", current_duty_cycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
reduced_pin
|
pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
||||||
.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +51,7 @@ fn main() -> ! {
|
|||||||
current_duty_cycle = 0.0;
|
current_duty_cycle = 0.0;
|
||||||
let mut upper_limit = 1.0;
|
let mut upper_limit = 1.0;
|
||||||
let mut lower_limit = 0.0;
|
let mut lower_limit = 0.0;
|
||||||
let mut pwmb: 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_lower_limit(get_duty_from_percent(lower_limit));
|
||||||
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
||||||
while lower_limit < 0.5 {
|
while lower_limit < 0.5 {
|
||||||
@ -64,9 +60,9 @@ fn main() -> ! {
|
|||||||
upper_limit -= 0.01;
|
upper_limit -= 0.01;
|
||||||
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
|
pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
|
||||||
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
|
||||||
rprintln!("Lower limit: {}", pwmb.pwmb_lower_limit());
|
defmt::info!("Lower limit: {}", pwmb.pwmb_lower_limit());
|
||||||
rprintln!("Upper limit: {}", pwmb.pwmb_upper_limit());
|
defmt::info!("Upper limit: {}", pwmb.pwmb_upper_limit());
|
||||||
}
|
}
|
||||||
reduced_pin = ReducedPwmPin::<PwmA>::from(pwmb);
|
pwm = PwmPin::<PwmA>::from(pwmb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +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};
|
|
||||||
|
|
||||||
#[entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
rtt_init_print!();
|
|
||||||
let mut counter = 0;
|
|
||||||
loop {
|
|
||||||
rprintln!("{}: Hello, world!", counter);
|
|
||||||
counter += 1;
|
|
||||||
cortex_m::asm::delay(25_000_000);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +1,28 @@
|
|||||||
//! SPI example application
|
//! SPI example application
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::cell::RefCell;
|
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
delay::DelayNs,
|
delay::DelayNs,
|
||||||
spi::{Mode, SpiBus, MODE_0},
|
spi::{Mode, SpiBus, MODE_0},
|
||||||
};
|
};
|
||||||
use panic_rtt_target as _;
|
// Import panic provider.
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use panic_probe as _;
|
||||||
|
// Import logger.
|
||||||
|
use defmt_rtt as _;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
gpio::{PinsA, PinsB},
|
pac,
|
||||||
pac::{self, interrupt},
|
pins::{PinsA, PinsB},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
pwm::{default_ms_irq_handler, set_up_ms_tick},
|
spi::{self, configure_pin_as_hw_cs_pin, Spi, SpiClkConfig, TransferConfig},
|
||||||
spi::{self, Spi, SpiBase, TransferConfig},
|
timer::CountdownTimer,
|
||||||
IrqCfg,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum ExampleSelect {
|
pub enum ExampleSelect {
|
||||||
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
|
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
|
||||||
Loopback,
|
Loopback,
|
||||||
// Send a test buffer and print everything received
|
MosiMisoTiedTogetherManually,
|
||||||
TestBuffer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
@ -44,21 +41,14 @@ const FILL_WORD: u8 = 0x0f;
|
|||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
defmt::println!("-- VA108xx SPI example application--");
|
||||||
rprintln!("-- VA108xx SPI example application--");
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut delay = CountdownTimer::new(dp.tim0, 50.MHz());
|
||||||
let mut delay = set_up_ms_tick(
|
|
||||||
IrqCfg::new(interrupt::OC0, true, true),
|
|
||||||
&mut dp.sysconfig,
|
|
||||||
Some(&mut dp.irqsel),
|
|
||||||
50.MHz(),
|
|
||||||
dp.tim0,
|
|
||||||
);
|
|
||||||
|
|
||||||
let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
|
let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz())
|
||||||
let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
|
.expect("creating SPI clock config failed");
|
||||||
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
|
let pinsa = PinsA::new(dp.porta);
|
||||||
let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
|
let pinsb = PinsB::new(dp.portb);
|
||||||
|
|
||||||
let mut spi_cfg = spi::SpiConfig::default();
|
let mut spi_cfg = spi::SpiConfig::default();
|
||||||
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
if EXAMPLE_SEL == ExampleSelect::Loopback {
|
||||||
@ -66,90 +56,56 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up the SPI peripheral
|
// Set up the SPI peripheral
|
||||||
match SPI_BUS_SEL {
|
let mut spi = match SPI_BUS_SEL {
|
||||||
SpiBusSelect::SpiAPortA => {
|
SpiBusSelect::SpiAPortA => {
|
||||||
let (sck, mosi, miso) = (
|
let (sck, mosi, miso) = (pinsa.pa31, pinsa.pa30, pinsa.pa29);
|
||||||
pinsa.pa31.into_funsel_1(),
|
let mut spia = Spi::new(dp.spia, (sck, miso, mosi), spi_cfg).unwrap();
|
||||||
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,
|
|
||||||
);
|
|
||||||
spia.set_fill_word(FILL_WORD);
|
spia.set_fill_word(FILL_WORD);
|
||||||
spia_ref.borrow_mut().replace(spia.downgrade());
|
spia
|
||||||
}
|
}
|
||||||
SpiBusSelect::SpiAPortB => {
|
SpiBusSelect::SpiAPortB => {
|
||||||
let (sck, mosi, miso) = (
|
let (sck, mosi, miso) = (pinsb.pb9, pinsb.pb8, pinsb.pb7);
|
||||||
pinsb.pb9.into_funsel_2(),
|
let mut spia = Spi::new(dp.spia, (sck, miso, mosi), spi_cfg).unwrap();
|
||||||
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,
|
|
||||||
);
|
|
||||||
spia.set_fill_word(FILL_WORD);
|
spia.set_fill_word(FILL_WORD);
|
||||||
spia_ref.borrow_mut().replace(spia.downgrade());
|
spia
|
||||||
}
|
}
|
||||||
SpiBusSelect::SpiBPortB => {
|
SpiBusSelect::SpiBPortB => {
|
||||||
let (sck, mosi, miso) = (
|
let (sck, mosi, miso) = (pinsb.pb5, pinsb.pb4, pinsb.pb3);
|
||||||
pinsb.pb5.into_funsel_1(),
|
let mut spib = Spi::new(dp.spib, (sck, miso, mosi), spi_cfg).unwrap();
|
||||||
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,
|
|
||||||
);
|
|
||||||
spib.set_fill_word(FILL_WORD);
|
spib.set_fill_word(FILL_WORD);
|
||||||
spib_ref.borrow_mut().replace(spib.downgrade());
|
spib
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// Configure transfer specific properties here
|
// Configure transfer specific properties here
|
||||||
match SPI_BUS_SEL {
|
match SPI_BUS_SEL {
|
||||||
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
|
SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => {
|
||||||
if let Some(ref mut spi) = *spia_ref.borrow_mut() {
|
let transfer_cfg = TransferConfig {
|
||||||
let transfer_cfg =
|
clk_cfg: Some(spi_clk_cfg),
|
||||||
TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false);
|
mode: Some(SPI_MODE),
|
||||||
|
sod: true,
|
||||||
|
blockmode: BLOCKMODE,
|
||||||
|
bmstall: true,
|
||||||
|
hw_cs: None,
|
||||||
|
};
|
||||||
spi.cfg_transfer(&transfer_cfg);
|
spi.cfg_transfer(&transfer_cfg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
SpiBusSelect::SpiBPortB => {
|
SpiBusSelect::SpiBPortB => {
|
||||||
if let Some(ref mut spi) = *spib_ref.borrow_mut() {
|
let hw_cs_pin = configure_pin_as_hw_cs_pin(pinsb.pb2);
|
||||||
let hw_cs_pin = pinsb.pb2.into_funsel_1();
|
let transfer_cfg = TransferConfig {
|
||||||
let transfer_cfg = TransferConfig::new(
|
clk_cfg: Some(spi_clk_cfg),
|
||||||
SPI_SPEED_KHZ.kHz(),
|
mode: Some(SPI_MODE),
|
||||||
SPI_MODE,
|
sod: false,
|
||||||
Some(hw_cs_pin),
|
blockmode: BLOCKMODE,
|
||||||
BLOCKMODE,
|
bmstall: true,
|
||||||
false,
|
hw_cs: Some(hw_cs_pin),
|
||||||
);
|
};
|
||||||
spi.cfg_transfer(&transfer_cfg);
|
spi.cfg_transfer(&transfer_cfg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Application logic
|
// Application logic
|
||||||
loop {
|
loop {
|
||||||
let mut reply_buf: [u8; 8] = [0; 8];
|
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.
|
// Can't really verify correct reply here.
|
||||||
spi.write(&[0x42]).expect("write failed");
|
spi.write(&[0x42]).expect("write failed");
|
||||||
// Because of the loopback mode, we should get back the fill word here.
|
// Because of the loopback mode, we should get back the fill word here.
|
||||||
@ -160,7 +116,7 @@ fn main() -> ! {
|
|||||||
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
|
let tx_buf: [u8; 3] = [0x01, 0x02, 0x03];
|
||||||
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
|
spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap();
|
||||||
assert_eq!(tx_buf, reply_buf[0..3]);
|
assert_eq!(tx_buf, reply_buf[0..3]);
|
||||||
rprintln!(
|
defmt::info!(
|
||||||
"Received reply: {}, {}, {}",
|
"Received reply: {}, {}, {}",
|
||||||
reply_buf[0],
|
reply_buf[0],
|
||||||
reply_buf[1],
|
reply_buf[1],
|
||||||
@ -170,75 +126,12 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
|
let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01];
|
||||||
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
|
spi.transfer_in_place(&mut tx_rx_buf).unwrap();
|
||||||
rprintln!(
|
defmt::info!(
|
||||||
"Received reply: {}, {}, {}",
|
"Received reply: {}, {}, {}",
|
||||||
tx_rx_buf[0],
|
tx_rx_buf[0],
|
||||||
tx_rx_buf[1],
|
tx_rx_buf[1],
|
||||||
tx_rx_buf[2]
|
tx_rx_buf[2]
|
||||||
);
|
);
|
||||||
assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn OC0() {
|
|
||||||
default_ms_irq_handler()
|
|
||||||
}
|
|
||||||
|
@ -2,17 +2,19 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::cell::Cell;
|
|
||||||
use cortex_m::interrupt::Mutex;
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use panic_rtt_target as _;
|
use embedded_hal::delay::DelayNs;
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
// Import panic provider.
|
||||||
|
use panic_probe as _;
|
||||||
|
// Import logger.
|
||||||
|
use defmt_rtt as _;
|
||||||
|
use portable_atomic::AtomicU32;
|
||||||
use va108xx_hal::{
|
use va108xx_hal::{
|
||||||
clock::{get_sys_clock, set_sys_clock},
|
clock::{get_sys_clock, set_sys_clock},
|
||||||
pac::{self, interrupt},
|
pac::{self, interrupt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
time::Hertz,
|
time::Hertz,
|
||||||
timer::{default_ms_irq_handler, set_up_ms_tick, CountDownTimer, Event, IrqCfg, MS_COUNTER},
|
timer::{CountdownTimer, InterruptConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -21,14 +23,15 @@ enum LibType {
|
|||||||
Hal,
|
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]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
let dp = pac::Peripherals::take().unwrap();
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut delay = CountdownTimer::new(dp.tim2, 50.MHz());
|
||||||
let mut last_ms = 0;
|
let mut last_ms = 0;
|
||||||
rprintln!("-- Vorago system ticks using timers --");
|
defmt::info!("-- Vorago system ticks using timers --");
|
||||||
set_sys_clock(50.MHz());
|
set_sys_clock(50.MHz());
|
||||||
let lib_type = LibType::Hal;
|
let lib_type = LibType::Hal;
|
||||||
match lib_type {
|
match lib_type {
|
||||||
@ -64,33 +67,24 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LibType::Hal => {
|
LibType::Hal => {
|
||||||
set_up_ms_tick(
|
let mut ms_timer = CountdownTimer::new(dp.tim0, get_sys_clock().unwrap());
|
||||||
IrqCfg::new(interrupt::OC0, true, true),
|
ms_timer.enable_interrupt(InterruptConfig::new(interrupt::OC0, true, true));
|
||||||
&mut dp.sysconfig,
|
ms_timer.start(1.kHz());
|
||||||
Some(&mut dp.irqsel),
|
let mut second_timer = CountdownTimer::new(dp.tim1, get_sys_clock().unwrap());
|
||||||
50.MHz(),
|
second_timer.enable_interrupt(InterruptConfig::new(interrupt::OC1, true, true));
|
||||||
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),
|
|
||||||
);
|
|
||||||
second_timer.start(1.Hz());
|
second_timer.start(1.Hz());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loop {
|
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 {
|
if current_ms - last_ms >= 1000 {
|
||||||
last_ms = current_ms;
|
// To prevent drift.
|
||||||
rprintln!("MS counter: {}", current_ms);
|
last_ms += 1000;
|
||||||
let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get());
|
defmt::info!("MS counter: {}", current_ms);
|
||||||
rprintln!("Second counter: {}", second);
|
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]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn OC0() {
|
fn OC0() {
|
||||||
default_ms_irq_handler()
|
MS_COUNTER.fetch_add(1, portable_atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn OC1() {
|
fn OC1() {
|
||||||
cortex_m::interrupt::free(|cs| {
|
SEC_COUNTER.fetch_add(1, portable_atomic::Ordering::Relaxed);
|
||||||
let mut sec = SEC_COUNTER.borrow(cs).get();
|
|
||||||
sec += 1;
|
|
||||||
SEC_COUNTER.borrow(cs).set(sec);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,23 +13,25 @@
|
|||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_hal_nb::{nb, serial::Read};
|
use embedded_hal_nb::{nb, serial::Read};
|
||||||
use embedded_io::Write as _;
|
use embedded_io::Write as _;
|
||||||
use panic_rtt_target as _;
|
// Import panic provider.
|
||||||
use rtt_target::{rprintln, rtt_init_print};
|
use panic_probe as _;
|
||||||
use va108xx_hal::{gpio::PinsA, pac, prelude::*, uart};
|
// Import logger.
|
||||||
|
use defmt_rtt as _;
|
||||||
|
use va108xx_hal::{pac, pins::PinsA, prelude::*, uart};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
rtt_init_print!();
|
defmt::println!("-- VA108xx UART example application--");
|
||||||
rprintln!("-- 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 gpioa = PinsA::new(dp.porta);
|
||||||
let tx = gpioa.pa9.into_funsel_2();
|
let tx = gpioa.pa9;
|
||||||
let rx = gpioa.pa8.into_funsel_2();
|
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) = uart.split();
|
||||||
let (mut tx, mut rx) = uarta.split();
|
|
||||||
writeln!(tx, "Hello World\r").unwrap();
|
writeln!(tx, "Hello World\r").unwrap();
|
||||||
loop {
|
loop {
|
||||||
// Echo what is received on the serial link.
|
// Echo what is received on the serial link.
|
||||||
@ -39,9 +41,6 @@ fn main() -> ! {
|
|||||||
.expect("TX send error");
|
.expect("TX send error");
|
||||||
}
|
}
|
||||||
Err(nb::Error::WouldBlock) => (),
|
Err(nb::Error::WouldBlock) => (),
|
||||||
Err(nb::Error::Other(uart_error)) => {
|
|
||||||
rprintln!("UART receive error {:?}", uart_error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use panic_rtt_target as _;
|
use panic_probe as _;
|
||||||
|
use va108xx_hal as _;
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
|
1
flashloader/.gitignore
vendored
Normal file
1
flashloader/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/venv
|
36
flashloader/Cargo.toml
Normal file
36
flashloader/Cargo.toml
Normal 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
75
flashloader/README.md
Normal 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
476
flashloader/image-loader.py
Executable 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
1
flashloader/loader.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
serial_port = "/dev/ttyUSB1"
|
5
flashloader/requirements.txt
Normal file
5
flashloader/requirements.txt
Normal 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
2
flashloader/slot-a-blinky/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/app.map
|
42
flashloader/slot-a-blinky/Cargo.toml
Normal file
42
flashloader/slot-a-blinky/Cargo.toml
Normal 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.
|
11
flashloader/slot-a-blinky/memory.x
Normal file
11
flashloader/slot-a-blinky/memory.x
Normal 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);
|
25
flashloader/slot-a-blinky/src/main.rs
Normal file
25
flashloader/slot-a-blinky/src/main.rs
Normal 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
2
flashloader/slot-b-blinky/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/app.map
|
42
flashloader/slot-b-blinky/Cargo.toml
Normal file
42
flashloader/slot-b-blinky/Cargo.toml
Normal 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.
|
11
flashloader/slot-b-blinky/memory.x
Normal file
11
flashloader/slot-b-blinky/memory.x
Normal 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);
|
25
flashloader/slot-b-blinky/src/main.rs
Normal file
25
flashloader/slot-b-blinky/src/main.rs
Normal 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
463
flashloader/src/main.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
JLinkGDBServer -select USB -device Cortex-M0 -endian little -if JTAG-speed auto \
|
JLinkGDBServer -select USB -device VA10820 -endian little -if JTAG -speed auto \
|
||||||
-LocalhostOnly
|
-LocalhostOnly -jtagconf -1,-1
|
||||||
|
@ -9,27 +9,56 @@ variants:
|
|||||||
core_access_options: !Arm
|
core_access_options: !Arm
|
||||||
ap: 0
|
ap: 0
|
||||||
psel: 0x0
|
psel: 0x0
|
||||||
|
jtag_tap: 1
|
||||||
memory_map:
|
memory_map:
|
||||||
- !Ram
|
- !Ram
|
||||||
name: IRAM1
|
name: DRAM
|
||||||
range:
|
range:
|
||||||
start: 0x10000000
|
start: 0x10000000
|
||||||
end: 0x10008000
|
end: 0x10008000
|
||||||
cores:
|
cores:
|
||||||
- main
|
- main
|
||||||
- !Nvm
|
- !Nvm
|
||||||
name: IROM1
|
name: NVM
|
||||||
range:
|
range:
|
||||||
start: 0x0
|
start: 0x0
|
||||||
end: 0x20000
|
end: 0x20000
|
||||||
is_boot_memory: true
|
|
||||||
cores:
|
cores:
|
||||||
- main
|
- main
|
||||||
|
access:
|
||||||
|
write: false
|
||||||
|
boot: true
|
||||||
flash_algorithms:
|
flash_algorithms:
|
||||||
- va108xx_fm25v20a_fram_128kb_prog
|
- va108xx_fm25v20a_fram_128kb_prog
|
||||||
- va108xx_m95m01_128kb_prog
|
- va108xx_m95m01_128kb_prog
|
||||||
- va108xx_mr25h10_1mb_prog
|
- va108xx_mr25h10_1mb_prog
|
||||||
- va108xx_ttflash_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:
|
flash_algorithms:
|
||||||
- name: va108xx_fm25v20a_fram_128kb_prog
|
- name: va108xx_fm25v20a_fram_128kb_prog
|
||||||
description: VA108_FM25V20A_FRAM_128KB
|
description: VA108_FM25V20A_FRAM_128KB
|
||||||
|
18
scripts/defmt-telnet.sh
Executable file
18
scripts/defmt-telnet.sh
Executable 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
10
scripts/memory_app_a.x
Normal 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
10
scripts/memory_app_b.x
Normal 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);
|
21524
sections/sec-debug.txt
21524
sections/sec-debug.txt
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
31
va108xx-embassy/CHANGELOG.md
Normal file
31
va108xx-embassy/CHANGELOG.md
Normal 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
|
27
va108xx-embassy/Cargo.toml
Normal file
27
va108xx-embassy/Cargo.toml
Normal 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
10
va108xx-embassy/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[](https://crates.io/crates/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
3
va108xx-embassy/docs.sh
Executable 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
109
va108xx-embassy/src/lib.rs
Normal 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)
|
||||||
|
}
|
@ -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/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
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
|
## [v0.6.0] 2024-06-16
|
||||||
|
|
||||||
- Updated `embedded-hal` to v1
|
- Updated `embedded-hal` to v1
|
||||||
- Added optional `defmt` v0.3 feature and support.
|
- Added optional `defmt` v0.3 feature and support.
|
||||||
|
|
||||||
## [v0.5.2] 2024-06-16
|
## v0.5.2 2024-06-16
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- Replaced usage to `ptr::write_volatile` in UART module which is denied on more recent Rust
|
- Replaced usage to `ptr::write_volatile` in UART module which is denied on more recent Rust
|
||||||
compilers.
|
compilers.
|
||||||
|
|
||||||
## [v0.5.1]
|
## v0.5.1
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
@ -27,7 +152,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- `once_cell` to 1.12.0
|
- `once_cell` to 1.12.0
|
||||||
- Other dependencies: Only revision has changed
|
- Other dependencies: Only revision has changed
|
||||||
|
|
||||||
## [v0.5.0]
|
## v0.5.0
|
||||||
|
|
||||||
### Added
|
### 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
|
- 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
|
- Various smaller fixes for READMEs, update of links in documentation
|
||||||
- Simplified CI for github, do not use `cross`
|
- Simplified CI for github, do not use `cross`
|
||||||
- New `blinky-pac` example
|
- New `blinky-pac` example
|
||||||
- Use HAL delay in `blinky` example
|
- Use HAL delay in `blinky` example
|
||||||
|
|
||||||
## [v0.4.2]
|
## v0.4.2
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@ -57,24 +182,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- Clear TX and RX FIFO in SPI transfer function
|
- Clear TX and RX FIFO in SPI transfer function
|
||||||
|
|
||||||
## [v0.4.1]
|
## v0.4.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Initial blockmode setting was not set in SPI constructor
|
- Initial blockmode setting was not set in SPI constructor
|
||||||
|
|
||||||
## [v0.4.0]
|
## v0.4.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Replaced `Hertz` by `impl Into<Hertz>` completely and removed
|
- Replaced `Hertz` by `impl Into<Hertz>` completely and removed
|
||||||
`+ Copy` where not necessary
|
`+ Copy` where not necessary
|
||||||
|
|
||||||
## [v0.3.1]
|
## v0.3.1
|
||||||
|
|
||||||
- Updated all links to point to new repository
|
- Updated all links to point to new repository
|
||||||
|
|
||||||
## [v0.3.0]
|
## v0.3.0
|
||||||
|
|
||||||
### Added
|
### 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
|
- Primary repository now hosted on IRS external git: https://egit.irs.uni-stuttgart.de/rust/va108xx-hal
|
||||||
- Relicensed as Apache-2.0
|
- Relicensed as Apache-2.0
|
||||||
|
|
||||||
## [0.2.3]
|
## v0.2.3
|
||||||
|
|
||||||
### Added
|
### 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`
|
- Improved Timer API. It is now possible to simply use `new` on `CountDownTimer`
|
||||||
|
|
||||||
## [0.2.2]
|
## v0.2.2
|
||||||
|
|
||||||
### Added
|
### 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
|
- API which expects values in Hertz now uses `impl Into<Hertz>` as input parameter
|
||||||
|
|
||||||
## [0.2.1]
|
## v0.2.1
|
||||||
|
|
||||||
### Added
|
### 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`
|
- Moved the `FilterClkSel` struct to the `clock` module, re-exporting in `gpio`
|
||||||
- Clearing output state at initialization of Output pins
|
- Clearing output state at initialization of Output pins
|
||||||
|
|
||||||
## [0.2.0]
|
## v0.2.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -139,7 +264,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Some bugfixes for GPIO implementation
|
- Some bugfixes for GPIO implementation
|
||||||
- Rust edition updated to 2021
|
- Rust edition updated to 2021
|
||||||
|
|
||||||
## [0.1.0]
|
## v0.1.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@ -148,3 +273,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- RTT example application
|
- RTT example application
|
||||||
- Added basic test binary in form of an example
|
- Added basic test binary in form of an example
|
||||||
- README with basic instructions how to set up own binary crate
|
- 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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "va108xx-hal"
|
name = "va108xx-hal"
|
||||||
version = "0.6.0"
|
version = "0.11.1"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "HAL for the Vorago VA108xx family of microcontrollers"
|
description = "HAL for the Vorago VA108xx family of microcontrollers"
|
||||||
@ -15,34 +15,37 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"]}
|
|||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
nb = "1"
|
nb = "1"
|
||||||
paste = "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"
|
embedded-hal-nb = "1"
|
||||||
libm = "0.2"
|
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
|
embedded-io-async = "0.6"
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
typenum = "1"
|
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 }
|
defmt = { version = "0.3", optional = true }
|
||||||
delegate = "0.12"
|
|
||||||
|
|
||||||
[dependencies.va108xx]
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
|
||||||
version = "0.3"
|
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
|
||||||
default-features = false
|
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies]
|
||||||
features = ["critical-section"]
|
portable-atomic = "1"
|
||||||
|
|
||||||
[dependencies.embedded-hal]
|
|
||||||
version = "1"
|
|
||||||
|
|
||||||
[dependencies.void]
|
|
||||||
version = "1"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.once_cell]
|
|
||||||
version = "1.14"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["rt"]
|
default = ["rt"]
|
||||||
rt = ["va108xx/rt"]
|
rt = ["va108xx/rt"]
|
||||||
|
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03", "vorago-shared-periphs/defmt"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
|
rustdoc-args = ["--generate-link-to-definition"]
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# HAL for the Vorago VA108xx MCU family
|
# HAL for the Vorago VA108xx MCU family
|
||||||
|
|
||||||
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
|
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
|
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
|
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.
|
After that, you can use `cargo build` to build the development version of the crate.
|
||||||
|
|
||||||
If you have not done this yet, it is recommended to read some of the excellent resources
|
|
||||||
available to learn Rust:
|
|
||||||
|
|
||||||
- [Rust Embedded Book](https://docs.rust-embedded.org/book/)
|
|
||||||
- [Rust Discovery Book](https://docs.rust-embedded.org/discovery/)
|
|
||||||
|
|
||||||
## Setting up your own binary crate
|
## Setting up your own binary crate
|
||||||
|
|
||||||
If you have a custom board, you might be interested in setting up a new binary crate for your
|
If you have a custom board, you might be interested in setting up a new binary crate for your
|
||||||
@ -39,7 +33,7 @@ your custom board.
|
|||||||
|
|
||||||
The hello world of embedded development is usually to blinky a LED. This example
|
The hello world of embedded development is usually to blinky a LED. This example
|
||||||
is contained within the
|
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)
|
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`
|
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
|
7. Flashing the board might work differently for different boards and there is usually
|
||||||
more than one way. You can find example instructions in primary README.
|
more than one way. You can find example instructions in primary README.
|
||||||
|
|
||||||
|
## Embedded Rust
|
||||||
|
|
||||||
|
If you have not done this yet, it is recommended to read some of the excellent resources available
|
||||||
|
to learn Rust:
|
||||||
|
|
||||||
|
- [Rust Embedded Book](https://docs.rust-embedded.org/book/)
|
||||||
|
- [Rust Discovery Book](https://docs.rust-embedded.org/discovery/)
|
||||||
|
3
va108xx-hal/docs.sh
Executable file
3
va108xx-hal/docs.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||||
|
cargo +nightly doc --all-features --open
|
@ -2,26 +2,14 @@
|
|||||||
//!
|
//!
|
||||||
//! This also includes functionality to enable the peripheral clocks
|
//! This also includes functionality to enable the peripheral clocks
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::PeripheralSelect;
|
|
||||||
use cortex_m::interrupt::{self, Mutex};
|
use cortex_m::interrupt::{self, Mutex};
|
||||||
use once_cell::unsync::OnceCell;
|
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());
|
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 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 here so it can be used by other software components as well.
|
||||||
/// The clock can be set exactly once
|
/// 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) {
|
pub fn set_clk_div_register(syscfg: &mut va108xx::Sysconfig, clk_sel: FilterClkSel, div: u32) {
|
||||||
match clk_sel {
|
match clk_sel {
|
||||||
FilterClkSel::SysClk => (),
|
FilterClkSel::SysClk => (),
|
||||||
FilterClkSel::Clk1 => syscfg.ioconfig_clkdiv1().write(|w| unsafe { w.bits(div) }),
|
FilterClkSel::Clk1 => {
|
||||||
FilterClkSel::Clk2 => syscfg.ioconfig_clkdiv2().write(|w| unsafe { w.bits(div) }),
|
syscfg.ioconfig_clkdiv1().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::Clk2 => {
|
||||||
FilterClkSel::Clk5 => syscfg.ioconfig_clkdiv5().write(|w| unsafe { w.bits(div) }),
|
syscfg.ioconfig_clkdiv2().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::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)) });
|
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +1,20 @@
|
|||||||
//! # API for the GPIO peripheral
|
//! GPIO support module.
|
||||||
//!
|
//!
|
||||||
//! The implementation of this GPIO module is heavily based on the
|
//! Contains abstractions to use the pins provided by the [crate::pins] module as GPIO or
|
||||||
//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/index.html).
|
//! IO peripheral pins.
|
||||||
//!
|
//!
|
||||||
//! This API provides two different submodules, [`mod@pins`] and [`dynpins`],
|
//! The core data structures provided for this are the
|
||||||
//! 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 type-level API is strongly preferred. By representing the state of each
|
//! - [Output] for push-pull output pins.
|
||||||
//! pin within the type system, the compiler can detect logic errors at
|
//! - [Input] for input pins.
|
||||||
//! compile-time. Furthermore, the type-level API has absolutely zero run-time
|
//! - [Flex] for pins with flexible configuration requirements.
|
||||||
//! cost.
|
//! - [IoPeriphPin] for IO peripheral pins.
|
||||||
//!
|
//!
|
||||||
//! If needed, [`dynpins`] can be used to erase the type-level differences
|
//! The [crate::pins] module exposes singletons to access the [Pin]s required by this module
|
||||||
//! between pins. However, by doing so, pins must now be tracked at run-time,
|
//! in a type-safe way.
|
||||||
//! and each pin has a non-zero memory footprint.
|
|
||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/blinky.rs)
|
//! - [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::*;
|
||||||
#[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;
|
|
||||||
|
@ -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),
|
|
||||||
]
|
|
||||||
);
|
|
@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,877 +2,5 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [REB1 I2C temperature sensor example](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/adt75-temp-sensor.rs)
|
//! - [REB1 I2C temperature sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs)
|
||||||
use crate::{
|
pub use vorago_shared_periphs::i2c::*;
|
||||||
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),);
|
|
||||||
|
@ -1,100 +1,59 @@
|
|||||||
#![no_std]
|
#![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;
|
||||||
pub use va108xx as pac;
|
pub use va108xx as pac;
|
||||||
|
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod gpio;
|
pub mod gpio;
|
||||||
pub mod i2c;
|
pub mod i2c;
|
||||||
|
pub mod pins;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
pub mod spi;
|
pub mod spi;
|
||||||
pub mod sysconfig;
|
pub mod sysconfig;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod typelevel;
|
|
||||||
pub mod uart;
|
pub mod uart;
|
||||||
pub mod utility;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, Copy, Clone, PartialEq)]
|
pub use vorago_shared_periphs::{
|
||||||
pub enum FunSel {
|
disable_nvic_interrupt, enable_nvic_interrupt, FunSel, InterruptConfig, PeripheralSelect,
|
||||||
Sel1 = 0b01,
|
};
|
||||||
Sel2 = 0b10,
|
|
||||||
Sel3 = 0b11,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
/// This is the NONE destination reigster value for the IRQSEL peripheral.
|
||||||
pub enum PortSel {
|
pub const IRQ_DST_NONE: u32 = 0xffffffff;
|
||||||
PortA,
|
|
||||||
PortB,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||||
pub enum PeripheralSelect {
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
PortA = 0,
|
#[error("invalid pin with number {0}")]
|
||||||
PortB = 1,
|
pub struct InvalidPinError(u8);
|
||||||
Spi0 = 4,
|
|
||||||
Spi1 = 5,
|
|
||||||
Spi2 = 6,
|
|
||||||
Uart0 = 8,
|
|
||||||
Uart1 = 9,
|
|
||||||
I2c0 = 16,
|
|
||||||
I2c1 = 17,
|
|
||||||
Irqsel = 21,
|
|
||||||
Ioconfig = 22,
|
|
||||||
Utility = 23,
|
|
||||||
Gpio = 24,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic IRQ config which can be used to specify whether the HAL driver will
|
/// Can be used to manually manipulate the function select of port pins.
|
||||||
/// 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
|
/// The function selection table can be found on p.36 of the programmers guide. Please note
|
||||||
/// this steps themselves
|
/// that most of the structures and APIs in this library will automatically correctly configure
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
/// the pin or statically expect the correct pin type.
|
||||||
pub struct IrqCfg {
|
pub fn port_function_select(
|
||||||
/// 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(
|
|
||||||
ioconfig: &mut pac::Ioconfig,
|
ioconfig: &mut pac::Ioconfig,
|
||||||
port: PortSel,
|
port: Port,
|
||||||
pin: u8,
|
pin: u8,
|
||||||
funsel: FunSel,
|
funsel: FunSel,
|
||||||
) -> Result<(), InvalidPin> {
|
) -> Result<(), InvalidPinError> {
|
||||||
match port {
|
if (port == Port::A && pin >= 32) || (port == Port::B && pin >= 24) {
|
||||||
PortSel::PortA => {
|
return Err(InvalidPinError(pin));
|
||||||
if pin > 31 {
|
|
||||||
return Err(InvalidPin(()));
|
|
||||||
}
|
}
|
||||||
ioconfig
|
|
||||||
.porta(pin as usize)
|
let reg_block = match port {
|
||||||
.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
PortSel::PortB => {
|
|
||||||
if pin > 23 {
|
#[allow(dead_code)]
|
||||||
return Err(InvalidPin(()));
|
pub(crate) mod sealed {
|
||||||
}
|
pub trait Sealed {}
|
||||||
ioconfig
|
|
||||||
.portb0(pin as usize)
|
|
||||||
.modify(|_, w| unsafe { w.funsel().bits(funsel as u8) });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
6
va108xx-hal/src/pins.rs
Normal file
6
va108xx-hal/src/pins.rs
Normal 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::*;
|
@ -1,3 +1,5 @@
|
|||||||
//! Prelude
|
//! Prelude
|
||||||
pub use fugit::ExtU32 as _;
|
pub use fugit::ExtU32 as _;
|
||||||
pub use fugit::RateExtU32 as _;
|
pub use fugit::RateExtU32 as _;
|
||||||
|
|
||||||
|
pub use crate::time::*;
|
||||||
|
@ -4,384 +4,5 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/pwm.rs)
|
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
|
||||||
use core::convert::Infallible;
|
pub use vorago_shared_periphs::pwm::*;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
12
va108xx-hal/src/spi/mod.rs
Normal file
12
va108xx-hal/src/spi/mod.rs
Normal 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::*;
|
@ -1,56 +1,43 @@
|
|||||||
use crate::{pac, PeripheralSelect};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[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
|
/// 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
|
/// (equivalent to disabling) or larger than 24 bits
|
||||||
pub fn enable_rom_scrubbing(
|
pub fn enable_rom_scrubbing(scrub_rate: u32) -> Result<(), InvalidCounterResetVal> {
|
||||||
syscfg: &mut pac::Sysconfig,
|
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||||
scrub_rate: u32,
|
|
||||||
) -> Result<(), InvalidounterResetVal> {
|
|
||||||
if scrub_rate == 0 || scrub_rate > u32::pow(2, 24) {
|
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) });
|
syscfg.rom_scrub().write(|w| unsafe { w.bits(scrub_rate) });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_rom_scrubbing(syscfg: &mut pac::Sysconfig) {
|
pub fn disable_rom_scrubbing() {
|
||||||
syscfg.rom_scrub().write(|w| unsafe { w.bits(0) })
|
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||||
|
syscfg.rom_scrub().write(|w| unsafe { w.bits(0) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable scrubbing for the RAM
|
/// 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
|
/// (equivalent to disabling) or larger than 24 bits
|
||||||
pub fn enable_ram_scrubbing(
|
pub fn enable_ram_scrubbing(scrub_rate: u32) -> Result<(), InvalidCounterResetVal> {
|
||||||
syscfg: &mut pac::Sysconfig,
|
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||||
scrub_rate: u32,
|
|
||||||
) -> Result<(), InvalidounterResetVal> {
|
|
||||||
if scrub_rate == 0 || scrub_rate > u32::pow(2, 24) {
|
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) });
|
syscfg.ram_scrub().write(|w| unsafe { w.bits(scrub_rate) });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_ram_scrubbing(syscfg: &mut pac::Sysconfig) {
|
pub fn disable_ram_scrubbing() {
|
||||||
syscfg.ram_scrub().write(|w| unsafe { w.bits(0) })
|
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
|
pub use vorago_shared_periphs::sysconfig::{
|
||||||
/// in a reset state
|
assert_peripheral_reset, disable_peripheral_clock, enable_peripheral_clock,
|
||||||
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)) });
|
|
||||||
}
|
|
||||||
|
@ -1,26 +1,2 @@
|
|||||||
//! Time units
|
//! Time units
|
||||||
|
pub use vorago_shared_periphs::time::*;
|
||||||
// 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;
|
|
||||||
|
@ -2,789 +2,6 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-hal/src/branch/main/examples/timer-ticks.rs)
|
//! - [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-hal/src/branch/main/examples/cascade.rs)
|
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
|
||||||
pub use crate::IrqCfg;
|
pub use vorago_shared_periphs::timer::*;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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
17
va108xx-hal/src/uart/mod.rs
Normal file
17
va108xx-hal/src/uart/mod.rs
Normal 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::*;
|
@ -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) };
|
|
||||||
}
|
|
@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## [unreleased]
|
## [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
|
## [v0.3.0] 2024-06-16
|
||||||
|
|
||||||
- Re-generated PAC with `svd2rust` v0.33.3
|
- Re-generated PAC with `svd2rust` v0.33.3
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "va108xx"
|
name = "va108xx"
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "PAC for the Vorago VA108xx family of microcontrollers"
|
description = "PAC for the Vorago VA108xx family of microcontrollers"
|
||||||
@ -13,6 +13,7 @@ categories = ["embedded", "no-std", "hardware-support"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
cortex-m = "0.7"
|
||||||
vcell = "0.1.3"
|
vcell = "0.1.3"
|
||||||
|
defmt = { version = "1", optional = true }
|
||||||
critical-section = { version = "1", optional = true }
|
critical-section = { version = "1", optional = true }
|
||||||
|
|
||||||
[dependencies.cortex-m-rt]
|
[dependencies.cortex-m-rt]
|
||||||
@ -21,7 +22,9 @@ version = ">=0.6.15,<0.8"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
rt = ["cortex-m-rt/device"]
|
rt = ["cortex-m-rt/device"]
|
||||||
|
# Adds Debug implementation
|
||||||
|
debug = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
|
rustdoc-args = ["--generate-link-to-definition"]
|
||||||
|
@ -24,7 +24,13 @@ features = ["rt"]
|
|||||||
The `rt` feature is optional and recommended. It brings in support for `cortex-m-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
|
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
|
## Regenerating the PAC
|
||||||
|
|
||||||
|
3
va108xx/docs.sh
Executable file
3
va108xx/docs.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||||
|
cargo +nightly doc --all-features --open
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
# Use installed tool by default
|
# Use installed tool by default
|
||||||
svd2rust_bin="svd2rust"
|
svd2rust_bin="svd2rust"
|
||||||
@ -30,7 +30,7 @@ fi
|
|||||||
|
|
||||||
svdtools patch svd/va108xx-patch.yml
|
svdtools patch svd/va108xx-patch.yml
|
||||||
# See https://github.com/rust-embedded/svd2rust/issues/830 for required re-export.
|
# 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=$?
|
result=$?
|
||||||
if [ $result -ne 0 ]; then
|
if [ $result -ne 0 ]; then
|
||||||
|
@ -82,169 +82,6 @@ pub trait Resettable: RegisterSpec {
|
|||||||
Self::RESET_VALUE
|
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)]
|
#[doc(hidden)]
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
#[doc = " Register reader."]
|
#[doc = " Register reader."]
|
||||||
@ -369,7 +206,7 @@ pub struct RangeTo<const MAX: u64>;
|
|||||||
#[doc = " Write field Proxy"]
|
#[doc = " Write field Proxy"]
|
||||||
pub type FieldWriter<'a, REG, const WI: u8, FI = u8, Safety = Unsafe> =
|
pub type FieldWriter<'a, REG, const WI: u8, FI = u8, Safety = Unsafe> =
|
||||||
raw::FieldWriter<'a, REG, WI, FI, Safety>;
|
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
|
where
|
||||||
REG: Writable + RegisterSpec,
|
REG: Writable + RegisterSpec,
|
||||||
FI: FieldSpec,
|
FI: FieldSpec,
|
||||||
@ -616,3 +453,278 @@ where
|
|||||||
self.w
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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>
|
pub struct FieldWriter<'a, REG, const WI: u8, FI = u8, Safety = Unsafe>
|
||||||
where
|
where
|
||||||
REG: Writable + RegisterSpec,
|
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>
|
pub struct BitWriter<'a, REG, FI = bool, M = BitM>
|
||||||
where
|
where
|
||||||
REG: Writable + RegisterSpec,
|
REG: Writable + RegisterSpec,
|
||||||
|
@ -240,67 +240,67 @@ impl RegisterBlock {
|
|||||||
&self.perid
|
&self.perid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[doc = "CTRL (rw) register accessor: Control Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`ctrl::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`ctrl::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@ctrl`]
|
#[doc = "CTRL (rw) register accessor: Control Register\n\nYou can [`read`](crate::Reg::read) this register and get [`ctrl::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`ctrl::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@ctrl`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "CTRL")]
|
#[doc(alias = "CTRL")]
|
||||||
pub type Ctrl = crate::Reg<ctrl::CtrlSpec>;
|
pub type Ctrl = crate::Reg<ctrl::CtrlSpec>;
|
||||||
#[doc = "Control Register"]
|
#[doc = "Control Register"]
|
||||||
pub mod ctrl;
|
pub mod ctrl;
|
||||||
#[doc = "CLKSCALE (rw) register accessor: Clock Scale divide value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`clkscale::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`clkscale::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@clkscale`]
|
#[doc = "CLKSCALE (rw) register accessor: Clock Scale divide value\n\nYou can [`read`](crate::Reg::read) this register and get [`clkscale::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`clkscale::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@clkscale`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "CLKSCALE")]
|
#[doc(alias = "CLKSCALE")]
|
||||||
pub type Clkscale = crate::Reg<clkscale::ClkscaleSpec>;
|
pub type Clkscale = crate::Reg<clkscale::ClkscaleSpec>;
|
||||||
#[doc = "Clock Scale divide value"]
|
#[doc = "Clock Scale divide value"]
|
||||||
pub mod clkscale;
|
pub mod clkscale;
|
||||||
#[doc = "WORDS (rw) register accessor: Word Count value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`words::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`words::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@words`]
|
#[doc = "WORDS (rw) register accessor: Word Count value\n\nYou can [`read`](crate::Reg::read) this register and get [`words::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`words::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@words`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "WORDS")]
|
#[doc(alias = "WORDS")]
|
||||||
pub type Words = crate::Reg<words::WordsSpec>;
|
pub type Words = crate::Reg<words::WordsSpec>;
|
||||||
#[doc = "Word Count value"]
|
#[doc = "Word Count value"]
|
||||||
pub mod words;
|
pub mod words;
|
||||||
#[doc = "ADDRESS (rw) register accessor: I2C Address value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`address::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`address::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@address`]
|
#[doc = "ADDRESS (rw) register accessor: I2C Address value\n\nYou can [`read`](crate::Reg::read) this register and get [`address::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`address::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@address`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "ADDRESS")]
|
#[doc(alias = "ADDRESS")]
|
||||||
pub type Address = crate::Reg<address::AddressSpec>;
|
pub type Address = crate::Reg<address::AddressSpec>;
|
||||||
#[doc = "I2C Address value"]
|
#[doc = "I2C Address value"]
|
||||||
pub mod address;
|
pub mod address;
|
||||||
#[doc = "DATA (rw) register accessor: Data Input/Output\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`data::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`data::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@data`]
|
#[doc = "DATA (rw) register accessor: Data Input/Output\n\nYou can [`read`](crate::Reg::read) this register and get [`data::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`data::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@data`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "DATA")]
|
#[doc(alias = "DATA")]
|
||||||
pub type Data = crate::Reg<data::DataSpec>;
|
pub type Data = crate::Reg<data::DataSpec>;
|
||||||
#[doc = "Data Input/Output"]
|
#[doc = "Data Input/Output"]
|
||||||
pub mod data;
|
pub mod data;
|
||||||
#[doc = "CMD (rw) register accessor: Command Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`cmd::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`cmd::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@cmd`]
|
#[doc = "CMD (rw) register accessor: Command Register\n\nYou can [`read`](crate::Reg::read) this register and get [`cmd::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`cmd::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@cmd`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "CMD")]
|
#[doc(alias = "CMD")]
|
||||||
pub type Cmd = crate::Reg<cmd::CmdSpec>;
|
pub type Cmd = crate::Reg<cmd::CmdSpec>;
|
||||||
#[doc = "Command Register"]
|
#[doc = "Command Register"]
|
||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
#[doc = "STATUS (r) register accessor: I2C Controller Status Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`status::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@status`]
|
#[doc = "STATUS (r) register accessor: I2C Controller Status Register\n\nYou can [`read`](crate::Reg::read) this register and get [`status::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@status`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "STATUS")]
|
#[doc(alias = "STATUS")]
|
||||||
pub type Status = crate::Reg<status::StatusSpec>;
|
pub type Status = crate::Reg<status::StatusSpec>;
|
||||||
#[doc = "I2C Controller Status Register"]
|
#[doc = "I2C Controller Status Register"]
|
||||||
pub mod status;
|
pub mod status;
|
||||||
#[doc = "STATE (r) register accessor: Internal STATE of I2C Master Controller\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`state::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@state`]
|
#[doc = "STATE (r) register accessor: Internal STATE of I2C Master Controller\n\nYou can [`read`](crate::Reg::read) this register and get [`state::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@state`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "STATE")]
|
#[doc(alias = "STATE")]
|
||||||
pub type State = crate::Reg<state::StateSpec>;
|
pub type State = crate::Reg<state::StateSpec>;
|
||||||
#[doc = "Internal STATE of I2C Master Controller"]
|
#[doc = "Internal STATE of I2C Master Controller"]
|
||||||
pub mod state;
|
pub mod state;
|
||||||
#[doc = "TXCOUNT (r) register accessor: TX Count Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`txcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@txcount`]
|
#[doc = "TXCOUNT (r) register accessor: TX Count Register\n\nYou can [`read`](crate::Reg::read) this register and get [`txcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@txcount`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "TXCOUNT")]
|
#[doc(alias = "TXCOUNT")]
|
||||||
pub type Txcount = crate::Reg<txcount::TxcountSpec>;
|
pub type Txcount = crate::Reg<txcount::TxcountSpec>;
|
||||||
#[doc = "TX Count Register"]
|
#[doc = "TX Count Register"]
|
||||||
pub mod txcount;
|
pub mod txcount;
|
||||||
#[doc = "RXCOUNT (r) register accessor: RX Count Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`rxcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@rxcount`]
|
#[doc = "RXCOUNT (r) register accessor: RX Count Register\n\nYou can [`read`](crate::Reg::read) this register and get [`rxcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@rxcount`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "RXCOUNT")]
|
#[doc(alias = "RXCOUNT")]
|
||||||
pub type Rxcount = crate::Reg<rxcount::RxcountSpec>;
|
pub type Rxcount = crate::Reg<rxcount::RxcountSpec>;
|
||||||
#[doc = "RX Count Register"]
|
#[doc = "RX Count Register"]
|
||||||
pub mod rxcount;
|
pub mod rxcount;
|
||||||
#[doc = "IRQ_ENB (rw) register accessor: Interrupt Enable Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`irq_enb::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`irq_enb::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_enb`]
|
#[doc = "IRQ_ENB (rw) register accessor: Interrupt Enable Register\n\nYou can [`read`](crate::Reg::read) this register and get [`irq_enb::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`irq_enb::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@irq_enb`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "IRQ_ENB")]
|
#[doc(alias = "IRQ_ENB")]
|
||||||
pub type IrqEnb = crate::Reg<irq_enb::IrqEnbSpec>;
|
pub type IrqEnb = crate::Reg<irq_enb::IrqEnbSpec>;
|
||||||
@ -312,97 +312,97 @@ pub use irq_enb as irq_clr;
|
|||||||
pub use IrqEnb as IrqRaw;
|
pub use IrqEnb as IrqRaw;
|
||||||
pub use IrqEnb as IrqEnd;
|
pub use IrqEnb as IrqEnd;
|
||||||
pub use IrqEnb as IrqClr;
|
pub use IrqEnb as IrqClr;
|
||||||
#[doc = "RXFIFOIRQTRG (rw) register accessor: Rx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`rxfifoirqtrg::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`rxfifoirqtrg::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@rxfifoirqtrg`]
|
#[doc = "RXFIFOIRQTRG (rw) register accessor: Rx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::Reg::read) this register and get [`rxfifoirqtrg::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`rxfifoirqtrg::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@rxfifoirqtrg`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "RXFIFOIRQTRG")]
|
#[doc(alias = "RXFIFOIRQTRG")]
|
||||||
pub type Rxfifoirqtrg = crate::Reg<rxfifoirqtrg::RxfifoirqtrgSpec>;
|
pub type Rxfifoirqtrg = crate::Reg<rxfifoirqtrg::RxfifoirqtrgSpec>;
|
||||||
#[doc = "Rx FIFO IRQ Trigger Level"]
|
#[doc = "Rx FIFO IRQ Trigger Level"]
|
||||||
pub mod rxfifoirqtrg;
|
pub mod rxfifoirqtrg;
|
||||||
#[doc = "TXFIFOIRQTRG (rw) register accessor: Tx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`txfifoirqtrg::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`txfifoirqtrg::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@txfifoirqtrg`]
|
#[doc = "TXFIFOIRQTRG (rw) register accessor: Tx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::Reg::read) this register and get [`txfifoirqtrg::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`txfifoirqtrg::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@txfifoirqtrg`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "TXFIFOIRQTRG")]
|
#[doc(alias = "TXFIFOIRQTRG")]
|
||||||
pub type Txfifoirqtrg = crate::Reg<txfifoirqtrg::TxfifoirqtrgSpec>;
|
pub type Txfifoirqtrg = crate::Reg<txfifoirqtrg::TxfifoirqtrgSpec>;
|
||||||
#[doc = "Tx FIFO IRQ Trigger Level"]
|
#[doc = "Tx FIFO IRQ Trigger Level"]
|
||||||
pub mod txfifoirqtrg;
|
pub mod txfifoirqtrg;
|
||||||
#[doc = "FIFO_CLR (w) register accessor: Clear FIFO Register\n\nYou can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`fifo_clr::W`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@fifo_clr`]
|
#[doc = "FIFO_CLR (w) register accessor: Clear FIFO Register\n\nYou can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`fifo_clr::W`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@fifo_clr`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "FIFO_CLR")]
|
#[doc(alias = "FIFO_CLR")]
|
||||||
pub type FifoClr = crate::Reg<fifo_clr::FifoClrSpec>;
|
pub type FifoClr = crate::Reg<fifo_clr::FifoClrSpec>;
|
||||||
#[doc = "Clear FIFO Register"]
|
#[doc = "Clear FIFO Register"]
|
||||||
pub mod fifo_clr;
|
pub mod fifo_clr;
|
||||||
#[doc = "TMCONFIG (rw) register accessor: Timing Config Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`tmconfig::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`tmconfig::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@tmconfig`]
|
#[doc = "TMCONFIG (rw) register accessor: Timing Config Register\n\nYou can [`read`](crate::Reg::read) this register and get [`tmconfig::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`tmconfig::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@tmconfig`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "TMCONFIG")]
|
#[doc(alias = "TMCONFIG")]
|
||||||
pub type Tmconfig = crate::Reg<tmconfig::TmconfigSpec>;
|
pub type Tmconfig = crate::Reg<tmconfig::TmconfigSpec>;
|
||||||
#[doc = "Timing Config Register"]
|
#[doc = "Timing Config Register"]
|
||||||
pub mod tmconfig;
|
pub mod tmconfig;
|
||||||
#[doc = "CLKTOLIMIT (rw) register accessor: Clock Low Timeout Limit Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`clktolimit::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`clktolimit::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@clktolimit`]
|
#[doc = "CLKTOLIMIT (rw) register accessor: Clock Low Timeout Limit Register\n\nYou can [`read`](crate::Reg::read) this register and get [`clktolimit::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`clktolimit::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@clktolimit`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "CLKTOLIMIT")]
|
#[doc(alias = "CLKTOLIMIT")]
|
||||||
pub type Clktolimit = crate::Reg<clktolimit::ClktolimitSpec>;
|
pub type Clktolimit = crate::Reg<clktolimit::ClktolimitSpec>;
|
||||||
#[doc = "Clock Low Timeout Limit Register"]
|
#[doc = "Clock Low Timeout Limit Register"]
|
||||||
pub mod clktolimit;
|
pub mod clktolimit;
|
||||||
#[doc = "S0_CTRL (rw) register accessor: Slave Control Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_ctrl::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_ctrl::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_ctrl`]
|
#[doc = "S0_CTRL (rw) register accessor: Slave Control Register\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_ctrl::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_ctrl::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_ctrl`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_CTRL")]
|
#[doc(alias = "S0_CTRL")]
|
||||||
pub type S0Ctrl = crate::Reg<s0_ctrl::S0CtrlSpec>;
|
pub type S0Ctrl = crate::Reg<s0_ctrl::S0CtrlSpec>;
|
||||||
#[doc = "Slave Control Register"]
|
#[doc = "Slave Control Register"]
|
||||||
pub mod s0_ctrl;
|
pub mod s0_ctrl;
|
||||||
#[doc = "S0_MAXWORDS (rw) register accessor: Slave MaxWords Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_maxwords::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_maxwords::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_maxwords`]
|
#[doc = "S0_MAXWORDS (rw) register accessor: Slave MaxWords Register\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_maxwords::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_maxwords::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_maxwords`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_MAXWORDS")]
|
#[doc(alias = "S0_MAXWORDS")]
|
||||||
pub type S0Maxwords = crate::Reg<s0_maxwords::S0MaxwordsSpec>;
|
pub type S0Maxwords = crate::Reg<s0_maxwords::S0MaxwordsSpec>;
|
||||||
#[doc = "Slave MaxWords Register"]
|
#[doc = "Slave MaxWords Register"]
|
||||||
pub mod s0_maxwords;
|
pub mod s0_maxwords;
|
||||||
#[doc = "S0_ADDRESS (rw) register accessor: Slave I2C Address Value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_address::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_address::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_address`]
|
#[doc = "S0_ADDRESS (rw) register accessor: Slave I2C Address Value\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_address::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_address::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_address`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_ADDRESS")]
|
#[doc(alias = "S0_ADDRESS")]
|
||||||
pub type S0Address = crate::Reg<s0_address::S0AddressSpec>;
|
pub type S0Address = crate::Reg<s0_address::S0AddressSpec>;
|
||||||
#[doc = "Slave I2C Address Value"]
|
#[doc = "Slave I2C Address Value"]
|
||||||
pub mod s0_address;
|
pub mod s0_address;
|
||||||
#[doc = "S0_ADDRESSMASK (rw) register accessor: Slave I2C Address Mask value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_addressmask::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_addressmask::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_addressmask`]
|
#[doc = "S0_ADDRESSMASK (rw) register accessor: Slave I2C Address Mask value\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_addressmask::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_addressmask::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_addressmask`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_ADDRESSMASK")]
|
#[doc(alias = "S0_ADDRESSMASK")]
|
||||||
pub type S0Addressmask = crate::Reg<s0_addressmask::S0AddressmaskSpec>;
|
pub type S0Addressmask = crate::Reg<s0_addressmask::S0AddressmaskSpec>;
|
||||||
#[doc = "Slave I2C Address Mask value"]
|
#[doc = "Slave I2C Address Mask value"]
|
||||||
pub mod s0_addressmask;
|
pub mod s0_addressmask;
|
||||||
#[doc = "S0_DATA (rw) register accessor: Slave Data Input/Output\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_data::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_data::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_data`]
|
#[doc = "S0_DATA (rw) register accessor: Slave Data Input/Output\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_data::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_data::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_data`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_DATA")]
|
#[doc(alias = "S0_DATA")]
|
||||||
pub type S0Data = crate::Reg<s0_data::S0DataSpec>;
|
pub type S0Data = crate::Reg<s0_data::S0DataSpec>;
|
||||||
#[doc = "Slave Data Input/Output"]
|
#[doc = "Slave Data Input/Output"]
|
||||||
pub mod s0_data;
|
pub mod s0_data;
|
||||||
#[doc = "S0_LASTADDRESS (r) register accessor: Slave I2C Last Address value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_lastaddress::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_lastaddress`]
|
#[doc = "S0_LASTADDRESS (r) register accessor: Slave I2C Last Address value\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_lastaddress::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_lastaddress`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_LASTADDRESS")]
|
#[doc(alias = "S0_LASTADDRESS")]
|
||||||
pub type S0Lastaddress = crate::Reg<s0_lastaddress::S0LastaddressSpec>;
|
pub type S0Lastaddress = crate::Reg<s0_lastaddress::S0LastaddressSpec>;
|
||||||
#[doc = "Slave I2C Last Address value"]
|
#[doc = "Slave I2C Last Address value"]
|
||||||
pub mod s0_lastaddress;
|
pub mod s0_lastaddress;
|
||||||
#[doc = "S0_STATUS (r) register accessor: Slave I2C Controller Status Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_status::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_status`]
|
#[doc = "S0_STATUS (r) register accessor: Slave I2C Controller Status Register\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_status::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_status`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_STATUS")]
|
#[doc(alias = "S0_STATUS")]
|
||||||
pub type S0Status = crate::Reg<s0_status::S0StatusSpec>;
|
pub type S0Status = crate::Reg<s0_status::S0StatusSpec>;
|
||||||
#[doc = "Slave I2C Controller Status Register"]
|
#[doc = "Slave I2C Controller Status Register"]
|
||||||
pub mod s0_status;
|
pub mod s0_status;
|
||||||
#[doc = "S0_STATE (r) register accessor: Internal STATE of I2C Slave Controller\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_state::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_state`]
|
#[doc = "S0_STATE (r) register accessor: Internal STATE of I2C Slave Controller\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_state::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_state`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_STATE")]
|
#[doc(alias = "S0_STATE")]
|
||||||
pub type S0State = crate::Reg<s0_state::S0StateSpec>;
|
pub type S0State = crate::Reg<s0_state::S0StateSpec>;
|
||||||
#[doc = "Internal STATE of I2C Slave Controller"]
|
#[doc = "Internal STATE of I2C Slave Controller"]
|
||||||
pub mod s0_state;
|
pub mod s0_state;
|
||||||
#[doc = "S0_TXCOUNT (r) register accessor: Slave TX Count Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_txcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_txcount`]
|
#[doc = "S0_TXCOUNT (r) register accessor: Slave TX Count Register\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_txcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_txcount`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_TXCOUNT")]
|
#[doc(alias = "S0_TXCOUNT")]
|
||||||
pub type S0Txcount = crate::Reg<s0_txcount::S0TxcountSpec>;
|
pub type S0Txcount = crate::Reg<s0_txcount::S0TxcountSpec>;
|
||||||
#[doc = "Slave TX Count Register"]
|
#[doc = "Slave TX Count Register"]
|
||||||
pub mod s0_txcount;
|
pub mod s0_txcount;
|
||||||
#[doc = "S0_RXCOUNT (r) register accessor: Slave RX Count Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_rxcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_rxcount`]
|
#[doc = "S0_RXCOUNT (r) register accessor: Slave RX Count Register\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_rxcount::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_rxcount`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_RXCOUNT")]
|
#[doc(alias = "S0_RXCOUNT")]
|
||||||
pub type S0Rxcount = crate::Reg<s0_rxcount::S0RxcountSpec>;
|
pub type S0Rxcount = crate::Reg<s0_rxcount::S0RxcountSpec>;
|
||||||
#[doc = "Slave RX Count Register"]
|
#[doc = "Slave RX Count Register"]
|
||||||
pub mod s0_rxcount;
|
pub mod s0_rxcount;
|
||||||
#[doc = "S0_IRQ_ENB (rw) register accessor: Slave Interrupt Enable Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_irq_enb::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_irq_enb::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_irq_enb`]
|
#[doc = "S0_IRQ_ENB (rw) register accessor: Slave Interrupt Enable Register\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_irq_enb::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_irq_enb::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_irq_enb`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_IRQ_ENB")]
|
#[doc(alias = "S0_IRQ_ENB")]
|
||||||
pub type S0IrqEnb = crate::Reg<s0_irq_enb::S0IrqEnbSpec>;
|
pub type S0IrqEnb = crate::Reg<s0_irq_enb::S0IrqEnbSpec>;
|
||||||
@ -414,37 +414,37 @@ pub use s0_irq_enb as s0_irq_clr;
|
|||||||
pub use S0IrqEnb as S0IrqRaw;
|
pub use S0IrqEnb as S0IrqRaw;
|
||||||
pub use S0IrqEnb as S0IrqEnd;
|
pub use S0IrqEnb as S0IrqEnd;
|
||||||
pub use S0IrqEnb as S0IrqClr;
|
pub use S0IrqEnb as S0IrqClr;
|
||||||
#[doc = "S0_RXFIFOIRQTRG (rw) register accessor: Slave Rx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_rxfifoirqtrg::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_rxfifoirqtrg::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_rxfifoirqtrg`]
|
#[doc = "S0_RXFIFOIRQTRG (rw) register accessor: Slave Rx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_rxfifoirqtrg::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_rxfifoirqtrg::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_rxfifoirqtrg`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_RXFIFOIRQTRG")]
|
#[doc(alias = "S0_RXFIFOIRQTRG")]
|
||||||
pub type S0Rxfifoirqtrg = crate::Reg<s0_rxfifoirqtrg::S0RxfifoirqtrgSpec>;
|
pub type S0Rxfifoirqtrg = crate::Reg<s0_rxfifoirqtrg::S0RxfifoirqtrgSpec>;
|
||||||
#[doc = "Slave Rx FIFO IRQ Trigger Level"]
|
#[doc = "Slave Rx FIFO IRQ Trigger Level"]
|
||||||
pub mod s0_rxfifoirqtrg;
|
pub mod s0_rxfifoirqtrg;
|
||||||
#[doc = "S0_TXFIFOIRQTRG (rw) register accessor: Slave Tx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_txfifoirqtrg::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_txfifoirqtrg::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_txfifoirqtrg`]
|
#[doc = "S0_TXFIFOIRQTRG (rw) register accessor: Slave Tx FIFO IRQ Trigger Level\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_txfifoirqtrg::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_txfifoirqtrg::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_txfifoirqtrg`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_TXFIFOIRQTRG")]
|
#[doc(alias = "S0_TXFIFOIRQTRG")]
|
||||||
pub type S0Txfifoirqtrg = crate::Reg<s0_txfifoirqtrg::S0TxfifoirqtrgSpec>;
|
pub type S0Txfifoirqtrg = crate::Reg<s0_txfifoirqtrg::S0TxfifoirqtrgSpec>;
|
||||||
#[doc = "Slave Tx FIFO IRQ Trigger Level"]
|
#[doc = "Slave Tx FIFO IRQ Trigger Level"]
|
||||||
pub mod s0_txfifoirqtrg;
|
pub mod s0_txfifoirqtrg;
|
||||||
#[doc = "S0_FIFO_CLR (w) register accessor: Slave Clear FIFO Register\n\nYou can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_fifo_clr::W`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_fifo_clr`]
|
#[doc = "S0_FIFO_CLR (w) register accessor: Slave Clear FIFO Register\n\nYou can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_fifo_clr::W`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_fifo_clr`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_FIFO_CLR")]
|
#[doc(alias = "S0_FIFO_CLR")]
|
||||||
pub type S0FifoClr = crate::Reg<s0_fifo_clr::S0FifoClrSpec>;
|
pub type S0FifoClr = crate::Reg<s0_fifo_clr::S0FifoClrSpec>;
|
||||||
#[doc = "Slave Clear FIFO Register"]
|
#[doc = "Slave Clear FIFO Register"]
|
||||||
pub mod s0_fifo_clr;
|
pub mod s0_fifo_clr;
|
||||||
#[doc = "S0_ADDRESSB (rw) register accessor: Slave I2C Address B Value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_addressb::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_addressb::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_addressb`]
|
#[doc = "S0_ADDRESSB (rw) register accessor: Slave I2C Address B Value\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_addressb::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_addressb::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_addressb`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_ADDRESSB")]
|
#[doc(alias = "S0_ADDRESSB")]
|
||||||
pub type S0Addressb = crate::Reg<s0_addressb::S0AddressbSpec>;
|
pub type S0Addressb = crate::Reg<s0_addressb::S0AddressbSpec>;
|
||||||
#[doc = "Slave I2C Address B Value"]
|
#[doc = "Slave I2C Address B Value"]
|
||||||
pub mod s0_addressb;
|
pub mod s0_addressb;
|
||||||
#[doc = "S0_ADDRESSMASKB (rw) register accessor: Slave I2C Address B Mask value\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`s0_addressmaskb::R`]. You can [`reset`](crate::generic::Reg::reset), [`write`](crate::generic::Reg::write), [`write_with_zero`](crate::generic::Reg::write_with_zero) this register using [`s0_addressmaskb::W`]. You can also [`modify`](crate::generic::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_addressmaskb`]
|
#[doc = "S0_ADDRESSMASKB (rw) register accessor: Slave I2C Address B Mask value\n\nYou can [`read`](crate::Reg::read) this register and get [`s0_addressmaskb::R`]. You can [`reset`](crate::Reg::reset), [`write`](crate::Reg::write), [`write_with_zero`](crate::Reg::write_with_zero) this register using [`s0_addressmaskb::W`]. You can also [`modify`](crate::Reg::modify) this register. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@s0_addressmaskb`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "S0_ADDRESSMASKB")]
|
#[doc(alias = "S0_ADDRESSMASKB")]
|
||||||
pub type S0Addressmaskb = crate::Reg<s0_addressmaskb::S0AddressmaskbSpec>;
|
pub type S0Addressmaskb = crate::Reg<s0_addressmaskb::S0AddressmaskbSpec>;
|
||||||
#[doc = "Slave I2C Address B Mask value"]
|
#[doc = "Slave I2C Address B Mask value"]
|
||||||
pub mod s0_addressmaskb;
|
pub mod s0_addressmaskb;
|
||||||
#[doc = "PERID (r) register accessor: Peripheral ID Register\n\nYou can [`read`](crate::generic::Reg::read) this register and get [`perid::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@perid`]
|
#[doc = "PERID (r) register accessor: Peripheral ID Register\n\nYou can [`read`](crate::Reg::read) this register and get [`perid::R`]. See [API](https://docs.rs/svd2rust/#read--modify--write-api).\n\nFor information about available fields see [`mod@perid`]
|
||||||
module"]
|
module"]
|
||||||
#[doc(alias = "PERID")]
|
#[doc(alias = "PERID")]
|
||||||
pub type Perid = crate::Reg<perid::PeridSpec>;
|
pub type Perid = crate::Reg<perid::PeridSpec>;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user