mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-11-23 20:22:51 +01:00
Fix nrf monotonics (#852)
* Fix nrf::timer * Bootstrap nrf52840-blinky example * More work on nrf blinky example * Fix README * Add asserts for correct timer functionality * Add correctness check to other monotonics as well * Update Changelog * Fix potential timing issues * Fix race condition in nrf::rtc * Add changelog * Add rtc blinky example * Change rtc example to RC lf clock source * Add changelog to rtic-time * Add changelog * Attempt to fix CI * Update teensy4-blinky Cargo.lock
This commit is contained in:
parent
1622f6b953
commit
89160b7cb9
18 changed files with 1080 additions and 101 deletions
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
@ -164,6 +164,9 @@ jobs:
|
||||||
- name: Cache Dependencies
|
- name: Cache Dependencies
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
|
- name: Install flip-link
|
||||||
|
run: cargo install flip-link
|
||||||
|
|
||||||
- name: Check the examples
|
- name: Check the examples
|
||||||
run: cargo xtask usage-example-build
|
run: cargo xtask usage-example-build
|
||||||
|
|
||||||
|
|
18
examples/nrf52840_blinky/.cargo/config.toml
Normal file
18
examples/nrf52840_blinky/.cargo/config.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
|
runner = "probe-rs run --chip nRF52840_xxAA"
|
||||||
|
|
||||||
|
rustflags = [
|
||||||
|
"-C", "linker=flip-link",
|
||||||
|
"-C", "link-arg=-Tlink.x",
|
||||||
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
||||||
|
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||||
|
"-C", "link-arg=--nmagic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
||||||
|
|
||||||
|
[alias]
|
||||||
|
rb = "run --bin"
|
||||||
|
rrb = "run --release --bin"
|
1
examples/nrf52840_blinky/.gitignore
vendored
Normal file
1
examples/nrf52840_blinky/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
9
examples/nrf52840_blinky/.vscode/settings.json
vendored
Normal file
9
examples/nrf52840_blinky/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
// override the default setting (`cargo check --all-targets`) which produces the following error
|
||||||
|
// "can't find crate for `test`" when the default compilation target is a no_std target
|
||||||
|
// with these changes RA will call `cargo check --bins` on save
|
||||||
|
"rust-analyzer.checkOnSave.allTargets": false,
|
||||||
|
"rust-analyzer.checkOnSave.extraArgs": [
|
||||||
|
"--bins"
|
||||||
|
]
|
||||||
|
}
|
612
examples/nrf52840_blinky/Cargo.lock
generated
Normal file
612
examples/nrf52840_blinky/Cargo.lock
generated
Normal file
|
@ -0,0 +1,612 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-polyfill"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "az"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bare-metal"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
|
||||||
|
dependencies = [
|
||||||
|
"rustc_version",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bare-metal"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitfield"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cast"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cortex-m"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
|
||||||
|
dependencies = [
|
||||||
|
"bare-metal 0.2.5",
|
||||||
|
"bitfield",
|
||||||
|
"critical-section",
|
||||||
|
"embedded-hal 0.2.7",
|
||||||
|
"volatile-register",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cortex-m-rt"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m-rt-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cortex-m-rt-macros"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cortex-m-semihosting"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"defmt-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-macros"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e"
|
||||||
|
dependencies = [
|
||||||
|
"defmt-parser",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.39",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-parser"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-rtt"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "609923761264dd99ed9c7d209718cda4631c5fe84668e0f0960124cbb844c49f"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"defmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-dma"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
|
||||||
|
dependencies = [
|
||||||
|
"nb 0.1.3",
|
||||||
|
"void",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal"
|
||||||
|
version = "1.0.0-rc.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e57ec6ad0bc8eb967cf9c9f144177f5e8f2f6f02dad0b8b683f9f05f6b22def"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-storage"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "156d7a2fdd98ebbf9ae579cbceca3058cff946e13f8e17b90e3511db0508c723"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixed"
|
||||||
|
version = "1.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02c69ce7e7c0f17aa18fdd9d0de39727adb9c6281f2ad12f57cbe54ae6e76e7d"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"bytemuck",
|
||||||
|
"half",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fugit"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7"
|
||||||
|
dependencies = [
|
||||||
|
"defmt",
|
||||||
|
"gcd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gcd"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nb"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
||||||
|
dependencies = [
|
||||||
|
"nb 1.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nb"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nrf-hal-common"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd244c63d588066d75cffdcae8a03299fd5fb272e36bdde4a9f922f3e4bc6e4b"
|
||||||
|
dependencies = [
|
||||||
|
"cast",
|
||||||
|
"cfg-if",
|
||||||
|
"cortex-m",
|
||||||
|
"embedded-dma",
|
||||||
|
"embedded-hal 0.2.7",
|
||||||
|
"embedded-storage",
|
||||||
|
"fixed",
|
||||||
|
"nb 1.1.0",
|
||||||
|
"nrf-usbd",
|
||||||
|
"nrf52840-pac",
|
||||||
|
"rand_core",
|
||||||
|
"void",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nrf-usbd"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66b2907c0b3ec4d264981c1fc5a2321d51c463d5a63d386e573f00e84d5495e6"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m",
|
||||||
|
"critical-section",
|
||||||
|
"usb-device",
|
||||||
|
"vcell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nrf52840-blinky"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m",
|
||||||
|
"cortex-m-rt",
|
||||||
|
"cortex-m-semihosting",
|
||||||
|
"defmt",
|
||||||
|
"defmt-rtt",
|
||||||
|
"fugit",
|
||||||
|
"nrf52840-hal",
|
||||||
|
"panic-probe",
|
||||||
|
"rtic",
|
||||||
|
"rtic-monotonics",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nrf52840-hal"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b54757ec98f38331889d1d6c535edb5dd543c762751abfe507f2d644b30f6d4f"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-hal 0.2.7",
|
||||||
|
"nrf-hal-common",
|
||||||
|
"nrf52840-pac",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nrf52840-pac"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30713f36f1be02e5bc9abefa30eae4a1f943d810f199d4923d3ad062d1be1b3d"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m",
|
||||||
|
"cortex-m-rt",
|
||||||
|
"vcell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "panic-probe"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m",
|
||||||
|
"defmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.70"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtic"
|
||||||
|
version = "2.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-polyfill",
|
||||||
|
"bare-metal 1.0.0",
|
||||||
|
"cortex-m",
|
||||||
|
"critical-section",
|
||||||
|
"rtic-core",
|
||||||
|
"rtic-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtic-common"
|
||||||
|
version = "1.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtic-core"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtic-macros"
|
||||||
|
version = "2.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtic-monotonics"
|
||||||
|
version = "1.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-polyfill",
|
||||||
|
"cfg-if",
|
||||||
|
"cortex-m",
|
||||||
|
"critical-section",
|
||||||
|
"embedded-hal 1.0.0-rc.2",
|
||||||
|
"fugit",
|
||||||
|
"nrf52840-pac",
|
||||||
|
"rtic-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtic-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"futures-util",
|
||||||
|
"rtic-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
|
dependencies = [
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
|
dependencies = [
|
||||||
|
"semver-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver-parser"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.39",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "usb-device"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcell"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "void"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "volatile-register"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
|
||||||
|
dependencies = [
|
||||||
|
"vcell",
|
||||||
|
]
|
68
examples/nrf52840_blinky/Cargo.toml
Normal file
68
examples/nrf52840_blinky/Cargo.toml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
[package]
|
||||||
|
authors = ["Finomnis <finomnis@gmail.com>"]
|
||||||
|
name = "nrf52840-blinky"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
defmt = "0.3"
|
||||||
|
defmt-rtt = "0.4"
|
||||||
|
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||||
|
cortex-m-semihosting = "0.5.0"
|
||||||
|
nrf52840-hal = "0.16.0"
|
||||||
|
fugit = { version = "0.3.7", features = ["defmt"] }
|
||||||
|
|
||||||
|
[dependencies.rtic]
|
||||||
|
path = "../../rtic"
|
||||||
|
version = "2.0.1"
|
||||||
|
features = ["thumbv7-backend"]
|
||||||
|
|
||||||
|
[dependencies.rtic-monotonics]
|
||||||
|
path = "../../rtic-monotonics"
|
||||||
|
version = "1.4.0"
|
||||||
|
features = ["nrf52840"]
|
||||||
|
|
||||||
|
# cargo build/run
|
||||||
|
[profile.dev]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true # <-
|
||||||
|
incremental = false
|
||||||
|
opt-level = 'z' # <-
|
||||||
|
overflow-checks = true # <-
|
||||||
|
|
||||||
|
# cargo test
|
||||||
|
[profile.test]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true # <-
|
||||||
|
incremental = false
|
||||||
|
opt-level = 3 # <-
|
||||||
|
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 # <-
|
||||||
|
|
||||||
|
# cargo test --release
|
||||||
|
[profile.bench]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false # <-
|
||||||
|
incremental = false
|
||||||
|
lto = 'fat'
|
||||||
|
opt-level = 3 # <-
|
||||||
|
overflow-checks = false # <-
|
33
examples/nrf52840_blinky/README.md
Normal file
33
examples/nrf52840_blinky/README.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# `nrf52840_blinky`
|
||||||
|
|
||||||
|
An RTIC blinky example intended for [nrf52840-dongle].
|
||||||
|
|
||||||
|
[nrf52840-dongle]: https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dongle
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
#### 1. `flip-link`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cargo install flip-link
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. `probe-rs`:
|
||||||
|
|
||||||
|
``` console
|
||||||
|
$ # make sure to install v0.2.0 or later
|
||||||
|
$ cargo install probe-rs --features cli
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
The [nrf52840-dongle] needs to be connected to the computer via an SWD probe, like a [J-Link EDU Mini].
|
||||||
|
|
||||||
|
Then, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --release --bin blinky_timer
|
||||||
|
```
|
||||||
|
|
||||||
|
[J-Link EDU Mini]: https://www.segger.com/products/debug-probes/j-link/models/j-link-edu-mini/
|
69
examples/nrf52840_blinky/src/bin/blinky_rtc.rs
Normal file
69
examples/nrf52840_blinky/src/bin/blinky_rtc.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#![deny(unsafe_code)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
use nrf52840_blinky::hal;
|
||||||
|
|
||||||
|
#[rtic::app(device = hal::pac, dispatchers = [SWI0_EGU0])]
|
||||||
|
mod app {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use hal::gpio::{Level, Output, Pin, PushPull};
|
||||||
|
use hal::prelude::*;
|
||||||
|
|
||||||
|
use rtic_monotonics::nrf::rtc::Rtc0 as Mono;
|
||||||
|
use rtic_monotonics::nrf::rtc::*;
|
||||||
|
use rtic_monotonics::Monotonic;
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
led: Pin<Output<PushPull>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
|
// Configure low frequency clock
|
||||||
|
hal::clocks::Clocks::new(cx.device.CLOCK).start_lfclk();
|
||||||
|
|
||||||
|
// Initialize Monotonic
|
||||||
|
let token = rtic_monotonics::create_nrf_rtc0_monotonic_token!();
|
||||||
|
Mono::start(cx.device.RTC0, token);
|
||||||
|
|
||||||
|
// Setup LED
|
||||||
|
let port0 = hal::gpio::p0::Parts::new(cx.device.P0);
|
||||||
|
let led = port0.p0_06.into_push_pull_output(Level::Low).degrade();
|
||||||
|
|
||||||
|
// Schedule the blinking task
|
||||||
|
blink::spawn().ok();
|
||||||
|
|
||||||
|
(Shared {}, Local { led })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(local = [led])]
|
||||||
|
async fn blink(cx: blink::Context) {
|
||||||
|
let blink::LocalResources { led, .. } = cx.local;
|
||||||
|
|
||||||
|
let mut next_tick = Mono::now();
|
||||||
|
let mut blink_on = false;
|
||||||
|
loop {
|
||||||
|
let now = Mono::now();
|
||||||
|
let now_ms: fugit::SecsDurationU64 = now.duration_since_epoch().convert();
|
||||||
|
defmt::println!("Timer {} ({})", now_ms, now.ticks());
|
||||||
|
|
||||||
|
blink_on = !blink_on;
|
||||||
|
if blink_on {
|
||||||
|
led.set_high().unwrap();
|
||||||
|
} else {
|
||||||
|
led.set_low().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
next_tick += 1000.millis();
|
||||||
|
Mono::delay_until(next_tick).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
examples/nrf52840_blinky/src/bin/blinky_timer.rs
Normal file
66
examples/nrf52840_blinky/src/bin/blinky_timer.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#![deny(unsafe_code)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
use nrf52840_blinky::hal;
|
||||||
|
|
||||||
|
#[rtic::app(device = hal::pac, dispatchers = [SWI0_EGU0])]
|
||||||
|
mod app {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use hal::gpio::{Level, Output, Pin, PushPull};
|
||||||
|
use hal::prelude::*;
|
||||||
|
|
||||||
|
use rtic_monotonics::nrf::timer::Timer0 as Mono;
|
||||||
|
use rtic_monotonics::nrf::timer::*;
|
||||||
|
use rtic_monotonics::Monotonic;
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
led: Pin<Output<PushPull>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(cx: init::Context) -> (Shared, Local) {
|
||||||
|
// Initialize Monotonic
|
||||||
|
let token = rtic_monotonics::create_nrf_timer0_monotonic_token!();
|
||||||
|
Mono::start(cx.device.TIMER0, token);
|
||||||
|
|
||||||
|
// Setup LED
|
||||||
|
let port0 = hal::gpio::p0::Parts::new(cx.device.P0);
|
||||||
|
let led = port0.p0_06.into_push_pull_output(Level::Low).degrade();
|
||||||
|
|
||||||
|
// Schedule the blinking task
|
||||||
|
blink::spawn().ok();
|
||||||
|
|
||||||
|
(Shared {}, Local { led })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(local = [led])]
|
||||||
|
async fn blink(cx: blink::Context) {
|
||||||
|
let blink::LocalResources { led, .. } = cx.local;
|
||||||
|
|
||||||
|
let mut next_tick = Mono::now();
|
||||||
|
let mut blink_on = false;
|
||||||
|
loop {
|
||||||
|
let now = Mono::now();
|
||||||
|
let now_ms: fugit::SecsDurationU64 = now.duration_since_epoch().convert();
|
||||||
|
defmt::println!("Timer {} ({})", now_ms, now.ticks());
|
||||||
|
|
||||||
|
blink_on = !blink_on;
|
||||||
|
if blink_on {
|
||||||
|
led.set_high().unwrap();
|
||||||
|
} else {
|
||||||
|
led.set_low().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
next_tick += 1000.millis();
|
||||||
|
Mono::delay_until(next_tick).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
examples/nrf52840_blinky/src/lib.rs
Normal file
37
examples/nrf52840_blinky/src/lib.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use cortex_m_semihosting::debug;
|
||||||
|
|
||||||
|
use defmt_rtt as _; // global logger
|
||||||
|
|
||||||
|
pub use nrf52840_hal as hal; // 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);
|
||||||
|
}
|
||||||
|
}
|
10
examples/teensy4_blinky/Cargo.lock
generated
10
examples/teensy4_blinky/Cargo.lock
generated
|
@ -133,9 +133,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-hal"
|
name = "embedded-hal"
|
||||||
version = "1.0.0-rc.1"
|
version = "1.0.0-rc.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2894bc2f0457b8ca3d6b8ab8aad64d9337583672494013457f86c5a9146c0e22"
|
checksum = "3e57ec6ad0bc8eb967cf9c9f144177f5e8f2f6f02dad0b8b683f9f05f6b22def"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
|
@ -440,12 +440,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rtic-monotonics"
|
name = "rtic-monotonics"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-polyfill",
|
"atomic-polyfill",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"embedded-hal 1.0.0-rc.1",
|
"embedded-hal 1.0.0-rc.2",
|
||||||
"fugit",
|
"fugit",
|
||||||
"imxrt-ral",
|
"imxrt-ral",
|
||||||
"rtic-time",
|
"rtic-time",
|
||||||
|
@ -453,7 +453,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rtic-time"
|
name = "rtic-time"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"critical-section",
|
"critical-section",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
|
@ -7,6 +7,12 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Race condition in `nrf::timer`.
|
||||||
|
- Race condition in `nrf::rtc`.
|
||||||
|
- Add internal counter integrity check to all half-period based monotonics.
|
||||||
|
|
||||||
## v1.4.0 - 2023-12-04
|
## v1.4.0 - 2023-12-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -141,13 +141,15 @@ macro_rules! make_timer {
|
||||||
// so it gets combined with rollover interrupt
|
// so it gets combined with rollover interrupt
|
||||||
ral::write_reg!(ral::gpt, gpt, OCR[1], 0x0000_0000);
|
ral::write_reg!(ral::gpt, gpt, OCR[1], 0x0000_0000);
|
||||||
|
|
||||||
|
// Initialize timer queue
|
||||||
|
$tq.initialize(Self {});
|
||||||
|
|
||||||
// Enable the timer
|
// Enable the timer
|
||||||
ral::modify_reg!(ral::gpt, gpt, CR, EN: 1);
|
ral::modify_reg!(ral::gpt, gpt, CR, EN: 1);
|
||||||
ral::modify_reg!(ral::gpt, gpt, CR,
|
ral::modify_reg!(ral::gpt, gpt, CR,
|
||||||
ENMOD: 0, // Keep state when disabled
|
ENMOD: 0, // Keep state when disabled
|
||||||
);
|
);
|
||||||
|
|
||||||
$tq.initialize(Self {});
|
|
||||||
|
|
||||||
// SAFETY: We take full ownership of the peripheral and interrupt vector,
|
// SAFETY: We take full ownership of the peripheral and interrupt vector,
|
||||||
// plus we are not using any external shared resources so we won't impact
|
// plus we are not using any external shared resources so we won't impact
|
||||||
|
@ -244,13 +246,15 @@ macro_rules! make_timer {
|
||||||
let (rollover, half_rollover) = ral::read_reg!(ral::gpt, gpt, SR, ROV, OF1);
|
let (rollover, half_rollover) = ral::read_reg!(ral::gpt, gpt, SR, ROV, OF1);
|
||||||
|
|
||||||
if rollover != 0 {
|
if rollover != 0 {
|
||||||
$period.fetch_add(1, Ordering::Relaxed);
|
let prev = $period.fetch_add(1, Ordering::Relaxed);
|
||||||
ral::write_reg!(ral::gpt, gpt, SR, ROV: 1);
|
ral::write_reg!(ral::gpt, gpt, SR, ROV: 1);
|
||||||
|
assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if half_rollover != 0 {
|
if half_rollover != 0 {
|
||||||
$period.fetch_add(1, Ordering::Relaxed);
|
let prev = $period.fetch_add(1, Ordering::Relaxed);
|
||||||
ral::write_reg!(ral::gpt, gpt, SR, OF1: 1);
|
ral::write_reg!(ral::gpt, gpt, SR, OF1: 1);
|
||||||
|
assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ use crate::{Monotonic, TimeoutError, TimerQueue};
|
||||||
use atomic_polyfill::{AtomicU32, Ordering};
|
use atomic_polyfill::{AtomicU32, Ordering};
|
||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
pub use fugit::{self, ExtU64, ExtU64Ceil};
|
pub use fugit::{self, ExtU64, ExtU64Ceil};
|
||||||
|
use rtic_time::half_period_counter::calculate_now;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -92,6 +93,16 @@ macro_rules! create_nrf_rtc2_monotonic_token {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TimerValueU24(u32);
|
||||||
|
impl rtic_time::half_period_counter::TimerValue for TimerValueU24 {
|
||||||
|
const BITS: u32 = 24;
|
||||||
|
}
|
||||||
|
impl From<TimerValueU24> for u64 {
|
||||||
|
fn from(value: TimerValueU24) -> Self {
|
||||||
|
Self::from(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! make_rtc {
|
macro_rules! make_rtc {
|
||||||
($mono_name:ident, $rtc:ident, $overflow:ident, $tq:ident$(, doc: ($($doc:tt)*))?) => {
|
($mono_name:ident, $rtc:ident, $overflow:ident, $tq:ident$(, doc: ($($doc:tt)*))?) => {
|
||||||
/// Monotonic timer queue implementation.
|
/// Monotonic timer queue implementation.
|
||||||
|
@ -107,14 +118,50 @@ macro_rules! make_rtc {
|
||||||
/// Start the timer monotonic.
|
/// Start the timer monotonic.
|
||||||
pub fn start(rtc: $rtc, _interrupt_token: impl crate::InterruptToken<Self>) {
|
pub fn start(rtc: $rtc, _interrupt_token: impl crate::InterruptToken<Self>) {
|
||||||
unsafe { rtc.prescaler.write(|w| w.bits(0)) };
|
unsafe { rtc.prescaler.write(|w| w.bits(0)) };
|
||||||
rtc.intenset.write(|w| w.compare0().set().ovrflw().set());
|
|
||||||
rtc.evtenset.write(|w| w.compare0().set().ovrflw().set());
|
|
||||||
|
|
||||||
|
// Disable interrupts, as preparation
|
||||||
|
rtc.intenclr.write(|w| w
|
||||||
|
.compare0().clear()
|
||||||
|
.compare1().clear()
|
||||||
|
.ovrflw().clear()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configure compare registers
|
||||||
|
rtc.cc[0].write(|w| unsafe { w.bits(0) }); // Dynamic wakeup
|
||||||
|
rtc.cc[1].write(|w| unsafe { w.bits(0x80_0000) }); // Half-period
|
||||||
|
|
||||||
|
// Timing critical, make sure we don't get interrupted
|
||||||
|
critical_section::with(|_|{
|
||||||
|
// Reset the timer
|
||||||
rtc.tasks_clear.write(|w| unsafe { w.bits(1) });
|
rtc.tasks_clear.write(|w| unsafe { w.bits(1) });
|
||||||
rtc.tasks_start.write(|w| unsafe { w.bits(1) });
|
rtc.tasks_start.write(|w| unsafe { w.bits(1) });
|
||||||
|
|
||||||
|
// Clear pending events.
|
||||||
|
// Should be close enough to the timer reset that we don't miss any events.
|
||||||
|
rtc.events_ovrflw.write(|w| w);
|
||||||
|
rtc.events_compare[0].write(|w| w);
|
||||||
|
rtc.events_compare[1].write(|w| w);
|
||||||
|
|
||||||
|
// Make sure overflow counter is synced with the timer value
|
||||||
|
$overflow.store(0, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Initialized the timer queue
|
||||||
$tq.initialize(Self {});
|
$tq.initialize(Self {});
|
||||||
|
|
||||||
|
// Enable interrupts.
|
||||||
|
// Should be close enough to the timer reset that we don't miss any events.
|
||||||
|
rtc.intenset.write(|w| w
|
||||||
|
.compare0().set()
|
||||||
|
.compare1().set()
|
||||||
|
.ovrflw().set()
|
||||||
|
);
|
||||||
|
rtc.evtenset.write(|w| w
|
||||||
|
.compare0().set()
|
||||||
|
.compare1().set()
|
||||||
|
.ovrflw().set()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// SAFETY: We take full ownership of the peripheral and interrupt vector,
|
// SAFETY: We take full ownership of the peripheral and interrupt vector,
|
||||||
// plus we are not using any external shared resources so we won't impact
|
// plus we are not using any external shared resources so we won't impact
|
||||||
// basepri/source masking based critical sections.
|
// basepri/source masking based critical sections.
|
||||||
|
@ -130,12 +177,6 @@ macro_rules! make_rtc {
|
||||||
&$tq
|
&$tq
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn is_overflow() -> bool {
|
|
||||||
let rtc = unsafe { &*$rtc::PTR };
|
|
||||||
rtc.events_ovrflw.read().bits() == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Timeout at a specific time.
|
/// Timeout at a specific time.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn timeout_at<F: Future>(
|
pub async fn timeout_at<F: Future>(
|
||||||
|
@ -181,31 +222,24 @@ macro_rules! make_rtc {
|
||||||
type Duration = fugit::TimerDurationU64<32_768>;
|
type Duration = fugit::TimerDurationU64<32_768>;
|
||||||
|
|
||||||
fn now() -> Self::Instant {
|
fn now() -> Self::Instant {
|
||||||
// In a critical section to not get a race between overflow updates and reading it
|
|
||||||
// and the flag here.
|
|
||||||
critical_section::with(|_| {
|
|
||||||
let rtc = unsafe { &*$rtc::PTR };
|
let rtc = unsafe { &*$rtc::PTR };
|
||||||
let cnt = rtc.counter.read().bits();
|
Self::Instant::from_ticks(calculate_now(
|
||||||
// OVERFLOW HAPPENS HERE race needs to be handled
|
$overflow.load(Ordering::Relaxed),
|
||||||
let ovf = if Self::is_overflow() {
|
|| TimerValueU24(rtc.counter.read().bits())
|
||||||
$overflow.load(Ordering::Relaxed) + 1
|
))
|
||||||
} else {
|
|
||||||
$overflow.load(Ordering::Relaxed)
|
|
||||||
} as u64;
|
|
||||||
|
|
||||||
// Check and fix if above race happened
|
|
||||||
let new_cnt = rtc.counter.read().bits();
|
|
||||||
let cnt = if new_cnt >= cnt { cnt } else { new_cnt } as u64;
|
|
||||||
|
|
||||||
Self::Instant::from_ticks((ovf << 24) | cnt)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_interrupt() {
|
fn on_interrupt() {
|
||||||
let rtc = unsafe { &*$rtc::PTR };
|
let rtc = unsafe { &*$rtc::PTR };
|
||||||
if Self::is_overflow() {
|
if rtc.events_ovrflw.read().bits() == 1 {
|
||||||
$overflow.fetch_add(1, Ordering::SeqCst);
|
|
||||||
rtc.events_ovrflw.write(|w| unsafe { w.bits(0) });
|
rtc.events_ovrflw.write(|w| unsafe { w.bits(0) });
|
||||||
|
let prev = $overflow.fetch_add(1, Ordering::Relaxed);
|
||||||
|
assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!");
|
||||||
|
}
|
||||||
|
if rtc.events_compare[1].read().bits() == 1 {
|
||||||
|
rtc.events_compare[1].write(|w| unsafe { w.bits(0) });
|
||||||
|
let prev = $overflow.fetch_add(1, Ordering::Relaxed);
|
||||||
|
assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +255,7 @@ macro_rules! make_rtc {
|
||||||
|
|
||||||
fn set_compare(instant: Self::Instant) {
|
fn set_compare(instant: Self::Instant) {
|
||||||
let rtc = unsafe { &*$rtc::PTR };
|
let rtc = unsafe { &*$rtc::PTR };
|
||||||
unsafe { rtc.cc[0].write(|w| w.bits(instant.ticks() as u32 & 0xffffff)) };
|
unsafe { rtc.cc[0].write(|w| w.bits(instant.ticks() as u32 & 0xff_ffff)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_compare_flag() {
|
fn clear_compare_flag() {
|
||||||
|
|
|
@ -30,6 +30,7 @@ use crate::{Monotonic, TimeoutError, TimerQueue};
|
||||||
use atomic_polyfill::{AtomicU32, Ordering};
|
use atomic_polyfill::{AtomicU32, Ordering};
|
||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
pub use fugit::{self, ExtU64, ExtU64Ceil};
|
pub use fugit::{self, ExtU64, ExtU64Ceil};
|
||||||
|
use rtic_time::half_period_counter::calculate_now;
|
||||||
|
|
||||||
#[cfg(feature = "nrf52810")]
|
#[cfg(feature = "nrf52810")]
|
||||||
use nrf52810_pac::{self as pac, Interrupt, TIMER0, TIMER1, TIMER2};
|
use nrf52810_pac::{self as pac, Interrupt, TIMER0, TIMER1, TIMER2};
|
||||||
|
@ -139,17 +140,45 @@ macro_rules! make_timer {
|
||||||
// 1 MHz
|
// 1 MHz
|
||||||
timer.prescaler.write(|w| unsafe { w.prescaler().bits(4) });
|
timer.prescaler.write(|w| unsafe { w.prescaler().bits(4) });
|
||||||
timer.bitmode.write(|w| w.bitmode()._32bit());
|
timer.bitmode.write(|w| w.bitmode()._32bit());
|
||||||
timer
|
|
||||||
.intenset
|
// Disable interrupts, as preparation
|
||||||
.modify(|_, w| w.compare0().set().compare1().set());
|
timer.intenclr.modify(|_, w| w
|
||||||
timer.cc[1].write(|w| unsafe { w.cc().bits(0) }); // Overflow
|
.compare0().clear()
|
||||||
|
.compare1().clear()
|
||||||
|
.compare2().clear()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configure compare registers
|
||||||
|
timer.cc[0].write(|w| unsafe { w.cc().bits(0) }); // Dynamic wakeup
|
||||||
|
timer.cc[1].write(|w| unsafe { w.cc().bits(0x0000_0000) }); // Overflow
|
||||||
|
timer.cc[2].write(|w| unsafe { w.cc().bits(0x8000_0000) }); // Half-period
|
||||||
|
|
||||||
|
// Timing critical, make sure we don't get interrupted
|
||||||
|
critical_section::with(|_|{
|
||||||
|
// Reset the timer
|
||||||
timer.tasks_clear.write(|w| unsafe { w.bits(1) });
|
timer.tasks_clear.write(|w| unsafe { w.bits(1) });
|
||||||
timer.tasks_start.write(|w| unsafe { w.bits(1) });
|
timer.tasks_start.write(|w| unsafe { w.bits(1) });
|
||||||
|
|
||||||
$tq.initialize(Self {});
|
// Clear pending events.
|
||||||
|
// Should be close enough to the timer reset that we don't miss any events.
|
||||||
timer.events_compare[0].write(|w| w);
|
timer.events_compare[0].write(|w| w);
|
||||||
timer.events_compare[1].write(|w| w);
|
timer.events_compare[1].write(|w| w);
|
||||||
|
timer.events_compare[2].write(|w| w);
|
||||||
|
|
||||||
|
// Make sure overflow counter is synced with the timer value
|
||||||
|
$overflow.store(0, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Initialized the timer queue
|
||||||
|
$tq.initialize(Self {});
|
||||||
|
|
||||||
|
// Enable interrupts.
|
||||||
|
// Should be close enough to the timer reset that we don't miss any events.
|
||||||
|
timer.intenset.modify(|_, w| w
|
||||||
|
.compare0().set()
|
||||||
|
.compare1().set()
|
||||||
|
.compare2().set()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// SAFETY: We take full ownership of the peripheral and interrupt vector,
|
// SAFETY: We take full ownership of the peripheral and interrupt vector,
|
||||||
// plus we are not using any external shared resources so we won't impact
|
// plus we are not using any external shared resources so we won't impact
|
||||||
|
@ -166,12 +195,6 @@ macro_rules! make_timer {
|
||||||
&$tq
|
&$tq
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn is_overflow() -> bool {
|
|
||||||
let timer = unsafe { &*$timer::PTR };
|
|
||||||
timer.events_compare[1].read().bits() & 1 != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Timeout at a specific time.
|
/// Timeout at a specific time.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn timeout_at<F: Future>(
|
pub async fn timeout_at<F: Future>(
|
||||||
|
@ -216,44 +239,34 @@ macro_rules! make_timer {
|
||||||
type Duration = fugit::TimerDurationU64<1_000_000>;
|
type Duration = fugit::TimerDurationU64<1_000_000>;
|
||||||
|
|
||||||
fn now() -> Self::Instant {
|
fn now() -> Self::Instant {
|
||||||
// In a critical section to not get a race between overflow updates and reading it
|
|
||||||
// and the flag here.
|
|
||||||
critical_section::with(|_| {
|
|
||||||
let timer = unsafe { &*$timer::PTR };
|
let timer = unsafe { &*$timer::PTR };
|
||||||
timer.tasks_capture[2].write(|w| unsafe { w.bits(1) });
|
|
||||||
let cnt = timer.cc[2].read().bits();
|
|
||||||
|
|
||||||
let unhandled_overflow = if Self::is_overflow() {
|
Self::Instant::from_ticks(calculate_now(
|
||||||
// The overflow has not been handled yet, so add an extra to the read overflow.
|
$overflow.load(Ordering::Relaxed),
|
||||||
1
|
|| {
|
||||||
} else {
|
timer.tasks_capture[3].write(|w| unsafe { w.bits(1) });
|
||||||
0
|
timer.cc[3].read().bits()
|
||||||
};
|
}
|
||||||
|
))
|
||||||
timer.tasks_capture[2].write(|w| unsafe { w.bits(1) });
|
|
||||||
let new_cnt = timer.cc[2].read().bits();
|
|
||||||
let cnt = if new_cnt >= cnt { cnt } else { new_cnt } as u64;
|
|
||||||
|
|
||||||
Self::Instant::from_ticks(
|
|
||||||
(unhandled_overflow + $overflow.load(Ordering::Relaxed) as u64) << 32
|
|
||||||
| cnt as u64,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_interrupt() {
|
fn on_interrupt() {
|
||||||
let timer = unsafe { &*$timer::PTR };
|
let timer = unsafe { &*$timer::PTR };
|
||||||
|
|
||||||
// If there is a compare match on channel 1, it is an overflow
|
// If there is a compare match on channel 1, it is an overflow
|
||||||
if Self::is_overflow() {
|
if timer.events_compare[1].read().bits() & 1 != 0 {
|
||||||
timer.events_compare[1].write(|w| w);
|
timer.events_compare[1].write(|w| w);
|
||||||
$overflow.fetch_add(1, Ordering::SeqCst);
|
let prev = $overflow.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_timer() {}
|
// If there is a compare match on channel 2, it is a half-period overflow
|
||||||
|
if timer.events_compare[2].read().bits() & 1 != 0 {
|
||||||
fn disable_timer() {}
|
timer.events_compare[2].write(|w| w);
|
||||||
|
let prev = $overflow.fetch_add(1, Ordering::Relaxed);
|
||||||
|
assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn set_compare(instant: Self::Instant) {
|
fn set_compare(instant: Self::Instant) {
|
||||||
let timer = unsafe { &*$timer::PTR };
|
let timer = unsafe { &*$timer::PTR };
|
||||||
|
|
|
@ -272,12 +272,14 @@ macro_rules! make_timer {
|
||||||
// Full period
|
// Full period
|
||||||
if $timer.sr().read().uif() {
|
if $timer.sr().read().uif() {
|
||||||
$timer.sr().modify(|r| r.set_uif(false));
|
$timer.sr().modify(|r| r.set_uif(false));
|
||||||
$overflow.fetch_add(1, Ordering::Relaxed);
|
let prev = $overflow.fetch_add(1, Ordering::Relaxed);
|
||||||
|
assert!(prev % 2 == 1, "Monotonic must have missed an interrupt!");
|
||||||
}
|
}
|
||||||
// Half period
|
// Half period
|
||||||
if $timer.sr().read().ccif(2) {
|
if $timer.sr().read().ccif(2) {
|
||||||
$timer.sr().modify(|r| r.set_ccif(2, false));
|
$timer.sr().modify(|r| r.set_ccif(2, false));
|
||||||
$overflow.fetch_add(1, Ordering::Relaxed);
|
let prev = $overflow.fetch_add(1, Ordering::Relaxed);
|
||||||
|
assert!(prev % 2 == 0, "Monotonic must have missed an interrupt!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Docs: Add sanity check to `half_period_counter` code example
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
## v1.1.0 - 2023-12-04
|
## v1.1.0 - 2023-12-04
|
||||||
|
|
|
@ -96,11 +96,13 @@
|
||||||
//! fn on_interrupt() {
|
//! fn on_interrupt() {
|
||||||
//! if overflow_interrupt_happened() {
|
//! if overflow_interrupt_happened() {
|
||||||
//! clear_overflow_interrupt();
|
//! clear_overflow_interrupt();
|
||||||
//! HALF_PERIOD_COUNTER.fetch_add(1, Ordering::Relaxed);
|
//! let prev = HALF_PERIOD_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
//! assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!");
|
||||||
//! }
|
//! }
|
||||||
//! if compare_interrupt_happened() {
|
//! if compare_interrupt_happened() {
|
||||||
//! clear_compare_interrupt();
|
//! clear_compare_interrupt();
|
||||||
//! HALF_PERIOD_COUNTER.fetch_add(1, Ordering::Relaxed);
|
//! let prev = HALF_PERIOD_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
//! assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!");
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
|
|
Loading…
Reference in a new issue