Compare commits
494 Commits
usb-serial
...
embassy-ex
Author | SHA1 | Date | |
---|---|---|---|
611e82711a | |||
baef856206 | |||
858d520882 | |||
3b43b00867 | |||
95262ad559 | |||
43e2edfbda | |||
fb49e03eda | |||
c312884692 | |||
bc156afbb2 | |||
8a9622ec2e | |||
8d71fbd032 | |||
820852be28 | |||
fcb77f3f96 | |||
32fdd4c787 | |||
6a73ab1afa | |||
6fc5c608f8 | |||
b7114fb951 | |||
d375c46590 | |||
c40b944da6 | |||
e31545860e | |||
47b8e04b1c | |||
2ab9a07b64 | |||
5d5cd23715 | |||
77844e2055 | |||
28eb3b95b0 | |||
8b837fae07 | |||
d49f40dd5c | |||
477a90b8e3 | |||
a34331ae5f | |||
b555af1c5d | |||
ae608cf2fa | |||
28618d12a1 | |||
66c1712118 | |||
7b3d7a3826 | |||
d7031fbe92 | |||
02fcb07aa9 | |||
66faba2df7 | |||
766b5fc6f6 | |||
a2fd7108ff | |||
630372b183 | |||
3c5f011245 | |||
e80db42061 | |||
7e269f6f17 | |||
30358c435e | |||
55e07712e5 | |||
293edc5772 | |||
dc5acc687f | |||
a40daa923b | |||
9dfda46e0c | |||
4d60c715e6 | |||
2c96fe917d | |||
b42a7ebd8c | |||
9b1bdc3099 | |||
bcaef1de18 | |||
33778d3772 | |||
a6b8f3d994 | |||
b166ed6b78 | |||
54d31c98fe | |||
48eac0b146 | |||
0d8a9b1e7a | |||
ef3b1f46a9 | |||
5e4f65fe1f | |||
5fcebd28f4 | |||
a1fce1b554 | |||
bbc8424a5b | |||
036bc669cd | |||
26cc0e634d | |||
1b0f4ee653 | |||
f3ad0c6ade | |||
4da9743317 | |||
dca1777a2f | |||
4a9df60a7b | |||
2c6fcdbd3f | |||
e6d4043279 | |||
5c2ba3b212 | |||
ebc173ea75 | |||
b394cc3394 | |||
6caf627262 | |||
3d68d42132 | |||
bdd59b8988 | |||
9f55228be0 | |||
83ff3cbc69 | |||
4afdce4ec5 | |||
71fcea159f | |||
036e00113e | |||
958cace36d | |||
2568c714c8 | |||
83ab8e057a | |||
0ddabf0423 | |||
b4d0f24bf9 | |||
5a2f61a031 | |||
5b4c099afc | |||
c3357f884a | |||
eff2d71f28 | |||
42b21fd7ae | |||
027801db60 | |||
ad85beb677 | |||
780569c08a | |||
ffa0c08140 | |||
c38c85ef1f | |||
6c6bd11c1a | |||
4999b045df | |||
105aa8f452 | |||
d8f02e151b | |||
3aef5999d5 | |||
3c3a1d89b5 | |||
e8d3e86591 | |||
d6c5c1772c | |||
b65406791a | |||
f3237d7a2c | |||
56b21ad429 | |||
538cf2bc24 | |||
d8420ed5a0 | |||
04ed45941a | |||
55fb1d5126 | |||
4f791799a9 | |||
d2127f6b82 | |||
1d815f4ba0 | |||
aef93246b4 | |||
6b1d802caa | |||
6f30e92c7a | |||
39c1cc9f00 | |||
73057ee241 | |||
a8a491212b | |||
2f18770e27 | |||
087e649bc2 | |||
fd9b6487e1 | |||
603c4cb4fa | |||
8bed573b88 | |||
2a004251a7 | |||
8064f4bfe0 | |||
6256a6c57c | |||
bae31ebce7 | |||
e0ce7fcde7 | |||
a9f6e30bcd | |||
0d7b005252 | |||
fcbfd224a7 | |||
eb097b9d03 | |||
7ed9e29326 | |||
bdc4aa4a3b | |||
5bb5654d84 | |||
a8d3bcbb75 | |||
ec787d3518 | |||
c7c701b3e3 | |||
e495d606ec | |||
28136579e9 | |||
cc414e63d3 | |||
fd47445d75 | |||
d39404cdda | |||
29acc46501 | |||
cffb819e61 | |||
b344c843c4 | |||
e3cc0d168c | |||
cbc8871a0b | |||
e97b14c068 | |||
5a8704b4d8 | |||
6dd2fc5941 | |||
69c0a89aa5 | |||
937a63ce28 | |||
b57ba84da5 | |||
c3ba08ffb6 | |||
c52d1d11f9 | |||
d752a3f980 | |||
973b152375 | |||
3690af9bea | |||
f81ee103bf | |||
b124222649 | |||
8d8c642845 | |||
d5f9d17b7c | |||
036e6ae30c | |||
38b5d1ee2b | |||
146c744223 | |||
0ced8400d0 | |||
2e4f89068a | |||
f9dd751b6b | |||
6b6acc256d | |||
91338adc15 | |||
1d4e1092c4 | |||
0f1ff77fcc | |||
e947aa0153 | |||
44c8db2911 | |||
93864610ce | |||
bbd2563e13 | |||
a6543cef16 | |||
2815540167 | |||
8f1ea85938 | |||
3ee3f0e21c | |||
13acca624f | |||
c54ae73d49 | |||
858ddf6777 | |||
a5f2152077 | |||
a56ef685f3 | |||
62ab0bf2e7 | |||
77e34c5e8a | |||
270d1d59a0 | |||
3c41784de8 | |||
1425dda0a7 | |||
a60d92cfbb | |||
9f898c460f | |||
2a0fe73045 | |||
8d50f8a3d3 | |||
622fcb0e10 | |||
7fc138c91e | |||
5b32db7564 | |||
bd60f003e0 | |||
4883fdd154 | |||
18b9b6c780 | |||
fbcc587eca | |||
fbe30b2453 | |||
d42dff45de | |||
8e230bf6ec | |||
603ea9f310 | |||
a56b3e9a44 | |||
0378366e29 | |||
80ce6d1fb7 | |||
ba8e5d8589 | |||
192cdc2f85 | |||
64f8a779ca | |||
5693ed1178 | |||
19c6c698b5 | |||
224fbc8125 | |||
e5b4641f9e | |||
c83552eadc | |||
4db63677f6 | |||
f4d6a23f92 | |||
2cdd593290 | |||
c675208b8a | |||
e9445ec72d | |||
899a68325c | |||
c80c232a72 | |||
83ff626c47 | |||
809d3476aa | |||
4d1d125f41 | |||
4d6b3c57b1 | |||
a3d4ae85b0 | |||
02d57afd51 | |||
28254842db | |||
3382ca1a54 | |||
07a9a4ffd8 | |||
36ff688fab | |||
3df2c71e6c | |||
ca1d4179a7 | |||
890d113b85 | |||
7555a1e302 | |||
be7fbe50d7 | |||
2c01f277c2 | |||
c333d855fc | |||
42de1c3a06 | |||
a1d3bc30fa | |||
98576c17b6 | |||
27a3d2cd0b | |||
10f5966787 | |||
48957dce87 | |||
fc901f9856 | |||
13964c7fca | |||
a1cc3f2c60 | |||
15a6e04887 | |||
6bf4717b0a | |||
f581831b86 | |||
6f02403184 | |||
d040871f7a | |||
8f23b6faa6 | |||
1d2c47273d | |||
55ac480cb0 | |||
e4ad1aa542 | |||
6b5df4523a | |||
7b34f5e866 | |||
fe1e7c4d76 | |||
34217ea797 | |||
a0515ca7ac | |||
28b419d65e | |||
7c465465c1 | |||
e95a7dc555 | |||
582006c75c | |||
c7ec45a004 | |||
88d1976e81 | |||
cd592cb055 | |||
0b63af3313 | |||
25197308e3 | |||
cf278ea1b6 | |||
4db4200c37 | |||
758862f4b1 | |||
3705b4f40d | |||
1f63fdbb15 | |||
d11a94e2a7 | |||
d6dd5ea5d3 | |||
8a146a50ec | |||
17d5e1c470 | |||
975a780efe | |||
c3774607a5 | |||
bb24cfd1e8 | |||
48b37aa2bf | |||
0bde4992ea | |||
7ec7d1bbcc | |||
0628dd997f | |||
283ec756a9 | |||
5b076cb0dd | |||
3bae533066 | |||
7b36fe049d | |||
4b3fda4f96 | |||
56ca179475 | |||
460cdc9e0f | |||
f90b170dad | |||
68792bb918 | |||
3f0c8bafb0 | |||
588c0479f5 | |||
d979841f17 | |||
dff9bd9711 | |||
eccd2ecebf | |||
ed86fc175f | |||
132327a40d | |||
a615a70eda | |||
a2501bd5c1 | |||
ff2daaff67 | |||
d5a4457b5e | |||
6d402fe393 | |||
466a391b52 | |||
a93714327e | |||
029b156563 | |||
55a5e9b3a5 | |||
d8c7c3fc4b | |||
f192f44018 | |||
b81c14f442 | |||
f54e1cea90 | |||
fbddfcbfb7 | |||
67b14e6e7a | |||
6f4172fbc1 | |||
c6e2f4a90b | |||
91c1d17f16 | |||
29f3d5b68d | |||
4aca7c8811 | |||
8a811cfcf7 | |||
bf4493dbdf | |||
c1bf5aee24 | |||
735d676a72 | |||
37c103b5f3 | |||
05c524a7db | |||
758a2c528f | |||
69b4e898b3 | |||
b0da6318f3 | |||
972cdd4265 | |||
f9aebfce01 | |||
151557fec3 | |||
7d68ca1f3b | |||
4b63829110 | |||
e196387e69 | |||
f8d608093f | |||
ffeb40ff43 | |||
47305c2bf2 | |||
c421b7f5f0 | |||
d137286981 | |||
864202a23a | |||
a77fb0f630 | |||
a42ac86f1b | |||
c6cd69887c | |||
d1711036db | |||
8313b7315a | |||
d7ecf6f593 | |||
082147939d | |||
67c4d165c7 | |||
fb3e6a2b40 | |||
8ee2f50b8c | |||
46a4600952 | |||
27992d9c07 | |||
a0dc87d64e | |||
1255d8a8ce | |||
7d3eb6463a | |||
ab7fcf1d5b | |||
2c5146f19f | |||
3341b53eb4 | |||
70c05c62e4 | |||
eb57bb298f | |||
953c745ed8 | |||
ce73c29246 | |||
9c4df46c46 | |||
2a035a24a6 | |||
10c0174903 | |||
d26a247a32 | |||
a101d9078d | |||
b2f843a4ce | |||
40d25da793 | |||
b956d5d06c | |||
0845eab8a0 | |||
582c721aec | |||
0c4180cdd0 | |||
d9824dfd64 | |||
a088c4bee6 | |||
8359d8c020 | |||
1869fe02ba | |||
e3e8d82933 | |||
a96f30edf4 | |||
af15b49bfe | |||
60b2f075dc | |||
0c49e6747c | |||
99b4ea7c1d | |||
c9b9be5b81 | |||
2e6b813225 | |||
aceba1c03f | |||
8141d53d94 | |||
21a8653195 | |||
d372df7ddb | |||
6e13f5b387 | |||
c07854fed8 | |||
8c4997c5fc | |||
3252eaa060 | |||
348019e37f | |||
b9eb3dfad7 | |||
71513ccb39 | |||
cdb3fb059f | |||
93caf97a04 | |||
bca2c54948 | |||
81cbb0fc32 | |||
c69f2929c0 | |||
4d23ea554b | |||
d6fde756a8 | |||
ba43444292 | |||
6eac49186d | |||
2432cece38 | |||
fef338f5c2 | |||
24e186e684 | |||
4feabb13bf | |||
3f19879f41 | |||
e90f47aba3 | |||
2aa2b843ce | |||
fa2cda81db | |||
837d3bcdbb | |||
9f50f34547 | |||
f0b17675d8 | |||
6eb46c419c | |||
96f1525ffe | |||
01101e3df0 | |||
b95e5a4ea6 | |||
f7ec579c18 | |||
4f0aca481f | |||
ce889900d6 | |||
8a0a7c81b6 | |||
e892014b65 | |||
8cbe5b8e20 | |||
5666c56903 | |||
d5898c11eb | |||
daedfbbd87 | |||
bf7e24e9d7 | |||
02f367f733 | |||
f2e7a23148 | |||
27a89019ad | |||
2eb7a67c70 | |||
59f829c6cc | |||
91c31d5e43 | |||
9b5d7ec061 | |||
ed493be869 | |||
f5ca687e9b | |||
9c81d63155 | |||
60c54107ce | |||
56dd22f0ac | |||
afec1b439b | |||
219ef5b37a | |||
a2d1e7f02c | |||
5e6e18b310 | |||
80407aa930 | |||
a575e40a35 | |||
28fb492c40 | |||
e7bc84dda8 | |||
8cafaa1f3c | |||
df944edeef | |||
388d3e273d | |||
915f79c974 | |||
ea04a0277b | |||
71afa40a69 | |||
89fbb02979 | |||
76a334bd7c | |||
f47a148f51 | |||
5ecf9ec7bc | |||
78736328a0 | |||
8d0095c618 | |||
fdb3c3d6ff | |||
56ab6d9f14 | |||
88052480b1 | |||
218b102b28 | |||
fe7b72948a | |||
e0747e937f | |||
320e2cf35b | |||
f8ee33abb9 | |||
78a2ca8a0e | |||
f5d084552d | |||
d896f80405 | |||
2900ab79e7 | |||
14eae9ca06 | |||
64154fec8c | |||
ed97e61dbe | |||
029713eca0 | |||
3d26573c6b | |||
0963b5f92c | |||
530f192acc | |||
a46f33b214 |
41
.gitattributes
vendored
Normal file
41
.gitattributes
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
* text=auto
|
||||
|
||||
*.adoc text
|
||||
*.html text
|
||||
*.in text
|
||||
*.json text
|
||||
*.md text
|
||||
*.proto text
|
||||
*.py text
|
||||
*.rs text
|
||||
*.service text
|
||||
*.sh text
|
||||
*.toml text
|
||||
*.txt text
|
||||
*.x text
|
||||
*.yml text
|
||||
|
||||
*.raw binary
|
||||
*.bin binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.flv binary
|
||||
*.fla binary
|
||||
*.swf binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.pdf binary
|
||||
*.ez binary
|
||||
*.bz2 binary
|
||||
*.swp binary
|
17
.github/ci/crlf.sh
vendored
Executable file
17
.github/ci/crlf.sh
vendored
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
## on push branch~=gh-readonly-queue/main/.*
|
||||
## on pull_request
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FILES_WITH_CRLF=$(find ! -path "./.git/*" -not -type d | xargs file -N | (grep " CRLF " || true))
|
||||
|
||||
if [ -z "$FILES_WITH_CRLF" ]; then
|
||||
echo -e "No files with CRLF endings found."
|
||||
exit 0
|
||||
else
|
||||
NR_FILES=$(echo "$FILES_WITH_CRLF" | wc -l)
|
||||
echo -e "ERROR: Found ${NR_FILES} files with CRLF endings."
|
||||
echo "$FILES_WITH_CRLF"
|
||||
exit "$NR_FILES"
|
||||
fi
|
52
.github/ci/doc.sh
vendored
52
.github/ci/doc.sh
vendored
@ -6,7 +6,7 @@ set -euo pipefail
|
||||
export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
export BUILDER_THREADS=6
|
||||
export BUILDER_THREADS=4
|
||||
export BUILDER_COMPRESS=true
|
||||
|
||||
# force rustup to download the toolchain before starting building.
|
||||
@ -15,30 +15,32 @@ export BUILDER_COMPRESS=true
|
||||
# which makes rustup very sad
|
||||
rustc --version > /dev/null
|
||||
|
||||
docserver-builder -i ./embassy-stm32 -o crates/embassy-stm32/git.zup
|
||||
docserver-builder -i ./embassy-boot/boot -o crates/embassy-boot/git.zup
|
||||
docserver-builder -i ./embassy-boot/nrf -o crates/embassy-boot-nrf/git.zup
|
||||
docserver-builder -i ./embassy-boot/rp -o crates/embassy-boot-rp/git.zup
|
||||
docserver-builder -i ./embassy-boot/stm32 -o crates/embassy-boot-stm32/git.zup
|
||||
docserver-builder -i ./embassy-embedded-hal -o crates/embassy-embedded-hal/git.zup
|
||||
docserver-builder -i ./embassy-executor -o crates/embassy-executor/git.zup
|
||||
docserver-builder -i ./embassy-futures -o crates/embassy-futures/git.zup
|
||||
docserver-builder -i ./embassy-lora -o crates/embassy-lora/git.zup
|
||||
docserver-builder -i ./embassy-net -o crates/embassy-net/git.zup
|
||||
docserver-builder -i ./embassy-net-driver -o crates/embassy-net-driver/git.zup
|
||||
docserver-builder -i ./embassy-net-driver-channel -o crates/embassy-net-driver-channel/git.zup
|
||||
docserver-builder -i ./embassy-nrf -o crates/embassy-nrf/git.zup
|
||||
docserver-builder -i ./embassy-rp -o crates/embassy-rp/git.zup
|
||||
docserver-builder -i ./embassy-sync -o crates/embassy-sync/git.zup
|
||||
docserver-builder -i ./embassy-time -o crates/embassy-time/git.zup
|
||||
docserver-builder -i ./embassy-usb -o crates/embassy-usb/git.zup
|
||||
docserver-builder -i ./embassy-usb-driver -o crates/embassy-usb-driver/git.zup
|
||||
docserver-builder -i ./embassy-usb-logger -o crates/embassy-usb-logger/git.zup
|
||||
docserver-builder -i ./cyw43 -o crates/cyw43/git.zup
|
||||
docserver-builder -i ./cyw43-pio -o crates/cyw43-pio/git.zup
|
||||
docserver-builder -i ./embassy-net-w5500 -o crates/embassy-net-w5500/git.zup
|
||||
docserver-builder -i ./embassy-stm32-wpan -o crates/embassy-stm32-wpan/git.zup
|
||||
docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup
|
||||
docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup
|
||||
docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup
|
||||
docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup
|
||||
docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup
|
||||
docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup
|
||||
docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup
|
||||
docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup
|
||||
docserver-builder -i ./embassy-lora -o webroot/crates/embassy-lora/git.zup
|
||||
docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup
|
||||
docserver-builder -i ./embassy-net-driver -o webroot/crates/embassy-net-driver/git.zup
|
||||
docserver-builder -i ./embassy-net-driver-channel -o webroot/crates/embassy-net-driver-channel/git.zup
|
||||
docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup
|
||||
docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup
|
||||
docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup
|
||||
docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup
|
||||
docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup
|
||||
docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup
|
||||
docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup
|
||||
docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup
|
||||
docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup
|
||||
docserver-builder -i ./embassy-net-w5500 -o webroot/crates/embassy-net-w5500/git.zup
|
||||
docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup
|
||||
docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static
|
||||
|
||||
export KUBECONFIG=/ci/secrets/kubeconfig.yml
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp crates $POD:/data
|
||||
kubectl cp webroot/crates $POD:/data
|
||||
kubectl cp webroot/static $POD:/data
|
2
.github/ci/test.sh
vendored
2
.github/ci/test.sh
vendored
@ -13,7 +13,7 @@ hashtime save /ci/cache/filetime.json
|
||||
|
||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-hal-common/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue
|
||||
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
|
||||
|
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@ -6,16 +6,16 @@
|
||||
"rust-analyzer.check.allTargets": false,
|
||||
"rust-analyzer.check.noDefaultFeatures": true,
|
||||
"rust-analyzer.cargo.noDefaultFeatures": true,
|
||||
"rust-analyzer.cargo.target": "thumbv7em-none-eabi",
|
||||
"rust-analyzer.cargo.target": "thumbv7m-none-eabi",
|
||||
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
|
||||
"rust-analyzer.cargo.features": [
|
||||
"nightly",
|
||||
///"nightly",
|
||||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
// Declare for the target you wish to develop
|
||||
// "embassy-executor/Cargo.toml",
|
||||
// "embassy-sync/Cargo.toml",
|
||||
"examples/nrf52840/Cargo.toml",
|
||||
"examples/stm32wl/Cargo.toml",
|
||||
// "examples/nrf5340/Cargo.toml",
|
||||
// "examples/nrf-rtos-trace/Cargo.toml",
|
||||
// "examples/rp/Cargo.toml",
|
||||
@ -25,6 +25,7 @@
|
||||
// "examples/stm32f1/Cargo.toml",
|
||||
// "examples/stm32f2/Cargo.toml",
|
||||
// "examples/stm32f3/Cargo.toml",
|
||||
// "examples/stm32f334/Cargo.toml",
|
||||
// "examples/stm32f4/Cargo.toml",
|
||||
// "examples/stm32f7/Cargo.toml",
|
||||
// "examples/stm32g0/Cargo.toml",
|
||||
@ -40,4 +41,5 @@
|
||||
// "examples/stm32wl/Cargo.toml",
|
||||
// "examples/wasm/Cargo.toml",
|
||||
],
|
||||
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||
}
|
15
README.md
15
README.md
@ -33,6 +33,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
|
||||
|
||||
- **Bluetooth** -
|
||||
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
The <a href="https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan">embassy-stm32-wpan</a> crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers.
|
||||
|
||||
- **LoRa** -
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
|
||||
@ -99,10 +100,10 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer
|
||||
|
||||
### Running examples
|
||||
|
||||
- Install `probe-rs-cli` with defmt support.
|
||||
- Install `probe-rs`.
|
||||
|
||||
```bash
|
||||
cargo install probe-rs-cli
|
||||
cargo install probe-rs --features cli
|
||||
```
|
||||
|
||||
- Change directory to the sample's base directory. For example:
|
||||
@ -111,6 +112,12 @@ cargo install probe-rs-cli
|
||||
cd examples/nrf52840
|
||||
```
|
||||
|
||||
- Ensure `Cargo.toml` sets the right feature for the name of the chip you are programming.
|
||||
If this name is incorrect, the example may fail to run or immediately crash
|
||||
after being programmed.
|
||||
|
||||
- Ensure `.cargo/config.toml` contains the name of the chip you are programming.
|
||||
|
||||
- Run the example
|
||||
|
||||
For example:
|
||||
@ -119,6 +126,8 @@ For example:
|
||||
cargo run --release --bin blinky
|
||||
```
|
||||
|
||||
For more help getting started, see [Getting Started][1] and [Running the Examples][2].
|
||||
|
||||
## Developing Embassy with Rust Analyzer based editors
|
||||
|
||||
The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/)
|
||||
@ -151,3 +160,5 @@ This work is licensed under either of
|
||||
|
||||
at your option.
|
||||
|
||||
[1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started
|
||||
[2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples
|
||||
|
20
ci.sh
20
ci.sh
@ -5,10 +5,6 @@ set -euo pipefail
|
||||
export RUSTFLAGS=-Dwarnings
|
||||
export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
|
||||
|
||||
# needed by wifi examples
|
||||
export WIFI_NETWORK=x
|
||||
export WIFI_PASSWORD=x
|
||||
|
||||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||||
|
||||
BUILD_EXTRA=""
|
||||
@ -24,20 +20,19 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
|
||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154,nightly \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \
|
||||
@ -57,6 +52,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,qspi-as-gpio \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any \
|
||||
@ -90,6 +86,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32g474pe,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \
|
||||
@ -119,6 +116,7 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \
|
||||
--- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \
|
||||
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \
|
||||
--- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f334 \
|
||||
--- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \
|
||||
--- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \
|
||||
--- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \
|
||||
|
@ -16,9 +16,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \
|
||||
@ -36,6 +34,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,defmt \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,log \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features qspi-as-gpio \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g473cc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \
|
||||
|
Binary file not shown.
Binary file not shown.
@ -2,4 +2,8 @@
|
||||
|
||||
Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439
|
||||
|
||||
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
|
||||
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62
|
||||
|
@ -8,7 +8,6 @@ use cyw43::SpiBusCyw43;
|
||||
use embassy_rp::dma::Channel;
|
||||
use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate};
|
||||
use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
|
||||
use embassy_rp::relocate::RelocatedProgram;
|
||||
use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef};
|
||||
use fixed::FixedU32;
|
||||
use pio_proc::pio_asm;
|
||||
@ -88,8 +87,6 @@ where
|
||||
".wrap"
|
||||
);
|
||||
|
||||
let relocated = RelocatedProgram::new(&program.program);
|
||||
|
||||
let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);
|
||||
pin_io.set_pull(Pull::None);
|
||||
pin_io.set_schmitt(true);
|
||||
@ -102,7 +99,8 @@ where
|
||||
pin_clk.set_slew_rate(SlewRate::Fast);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&common.load_program(&relocated), &[&pin_clk]);
|
||||
let loaded_program = common.load_program(&program.program);
|
||||
cfg.use_program(&loaded_program, &[&pin_clk]);
|
||||
cfg.set_out_pins(&[&pin_io]);
|
||||
cfg.set_in_pins(&[&pin_io]);
|
||||
cfg.set_set_pins(&[&pin_io]);
|
||||
@ -142,7 +140,7 @@ where
|
||||
sm,
|
||||
irq,
|
||||
dma: dma.into_ref(),
|
||||
wrap_target: relocated.wrap().target,
|
||||
wrap_target: loaded_program.wrap.target,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ log = ["dep:log"]
|
||||
firmware-logs = []
|
||||
|
||||
[dependencies]
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time"}
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time"}
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
|
||||
@ -24,7 +24,7 @@ cortex-m = "0.7.6"
|
||||
cortex-m-rt = "0.7.0"
|
||||
futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
|
||||
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.11" }
|
||||
num_enum = { version = "0.5.7", default-features = false }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# cyw43
|
||||
|
||||
WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
|
||||
Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
|
||||
|
||||
## Current status
|
||||
|
||||
@ -19,18 +19,18 @@ Working:
|
||||
TODO:
|
||||
|
||||
- Setting a custom MAC address.
|
||||
- Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?)
|
||||
- Bus sleep (for power consumption optimization)
|
||||
|
||||
## Running the examples
|
||||
|
||||
- `cargo install probe-rs-cli`
|
||||
- `cd examples/rpi-pico-w`
|
||||
- `cargo install probe-rs --features cli`
|
||||
- `cd examples/rp`
|
||||
### Example 1: Scan the wifi stations
|
||||
- `cargo run --release --bin wifi_scan`
|
||||
### Example 2: Create an access point (IP and credentials in the code)
|
||||
- `cargo run --release --bin tcp_server_ap`
|
||||
- `cargo run --release --bin wifi_ap_tcp_server`
|
||||
### Example 3: Connect to an existing network and create a server
|
||||
- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release`
|
||||
- `cargo run --release --bin wifi_tcp_server`
|
||||
|
||||
After a few seconds, you should see that DHCP picks up an IP address like this
|
||||
```
|
||||
|
@ -216,7 +216,7 @@ where
|
||||
PWR: OutputPin,
|
||||
SPI: SpiBusCyw43,
|
||||
{
|
||||
let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
|
||||
let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
|
||||
let state_ch = ch_runner.state_runner();
|
||||
|
||||
let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events);
|
||||
|
@ -345,7 +345,9 @@ where
|
||||
}
|
||||
|
||||
fn rx(&mut self, packet: &mut [u8]) {
|
||||
let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return };
|
||||
let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.update_credit(&sdpcm_header);
|
||||
|
||||
@ -353,7 +355,9 @@ where
|
||||
|
||||
match channel {
|
||||
CHANNEL_TYPE_CONTROL => {
|
||||
let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; };
|
||||
let Some((cdc_header, response)) = CdcHeader::parse(payload) else {
|
||||
return;
|
||||
};
|
||||
trace!(" {:?}", cdc_header);
|
||||
|
||||
if cdc_header.id == self.ioctl_id {
|
||||
@ -417,8 +421,12 @@ where
|
||||
let status = event_packet.msg.status;
|
||||
let event_payload = match evt_type {
|
||||
Event::ESCAN_RESULT if status == EStatus::PARTIAL => {
|
||||
let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return };
|
||||
let Some(bss_info) = BssInfo::parse(bss_info) else { return };
|
||||
let Some((_, bss_info)) = ScanResults::parse(evt_data) else {
|
||||
return;
|
||||
};
|
||||
let Some(bss_info) = BssInfo::parse(bss_info) else {
|
||||
return;
|
||||
};
|
||||
events::Payload::BssInfo(*bss_info)
|
||||
}
|
||||
Event::ESCAN_RESULT => events::Payload::None,
|
||||
@ -439,7 +447,9 @@ where
|
||||
}
|
||||
}
|
||||
CHANNEL_TYPE_DATA => {
|
||||
let Some((_, packet)) = BdcHeader::parse(payload) else { return };
|
||||
let Some((_, packet)) = BdcHeader::parse(payload) else {
|
||||
return;
|
||||
};
|
||||
trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
|
||||
|
||||
match self.ch.try_rx_buf() {
|
||||
|
@ -10,9 +10,9 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAG
|
||||
|
||||
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||
/// 'mess up' the internal bootloader state
|
||||
pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
|
||||
pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
|
||||
dfu: DFU,
|
||||
state: STATE,
|
||||
state: FirmwareState<'d, STATE>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
@ -47,22 +47,12 @@ impl<'a, FLASH: NorFlash>
|
||||
}
|
||||
}
|
||||
|
||||
impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
|
||||
Self {
|
||||
dfu: config.dfu,
|
||||
state: config.state,
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||
async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
if self.get_state(aligned).await? == State::Boot {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
state: FirmwareState::new(config.state, aligned),
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,14 +61,8 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, aligned).await?;
|
||||
|
||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.get_state().await
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
@ -92,23 +76,16 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// If no signature feature is set then this method will always return a
|
||||
/// signature error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub async fn verify_and_mark_updated(
|
||||
&mut self,
|
||||
_public_key: &[u8],
|
||||
_signature: &[u8],
|
||||
_update_len: u32,
|
||||
_aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
|
||||
assert!(_update_len <= self.dfu.capacity() as u32);
|
||||
|
||||
self.verify_booted(_aligned).await?;
|
||||
self.state.verify_booted().await?;
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
@ -121,8 +98,9 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
@ -143,7 +121,8 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
@ -156,7 +135,7 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
r.map_err(into_signature_error)?
|
||||
}
|
||||
|
||||
self.set_magic(_aligned, SWAP_MAGIC).await
|
||||
self.state.mark_updated().await
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
@ -177,49 +156,14 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.set_magic(aligned, SWAP_MAGIC).await
|
||||
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_updated().await
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.set_magic(aligned, BOOT_MAGIC).await
|
||||
}
|
||||
|
||||
async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(0, aligned).await?;
|
||||
|
||||
if aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(STATE::WRITE_SIZE as u32, aligned).await?;
|
||||
|
||||
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, aligned).await?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32).await?;
|
||||
|
||||
// Set magic
|
||||
aligned.fill(magic);
|
||||
self.state.write(0, aligned).await?;
|
||||
}
|
||||
Ok(())
|
||||
pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_booted().await
|
||||
}
|
||||
|
||||
/// Write data to a flash page.
|
||||
@ -229,16 +173,10 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
pub async fn write_firmware(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
offset: usize,
|
||||
data: &[u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(data.len() >= DFU::ERASE_SIZE);
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
|
||||
self.verify_booted(aligned).await?;
|
||||
self.state.verify_booted().await?;
|
||||
|
||||
self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
|
||||
|
||||
@ -252,20 +190,94 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
||||
/// exchange for added complexity.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
pub async fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.verify_booted(aligned).await?;
|
||||
|
||||
pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
|
||||
self.state.verify_booted().await?;
|
||||
self.dfu.erase(0, self.dfu.capacity() as u32).await?;
|
||||
|
||||
Ok(&mut self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the state partition of the firmware update.
|
||||
///
|
||||
/// Can be used standalone for more fine grained control, or as part of the updater.
|
||||
pub struct FirmwareState<'d, STATE> {
|
||||
state: STATE,
|
||||
aligned: &'d mut [u8],
|
||||
}
|
||||
|
||||
impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
|
||||
/// Create a firmware state instance with a buffer for magic content and state partition.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
Self { state, aligned }
|
||||
}
|
||||
|
||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||
async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
if self.get_state().await? == State::Boot {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned).await?;
|
||||
|
||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(SWAP_MAGIC).await
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(BOOT_MAGIC).await
|
||||
}
|
||||
|
||||
async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned).await?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
self.aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32).await?;
|
||||
|
||||
// Set magic
|
||||
self.aligned.fill(magic);
|
||||
self.state.write(0, &self.aligned).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_embedded_hal::flash::partition::Partition;
|
||||
@ -288,8 +300,8 @@ mod tests {
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
|
||||
block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
|
||||
block_on(updater.write_firmware(0, to_write.as_slice())).unwrap();
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut hash = [0; 20];
|
||||
block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
|
||||
|
@ -10,9 +10,9 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAG
|
||||
|
||||
/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||
/// 'mess up' the internal bootloader state
|
||||
pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
|
||||
pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
|
||||
dfu: DFU,
|
||||
state: STATE,
|
||||
state: BlockingFirmwareState<'d, STATE>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
@ -49,22 +49,17 @@ impl<'a, FLASH: NorFlash>
|
||||
}
|
||||
}
|
||||
|
||||
impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
|
||||
Self {
|
||||
dfu: config.dfu,
|
||||
state: config.state,
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||
fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
if self.get_state(aligned)? == State::Boot {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
state: BlockingFirmwareState::new(config.state, aligned),
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,14 +68,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, aligned)?;
|
||||
|
||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.get_state()
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
@ -94,23 +83,16 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// If no signature feature is set then this method will always return a
|
||||
/// signature error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub fn verify_and_mark_updated(
|
||||
&mut self,
|
||||
_public_key: &[u8],
|
||||
_signature: &[u8],
|
||||
_update_len: u32,
|
||||
_aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
|
||||
assert!(_update_len <= self.dfu.capacity() as u32);
|
||||
|
||||
self.verify_booted(_aligned)?;
|
||||
self.state.verify_booted()?;
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
@ -124,7 +106,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
@ -145,7 +128,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
@ -158,7 +142,7 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
r.map_err(into_signature_error)?
|
||||
}
|
||||
|
||||
self.set_magic(_aligned, SWAP_MAGIC)
|
||||
self.state.mark_updated()
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
@ -179,49 +163,14 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.set_magic(aligned, SWAP_MAGIC)
|
||||
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_updated()
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.set_magic(aligned, BOOT_MAGIC)
|
||||
}
|
||||
|
||||
fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(0, aligned)?;
|
||||
|
||||
if aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(STATE::WRITE_SIZE as u32, aligned)?;
|
||||
|
||||
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, aligned)?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32)?;
|
||||
|
||||
// Set magic
|
||||
aligned.fill(magic);
|
||||
self.state.write(0, aligned)?;
|
||||
}
|
||||
Ok(())
|
||||
pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_booted()
|
||||
}
|
||||
|
||||
/// Write data to a flash page.
|
||||
@ -231,15 +180,9 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
pub fn write_firmware(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
offset: usize,
|
||||
data: &[u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(data.len() >= DFU::ERASE_SIZE);
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.verify_booted(aligned)?;
|
||||
self.state.verify_booted()?;
|
||||
|
||||
self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
|
||||
|
||||
@ -253,19 +196,94 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
||||
/// exchange for added complexity.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.verify_booted(aligned)?;
|
||||
pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
|
||||
self.state.verify_booted()?;
|
||||
self.dfu.erase(0, self.dfu.capacity() as u32)?;
|
||||
|
||||
Ok(&mut self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the state partition of the firmware update.
|
||||
///
|
||||
/// Can be used standalone for more fine grained control, or as part of the updater.
|
||||
pub struct BlockingFirmwareState<'d, STATE> {
|
||||
state: STATE,
|
||||
aligned: &'d mut [u8],
|
||||
}
|
||||
|
||||
impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
|
||||
/// Create a firmware state instance with a buffer for magic content and state partition.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
Self { state, aligned }
|
||||
}
|
||||
|
||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||
fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
if self.get_state()? == State::Boot {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned)?;
|
||||
|
||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(SWAP_MAGIC)
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(BOOT_MAGIC)
|
||||
}
|
||||
|
||||
fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned)?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
self.aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32)?;
|
||||
|
||||
// Set magic
|
||||
self.aligned.fill(magic);
|
||||
self.state.write(0, &self.aligned)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::cell::RefCell;
|
||||
@ -283,14 +301,14 @@ mod tests {
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
|
||||
let state = BlockingPartition::new(&flash, 0, 4096);
|
||||
let dfu = BlockingPartition::new(&flash, 65536, 65536);
|
||||
let mut aligned = [0; 8];
|
||||
|
||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
|
||||
let mut aligned = [0; 8];
|
||||
updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap();
|
||||
let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
|
||||
updater.write_firmware(0, to_write.as_slice()).unwrap();
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut hash = [0; 20];
|
||||
updater
|
||||
|
@ -3,8 +3,8 @@ mod asynch;
|
||||
mod blocking;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use asynch::FirmwareUpdater;
|
||||
pub use blocking::BlockingFirmwareUpdater;
|
||||
pub use asynch::{FirmwareState, FirmwareUpdater};
|
||||
pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater};
|
||||
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||
|
||||
/// Firmware updater flash configuration holding the two flashes used by the updater
|
||||
|
@ -16,9 +16,11 @@ mod test_flash;
|
||||
// TODO: Use the value provided by NorFlash when available
|
||||
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
|
||||
pub use firmware_updater::{
|
||||
BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError,
|
||||
};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use firmware_updater::FirmwareUpdater;
|
||||
pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
|
||||
pub use firmware_updater::{FirmwareState, FirmwareUpdater};
|
||||
|
||||
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
|
||||
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
|
||||
@ -118,15 +120,18 @@ mod tests {
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated(&mut aligned)).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated()).unwrap();
|
||||
|
||||
// Writing after marking updated is not allowed until marked as booted.
|
||||
let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE));
|
||||
let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE));
|
||||
assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
@ -158,11 +163,14 @@ mod tests {
|
||||
|
||||
// Mark as booted
|
||||
let flash = flash.into_async();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.mark_booted(&mut aligned)).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.mark_booted()).unwrap();
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
@ -190,12 +198,15 @@ mod tests {
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated(&mut aligned)).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated()).unwrap();
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
@ -232,12 +243,15 @@ mod tests {
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated(&mut aligned)).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated()).unwrap();
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
@ -293,18 +307,19 @@ mod tests {
|
||||
|
||||
// On with the test
|
||||
let flash = flash.into_async();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
|
||||
let mut aligned = [0; 4];
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
|
||||
assert!(block_on(updater.verify_and_mark_updated(
|
||||
&public_key.to_bytes(),
|
||||
&signature.to_bytes(),
|
||||
firmware_len as u32,
|
||||
&mut aligned,
|
||||
))
|
||||
.is_ok());
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ An adaptation of `embassy-boot` for nRF.
|
||||
|
||||
## Features
|
||||
|
||||
* Load applications with our without the softdevice.
|
||||
* Load applications with or without the softdevice.
|
||||
* Configure bootloader partitions based on linker script.
|
||||
* Using watchdog timer to detect application failure.
|
||||
|
||||
|
@ -3,10 +3,12 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{
|
||||
AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig,
|
||||
};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_boot::FirmwareUpdater;
|
||||
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig};
|
||||
use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE};
|
||||
pub use embassy_boot::{FirmwareState, FirmwareUpdater};
|
||||
use embassy_nrf::nvmc::PAGE_SIZE;
|
||||
use embassy_nrf::peripherals::WDT;
|
||||
use embassy_nrf::wdt;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
@ -104,15 +106,15 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
}
|
||||
}
|
||||
|
||||
/// A flash implementation that wraps NVMC and will pet a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<'d> {
|
||||
flash: Nvmc<'d>,
|
||||
/// A flash implementation that wraps any flash and will pet a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<FLASH> {
|
||||
flash: FLASH,
|
||||
wdt: wdt::WatchdogHandle,
|
||||
}
|
||||
|
||||
impl<'d> WatchdogFlash<'d> {
|
||||
impl<FLASH> WatchdogFlash<FLASH> {
|
||||
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
|
||||
pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self {
|
||||
pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self {
|
||||
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
@ -127,13 +129,13 @@ impl<'d> WatchdogFlash<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ErrorType for WatchdogFlash<'d> {
|
||||
type Error = <Nvmc<'d> as ErrorType>::Error;
|
||||
impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> {
|
||||
type Error = FLASH::Error;
|
||||
}
|
||||
|
||||
impl<'d> NorFlash for WatchdogFlash<'d> {
|
||||
const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE;
|
||||
impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> {
|
||||
const WRITE_SIZE: usize = FLASH::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = FLASH::ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
@ -145,8 +147,8 @@ impl<'d> NorFlash for WatchdogFlash<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ReadNorFlash for WatchdogFlash<'d> {
|
||||
const READ_SIZE: usize = <Nvmc<'d> as ReadNorFlash>::READ_SIZE;
|
||||
impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> {
|
||||
const READ_SIZE: usize = FLASH::READ_SIZE;
|
||||
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
self.flash.read(offset, data)
|
||||
|
@ -3,10 +3,12 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{
|
||||
AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State,
|
||||
};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_boot::FirmwareUpdater;
|
||||
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
|
||||
use embassy_rp::flash::{Flash, ERASE_SIZE};
|
||||
pub use embassy_boot::{FirmwareState, FirmwareUpdater};
|
||||
use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE};
|
||||
use embassy_rp::peripherals::{FLASH, WATCHDOG};
|
||||
use embassy_rp::watchdog::Watchdog;
|
||||
use embassy_time::Duration;
|
||||
@ -58,14 +60,14 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
|
||||
/// A flash implementation that will feed a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<'d, const SIZE: usize> {
|
||||
flash: Flash<'d, FLASH, SIZE>,
|
||||
flash: Flash<'d, FLASH, Blocking, SIZE>,
|
||||
watchdog: Watchdog,
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
|
||||
/// Start a new watchdog with a given flash and watchdog peripheral and a timeout
|
||||
pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self {
|
||||
let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash);
|
||||
let flash = Flash::<_, Blocking, SIZE>::new(flash);
|
||||
let mut watchdog = Watchdog::new(watchdog);
|
||||
watchdog.start(timeout);
|
||||
Self { flash, watchdog }
|
||||
@ -73,12 +75,12 @@ impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> {
|
||||
type Error = <Flash<'d, FLASH, SIZE> as ErrorType>::Error;
|
||||
type Error = <Flash<'d, FLASH, Blocking, SIZE> as ErrorType>::Error;
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
|
||||
const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_SIZE;
|
||||
const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.watchdog.feed();
|
||||
@ -91,7 +93,7 @@ impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> {
|
||||
const READ_SIZE: usize = <Flash<'d, FLASH, SIZE> as ReadNorFlash>::READ_SIZE;
|
||||
const READ_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as ReadNorFlash>::READ_SIZE;
|
||||
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.watchdog.feed();
|
||||
self.flash.read(offset, data)
|
||||
|
@ -3,9 +3,11 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{
|
||||
AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State,
|
||||
};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_boot::FirmwareUpdater;
|
||||
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
|
||||
pub use embassy_boot::{FirmwareState, FirmwareUpdater};
|
||||
use embedded_storage::nor_flash::NorFlash;
|
||||
|
||||
/// A bootloader for STM32 devices.
|
||||
|
@ -15,15 +15,18 @@ target = "x86_64-unknown-linux-gnu"
|
||||
std = []
|
||||
# Enable nightly-only features
|
||||
nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"]
|
||||
time = ["dep:embassy-time"]
|
||||
default = ["time"]
|
||||
|
||||
[dependencies]
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||
"unproven",
|
||||
] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
nb = "1.0.0"
|
||||
|
@ -74,7 +74,21 @@ where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
// Ensure we write the expected bytes
|
||||
for i in 0..core::cmp::min(read.len(), write.len()) {
|
||||
read[i] = write[i].clone();
|
||||
@ -83,38 +97,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBusFlush for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBusWrite<u8> for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBusRead<u8> for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -69,54 +69,39 @@ where
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBus<u8> for YieldingAsync<T>
|
||||
impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBus,
|
||||
{
|
||||
async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(read, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer_in_place(words).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBusFlush for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBusFlush,
|
||||
T: embedded_hal_async::spi::SpiBus<Word>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.wrapped.flush().await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBusWrite<u8> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBusWrite<u8>,
|
||||
{
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBusRead<u8> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBusRead<u8>,
|
||||
{
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(read, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer_in_place(words).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -56,62 +56,6 @@ where
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS, CS> spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::SpiBusRead,
|
||||
CS: OutputPin,
|
||||
{
|
||||
async fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res: Result<(), BUS::Error> = try {
|
||||
for buf in operations {
|
||||
bus.read(buf).await?;
|
||||
}
|
||||
};
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush().await;
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::SpiBusWrite,
|
||||
CS: OutputPin,
|
||||
{
|
||||
async fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res: Result<(), BUS::Error> = try {
|
||||
for buf in operations {
|
||||
bus.write(buf).await?;
|
||||
}
|
||||
};
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush().await;
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, BUS, CS> spi::SpiDevice for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
@ -129,6 +73,12 @@ where
|
||||
Operation::Write(buf) => bus.write(buf).await?,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await?,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?,
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayUs(us) => {
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -172,64 +122,6 @@ where
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::SpiBusWrite + SetConfig,
|
||||
CS: OutputPin,
|
||||
{
|
||||
async fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config);
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res: Result<(), BUS::Error> = try {
|
||||
for buf in operations {
|
||||
bus.write(buf).await?;
|
||||
}
|
||||
};
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush().await;
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, BUS, CS> spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::SpiBusRead + SetConfig,
|
||||
CS: OutputPin,
|
||||
{
|
||||
async fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config);
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res: Result<(), BUS::Error> = try {
|
||||
for buf in operations {
|
||||
bus.read(buf).await?;
|
||||
}
|
||||
};
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush().await;
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, BUS, CS> spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
@ -248,6 +140,12 @@ where
|
||||
Operation::Write(buf) => bus.write(buf).await?,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await?,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?,
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayUs(us) => {
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ use core::cell::RefCell;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
use embedded_hal_1::spi::{self, Operation, SpiBus, SpiBusRead, SpiBusWrite};
|
||||
use embedded_hal_1::spi::{self, Operation, SpiBus};
|
||||
|
||||
use crate::shared_bus::SpiDeviceError;
|
||||
use crate::SetConfig;
|
||||
@ -48,58 +48,6 @@ where
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: SpiBusRead,
|
||||
CS: OutputPin,
|
||||
{
|
||||
fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res = operations.iter_mut().try_for_each(|buf| bus.read(buf));
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceWrite for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: SpiBusWrite,
|
||||
CS: OutputPin,
|
||||
{
|
||||
fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res = operations.iter().try_for_each(|buf| bus.write(buf));
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
@ -116,6 +64,13 @@ where
|
||||
Operation::Write(buf) => bus.write(buf),
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write),
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayUs(us) => {
|
||||
embassy_time::block_for(embassy_time::Duration::from_micros(*us as _));
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
@ -199,58 +154,6 @@ where
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: SpiBusRead + SetConfig,
|
||||
CS: OutputPin,
|
||||
{
|
||||
fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config);
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res = operations.iter_mut().try_for_each(|buf| bus.read(buf));
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
Ok(op_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: SpiBusWrite + SetConfig,
|
||||
CS: OutputPin,
|
||||
{
|
||||
fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config);
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res = operations.iter().try_for_each(|buf| bus.write(buf));
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
Ok(op_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
@ -268,6 +171,13 @@ where
|
||||
Operation::Write(buf) => bus.write(buf),
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write),
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayUs(us) => {
|
||||
embassy_time::block_for(embassy_time::Duration::from_micros(*us as _));
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
|
@ -30,11 +30,14 @@ where
|
||||
/// Error returned by SPI device implementations in this crate.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum SpiDeviceError<BUS, CS> {
|
||||
/// An operation on the inner SPI bus failed.
|
||||
Spi(BUS),
|
||||
/// Setting the value of the Chip Select (CS) pin failed.
|
||||
Cs(CS),
|
||||
/// DelayUs operations are not supported when the `time` Cargo feature is not enabled.
|
||||
DelayUsNotSupported,
|
||||
}
|
||||
|
||||
impl<BUS, CS> spi::Error for SpiDeviceError<BUS, CS>
|
||||
@ -46,6 +49,7 @@ where
|
||||
match self {
|
||||
Self::Spi(e) => e.kind(),
|
||||
Self::Cs(_) => spi::ErrorKind::Other,
|
||||
Self::DelayUsNotSupported => spi::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.2.1 - 2023-08-10
|
||||
|
||||
- Avoid calling `pend()` when waking expired timers
|
||||
- Properly reset finished task state with `integrated-timers` enabled
|
||||
- Introduce `InterruptExecutor::spawner()`
|
||||
- Fix incorrect critical section in Xtensa executor
|
||||
|
||||
## 0.2.0 - 2023-04-27
|
||||
|
||||
- Replace unnecessary atomics in runqueue
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-executor"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "async/await executor designed for embedded usage"
|
||||
@ -61,8 +61,8 @@ log = { version = "0.4.14", optional = true }
|
||||
rtos-trace = { version = "0.1.2", optional = true }
|
||||
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true}
|
||||
embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true}
|
||||
atomic-polyfill = "1.0.1"
|
||||
critical-section = "1.1"
|
||||
static_cell = "1.1"
|
||||
|
@ -165,6 +165,9 @@ impl<F: Future + 'static> TaskStorage<F> {
|
||||
Poll::Ready(_) => {
|
||||
this.future.drop_in_place();
|
||||
this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
this.raw.expires_at.set(Instant::MAX);
|
||||
}
|
||||
Poll::Pending => {}
|
||||
}
|
||||
@ -406,7 +409,7 @@ impl SyncExecutor {
|
||||
#[allow(clippy::never_loop)]
|
||||
loop {
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task));
|
||||
self.timer_queue.dequeue_expired(Instant::now(), wake_task_no_pend);
|
||||
|
||||
self.run_queue.dequeue_all(|p| {
|
||||
let task = p.header();
|
||||
@ -570,6 +573,31 @@ pub fn wake_task(task: TaskRef) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake a task by `TaskRef` without calling pend.
|
||||
///
|
||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||
pub fn wake_task_no_pend(task: TaskRef) {
|
||||
let header = task.header();
|
||||
|
||||
let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
|
||||
// If already scheduled, or if not started,
|
||||
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
|
||||
None
|
||||
} else {
|
||||
// Mark it as scheduled
|
||||
Some(state | STATE_RUN_QUEUED)
|
||||
}
|
||||
});
|
||||
|
||||
if res.is_ok() {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
let executor = header.executor.get().unwrap_unchecked();
|
||||
executor.run_queue.enqueue(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
struct TimerQueue;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "embassy-hal-common"
|
||||
name = "embassy-hal-internal"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
16
embassy-hal-internal/README.md
Normal file
16
embassy-hal-internal/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# embassy-macros
|
||||
|
||||
An [Embassy](https://embassy.dev) project.
|
||||
|
||||
Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY. Embassy HALs (`embassy-nrf`, `embassy-stm32`, `embassy-rp`) already reexport
|
||||
everything you need to use them effectively.
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||
|
||||
at your option.
|
@ -1,5 +1,6 @@
|
||||
#![no_std]
|
||||
#![allow(clippy::new_without_default)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
@ -116,6 +116,7 @@ macro_rules! impl_peripheral {
|
||||
|
||||
#[inline]
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
#[allow(clippy::needless_update)]
|
||||
$type { ..*self }
|
||||
}
|
||||
}
|
@ -161,7 +161,7 @@ pub trait Peripheral: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T: Deref> Peripheral for T
|
||||
impl<'b, T: DerefMut> Peripheral for T
|
||||
where
|
||||
T::Target: Peripheral,
|
||||
{
|
@ -12,7 +12,7 @@ target = "thumbv7em-none-eabi"
|
||||
|
||||
[features]
|
||||
stm32wl = ["dep:embassy-stm32"]
|
||||
time = []
|
||||
time = ["embassy-time", "lorawan-device"]
|
||||
defmt = ["dep:defmt", "lorawan-device/defmt"]
|
||||
|
||||
[dependencies]
|
||||
@ -20,15 +20,12 @@ defmt = ["dep:defmt", "lorawan-device/defmt"]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1" }
|
||||
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false }
|
||||
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.2" }
|
||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||
bit_field = { version = "0.10" }
|
||||
|
||||
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
|
||||
lora-phy = { version = "1" }
|
||||
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
|
||||
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"], optional = true }
|
||||
|
@ -2,6 +2,14 @@
|
||||
name = "embassy-net-driver-channel"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack."
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/"
|
||||
@ -9,6 +17,9 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-d
|
||||
features = ["defmt"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["defmt"]
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
96
embassy-net-driver-channel/README.md
Normal file
96
embassy-net-driver-channel/README.md
Normal file
@ -0,0 +1,96 @@
|
||||
# embassy-net-driver-channel
|
||||
|
||||
This crate provides a toolkit for implementing [`embassy-net`](https://crates.io/crates/embassy-net) drivers in a
|
||||
higher level way than implementing the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) trait directly.
|
||||
|
||||
The `embassy-net-driver` trait is polling-based. To implement it, you must write the packet receive/transmit state machines by
|
||||
hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net`
|
||||
knows when to poll your driver again to make more progress.
|
||||
|
||||
With `embassy-net-driver-channel`
|
||||
|
||||
## A note about deadlocks
|
||||
|
||||
When implementing a driver using this crate, it might be tempting to write it in the most straightforward way:
|
||||
|
||||
```rust,ignore
|
||||
loop {
|
||||
// Wait for either..
|
||||
match select(
|
||||
// ... the chip signaling an interrupt, indicating a packet is available to receive, or
|
||||
irq_pin.wait_for_low(),
|
||||
// ... a TX buffer becoming available, i.e. embassy-net wants to send a packet
|
||||
tx_chan.tx_buf(),
|
||||
).await {
|
||||
Either::First(_) => {
|
||||
// a packet is ready to be received!
|
||||
let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue
|
||||
let n = receive_packet_over_spi(buf).await;
|
||||
rx_chan.rx_done(n);
|
||||
}
|
||||
Either::Second(buf) => {
|
||||
// a packet is ready to be sent!
|
||||
send_packet_over_spi(buf).await;
|
||||
tx_chan.tx_done();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, this code has a latent deadlock bug. The symptom is it can hang at `rx_chan.rx_buf().await` under load.
|
||||
|
||||
The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue.
|
||||
|
||||
The fix is to make sure to always service the TX queue while you're waiting for space to become available in the TX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available":
|
||||
|
||||
```rust,ignore
|
||||
loop {
|
||||
// Wait for either..
|
||||
match select(
|
||||
async {
|
||||
// ... the chip signaling an interrupt, indicating a packet is available to receive
|
||||
irq_pin.wait_for_low().await;
|
||||
// *AND* the buffer is ready...
|
||||
rx_chan.rx_buf().await
|
||||
},
|
||||
// ... or a TX buffer becoming available, i.e. embassy-net wants to send a packet
|
||||
tx_chan.tx_buf(),
|
||||
).await {
|
||||
Either::First(buf) => {
|
||||
// a packet is ready to be received!
|
||||
let n = receive_packet_over_spi(buf).await;
|
||||
rx_chan.rx_done(n);
|
||||
}
|
||||
Either::Second(buf) => {
|
||||
// a packet is ready to be sent!
|
||||
send_packet_over_spi(buf).await;
|
||||
tx_chan.tx_done();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
These `embassy-net` drivers are implemented using this crate. You can look at them for inspiration.
|
||||
|
||||
- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W
|
||||
- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support.
|
||||
- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip.
|
||||
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
|
||||
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
@ -1,4 +1,5 @@
|
||||
#![no_std]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
// must go first!
|
||||
mod fmt;
|
||||
@ -41,7 +42,7 @@ struct StateInner<'d, const MTU: usize> {
|
||||
struct Shared {
|
||||
link_state: LinkState,
|
||||
waker: WakerRegistration,
|
||||
ethernet_address: [u8; 6],
|
||||
hardware_address: driver::HardwareAddress,
|
||||
}
|
||||
|
||||
pub struct Runner<'d, const MTU: usize> {
|
||||
@ -84,10 +85,10 @@ impl<'d, const MTU: usize> Runner<'d, MTU> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_ethernet_address(&mut self, address: [u8; 6]) {
|
||||
pub fn set_hardware_address(&mut self, address: driver::HardwareAddress) {
|
||||
self.shared.lock(|s| {
|
||||
let s = &mut *s.borrow_mut();
|
||||
s.ethernet_address = address;
|
||||
s.hardware_address = address;
|
||||
s.waker.wake();
|
||||
});
|
||||
}
|
||||
@ -149,7 +150,15 @@ impl<'d> StateRunner<'d> {
|
||||
pub fn set_ethernet_address(&self, address: [u8; 6]) {
|
||||
self.shared.lock(|s| {
|
||||
let s = &mut *s.borrow_mut();
|
||||
s.ethernet_address = address;
|
||||
s.hardware_address = driver::HardwareAddress::Ethernet(address);
|
||||
s.waker.wake();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_ieee802154_address(&self, address: [u8; 8]) {
|
||||
self.shared.lock(|s| {
|
||||
let s = &mut *s.borrow_mut();
|
||||
s.hardware_address = driver::HardwareAddress::Ieee802154(address);
|
||||
s.waker.wake();
|
||||
});
|
||||
}
|
||||
@ -205,7 +214,7 @@ impl<'d, const MTU: usize> TxRunner<'d, MTU> {
|
||||
|
||||
pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||
state: &'d mut State<MTU, N_RX, N_TX>,
|
||||
ethernet_address: [u8; 6],
|
||||
hardware_address: driver::HardwareAddress,
|
||||
) -> (Runner<'d, MTU>, Device<'d, MTU>) {
|
||||
let mut caps = Capabilities::default();
|
||||
caps.max_transmission_unit = MTU;
|
||||
@ -221,7 +230,7 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||
tx: zerocopy_channel::Channel::new(&mut state.tx[..]),
|
||||
shared: Mutex::new(RefCell::new(Shared {
|
||||
link_state: LinkState::Down,
|
||||
ethernet_address,
|
||||
hardware_address,
|
||||
waker: WakerRegistration::new(),
|
||||
})),
|
||||
});
|
||||
@ -288,8 +297,8 @@ impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
|
||||
self.caps.clone()
|
||||
}
|
||||
|
||||
fn ethernet_address(&self) -> [u8; 6] {
|
||||
self.shared.lock(|s| s.borrow().ethernet_address)
|
||||
fn hardware_address(&self) -> driver::HardwareAddress {
|
||||
self.shared.lock(|s| s.borrow().hardware_address)
|
||||
}
|
||||
|
||||
fn link_state(&mut self, cx: &mut Context) -> LinkState {
|
||||
|
@ -3,7 +3,13 @@ name = "embassy-net-driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
description = "Driver trait for the `embassy-net` async TCP/IP network stack."
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/"
|
||||
@ -11,5 +17,8 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-d
|
||||
features = ["defmt"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["defmt"]
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
@ -1,5 +1,21 @@
|
||||
# embassy-net-driver
|
||||
|
||||
This crate contains the driver trait necessary for adding [`embassy-net`](https://crates.io/crates/embassy-net) support
|
||||
for a new hardware platform.
|
||||
|
||||
If you want to *use* `embassy-net` with already made drivers, you should depend on the main `embassy-net` crate, not on this crate.
|
||||
|
||||
If you are writing a driver, you should depend only on this crate, not on the main `embassy-net` crate.
|
||||
This will allow your driver to continue working for newer `embassy-net` major versions, without needing an update,
|
||||
if the driver trait has not had breaking changes.
|
||||
|
||||
See also [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel), which provides a higer-level API
|
||||
to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via
|
||||
packet queues for RX and TX.
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -4,6 +4,18 @@
|
||||
|
||||
use core::task::Context;
|
||||
|
||||
/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum HardwareAddress {
|
||||
/// A six-octet Ethernet address
|
||||
Ethernet([u8; 6]),
|
||||
/// An eight-octet IEEE802.15.4 address
|
||||
Ieee802154([u8; 8]),
|
||||
/// Indicates that a Driver is IP-native, and has no hardware address
|
||||
Ip,
|
||||
}
|
||||
|
||||
/// Main `embassy-net` driver API.
|
||||
///
|
||||
/// This is essentially an interface for sending and receiving raw network frames.
|
||||
@ -51,8 +63,8 @@ pub trait Driver {
|
||||
/// Get a description of device capabilities.
|
||||
fn capabilities(&self) -> Capabilities;
|
||||
|
||||
/// Get the device's Ethernet address.
|
||||
fn ethernet_address(&self) -> [u8; 6];
|
||||
/// Get the device's hardware address.
|
||||
fn hardware_address(&self) -> HardwareAddress;
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Driver> Driver for &mut T {
|
||||
@ -75,8 +87,8 @@ impl<T: ?Sized + Driver> Driver for &mut T {
|
||||
fn link_state(&mut self, cx: &mut Context) -> LinkState {
|
||||
T::link_state(self, cx)
|
||||
}
|
||||
fn ethernet_address(&self) -> [u8; 6] {
|
||||
T::ethernet_address(self)
|
||||
fn hardware_address(&self) -> HardwareAddress {
|
||||
T::hardware_address(self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,6 +176,9 @@ pub enum Medium {
|
||||
///
|
||||
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
|
||||
Ip,
|
||||
|
||||
/// IEEE 802_15_4 medium
|
||||
Ieee802154,
|
||||
}
|
||||
|
||||
impl Default for Medium {
|
||||
|
@ -7,14 +7,20 @@ edition = "2021"
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time" }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
embedded-hal = { version = "1.0.0-alpha.10" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1" }
|
||||
embedded-hal = { version = "1.0.0-alpha.11" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.2" }
|
||||
|
||||
noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] }
|
||||
#noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] }
|
||||
heapless = "0.7.16"
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/"
|
||||
target = "thumbv7em-none-eabi"
|
||||
features = ["defmt"]
|
@ -1,5 +1,4 @@
|
||||
use ch::driver::LinkState;
|
||||
use defmt::Debug2Format;
|
||||
use embassy_net_driver_channel as ch;
|
||||
use heapless::String;
|
||||
|
||||
@ -54,8 +53,9 @@ impl<'a> Control<'a> {
|
||||
})),
|
||||
};
|
||||
let resp = self.ioctl(req).await;
|
||||
let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
|
||||
debug!("======= {:?}", Debug2Format(&resp));
|
||||
let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else {
|
||||
panic!("unexpected resp")
|
||||
};
|
||||
assert_eq!(resp.resp, 0);
|
||||
self.state_ch.set_link_state(LinkState::Up);
|
||||
}
|
||||
@ -71,7 +71,9 @@ impl<'a> Control<'a> {
|
||||
)),
|
||||
};
|
||||
let resp = self.ioctl(req).await;
|
||||
let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
|
||||
let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else {
|
||||
panic!("unexpected resp")
|
||||
};
|
||||
assert_eq!(resp.resp, 0);
|
||||
|
||||
// WHY IS THIS A STRING? WHYYYY
|
||||
@ -100,7 +102,9 @@ impl<'a> Control<'a> {
|
||||
payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })),
|
||||
};
|
||||
let resp = self.ioctl(req).await;
|
||||
let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
|
||||
let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else {
|
||||
panic!("unexpected resp")
|
||||
};
|
||||
assert_eq!(resp.resp, 0);
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ where
|
||||
IN: InputPin + Wait,
|
||||
OUT: OutputPin,
|
||||
{
|
||||
let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
|
||||
let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
|
||||
let state_ch = ch_runner.state_runner();
|
||||
|
||||
let mut runner = Runner {
|
||||
@ -311,14 +311,14 @@ where
|
||||
fn handle_event(&self, data: &[u8]) {
|
||||
let Ok(event) = noproto::read::<CtrlMsg>(data) else {
|
||||
warn!("failed to parse event");
|
||||
return
|
||||
return;
|
||||
};
|
||||
|
||||
debug!("event: {:?}", &event);
|
||||
|
||||
let Some(payload) = &event.payload else {
|
||||
warn!("event without payload?");
|
||||
return
|
||||
return;
|
||||
};
|
||||
|
||||
match payload {
|
||||
|
19
embassy-net-tuntap/Cargo.toml
Normal file
19
embassy-net-tuntap/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "embassy-net-tuntap"
|
||||
version = "0.1.0"
|
||||
description = "embassy-net driver for Linux TUN/TAP interfaces."
|
||||
keywords = ["embedded", "tuntap", "embassy-net", "embedded-hal-async", "ethernet", "async"]
|
||||
categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
|
||||
async-io = "1.6.0"
|
||||
log = "0.4.14"
|
||||
libc = "0.2.101"
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-tuntap-v$VERSION/embassy-net-tuntap/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-tuntap/src/"
|
||||
target = "thumbv7em-none-eabi"
|
17
embassy-net-tuntap/README.md
Normal file
17
embassy-net-tuntap/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# `embassy-net` integration for Linux TUN/TAP interfaces.
|
||||
|
||||
[`embassy-net`](https://crates.io/crates/embassy-net) integration for for Linux TUN (IP medium) and TAP (Ethernet medium) interfaces.
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
@ -4,7 +4,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::task::Context;
|
||||
|
||||
use async_io::Async;
|
||||
use embassy_net_driver::{self, Capabilities, Driver, LinkState};
|
||||
use embassy_net_driver::{self, Capabilities, Driver, HardwareAddress, LinkState};
|
||||
use log::*;
|
||||
|
||||
pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
|
||||
@ -19,6 +19,7 @@ const ETHERNET_HEADER_LEN: usize = 14;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct ifreq {
|
||||
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
|
||||
ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
|
||||
@ -180,8 +181,8 @@ impl Driver for TunTapDevice {
|
||||
LinkState::Up
|
||||
}
|
||||
|
||||
fn ethernet_address(&self) -> [u8; 6] {
|
||||
[0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
|
||||
fn hardware_address(&self) -> HardwareAddress {
|
||||
HardwareAddress::Ethernet([0x02, 0x03, 0x04, 0x05, 0x06, 0x07])
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
embedded-hal = { version = "1.0.0-alpha.10" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1" }
|
||||
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
|
||||
embassy-time = { version = "0.1.0" }
|
||||
embassy-futures = { version = "0.1.0" }
|
||||
embedded-hal = { version = "1.0.0-alpha.11" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.2" }
|
||||
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip.
|
||||
#![no_std]
|
||||
/// [`embassy-net`](crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip.
|
||||
|
||||
mod device;
|
||||
mod socket;
|
||||
mod spi;
|
||||
@ -77,7 +78,7 @@ impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a driver for using the W5500 with [`embassy-net`](crates.io/crates/embassy-net).
|
||||
/// Obtain a driver for using the W5500 with [`embassy-net`](https://crates.io/crates/embassy-net).
|
||||
pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>(
|
||||
mac_addr: [u8; 6],
|
||||
state: &'a mut State<N_RX, N_TX>,
|
||||
@ -95,7 +96,7 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT:
|
||||
|
||||
let mac = W5500::new(spi_dev, mac_addr).await.unwrap();
|
||||
|
||||
let (runner, device) = ch::new(&mut state.ch_state, mac_addr);
|
||||
let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr));
|
||||
(
|
||||
device,
|
||||
Runner {
|
||||
|
@ -22,7 +22,11 @@ impl<SPI: SpiDevice> SpiInterface<SPI> {
|
||||
let address_phase = address.to_be_bytes();
|
||||
let control_phase = [(block as u8) << 3 | 0b0000_0100];
|
||||
let data_phase = data;
|
||||
let operations = &[&address_phase[..], &control_phase, &data_phase];
|
||||
self.0.write_transaction(operations).await
|
||||
let operations = &mut [
|
||||
Operation::Write(&address_phase[..]),
|
||||
Operation::Write(&control_phase),
|
||||
Operation::Write(&data_phase),
|
||||
];
|
||||
self.0.transaction(operations).await
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,22 @@ name = "embassy-net"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
description = "Async TCP/IP network stack for embedded systems"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/"
|
||||
features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"]
|
||||
features = ["nightly", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"]
|
||||
features = ["nightly", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@ -20,8 +26,7 @@ std = []
|
||||
|
||||
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"]
|
||||
|
||||
nightly = ["dep:embedded-io", "embedded-io?/async", "dep:embedded-nal-async"]
|
||||
unstable-traits = []
|
||||
nightly = ["dep:embedded-io-async", "dep:embedded-nal-async"]
|
||||
|
||||
udp = ["smoltcp/socket-udp"]
|
||||
tcp = ["smoltcp/socket-tcp"]
|
||||
@ -31,6 +36,7 @@ proto-ipv4 = ["smoltcp/proto-ipv4"]
|
||||
proto-ipv6 = ["smoltcp/proto-ipv6"]
|
||||
medium-ethernet = ["smoltcp/medium-ethernet"]
|
||||
medium-ip = ["smoltcp/medium-ip"]
|
||||
medium-ieee802154 = ["smoltcp/medium-ieee802154"]
|
||||
igmp = ["smoltcp/proto-igmp"]
|
||||
|
||||
[dependencies]
|
||||
@ -44,10 +50,9 @@ smoltcp = { version = "0.10.0", default-features = false, features = [
|
||||
] }
|
||||
|
||||
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
|
||||
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time" }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embedded-io = { version = "0.4.0", optional = true }
|
||||
embedded-io-async = { version = "0.5.0", optional = true }
|
||||
|
||||
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
|
||||
heapless = { version = "0.7.5", default-features = false }
|
||||
@ -56,5 +61,5 @@ generic-array = { version = "0.14.4", default-features = false }
|
||||
stable_deref_trait = { version = "1.2.0", default-features = false }
|
||||
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
|
||||
atomic-pool = "1.0"
|
||||
embedded-nal-async = { version = "0.4.0", optional = true }
|
||||
embedded-nal-async = { version = "0.5.0", optional = true }
|
||||
atomic-polyfill = { version = "1.0" }
|
||||
|
@ -1,30 +1,56 @@
|
||||
# embassy-net
|
||||
|
||||
embassy-net contains an async network API based on smoltcp and embassy, designed
|
||||
for embedded systems.
|
||||
`embassy-net` is a no-std no-alloc async network stack, designed for embedded systems.
|
||||
|
||||
## Running the example
|
||||
It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated
|
||||
API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and
|
||||
memory management designed to work well for embedded systems, aiiming for a more "Just Works" experience.
|
||||
|
||||
First, create the tap0 interface. You only need to do this once.
|
||||
## Features
|
||||
|
||||
```sh
|
||||
sudo ip tuntap add name tap0 mode tap user $USER
|
||||
sudo ip link set tap0 up
|
||||
sudo ip addr add 192.168.69.100/24 dev tap0
|
||||
sudo ip -6 addr add fe80::100/64 dev tap0
|
||||
sudo ip -6 addr add fdaa::100/64 dev tap0
|
||||
sudo ip -6 route add fe80::/64 dev tap0
|
||||
sudo ip -6 route add fdaa::/64 dev tap0
|
||||
```
|
||||
- IPv4, IPv6
|
||||
- Ethernet and bare-IP mediums.
|
||||
- TCP, UDP, DNS, DHCPv4, IGMPv4
|
||||
- TCP sockets implement the `embedded-io` async traits.
|
||||
|
||||
Second, have something listening there. For example `nc -l 8000`
|
||||
See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and
|
||||
unimplemented features of the network protocols.
|
||||
|
||||
Then run the example located in the `examples` folder:
|
||||
## Hardware support
|
||||
|
||||
```sh
|
||||
cd $EMBASSY_ROOT/examples/std/
|
||||
cargo run --bin net -- --static-ip
|
||||
```
|
||||
- [`esp-wifi`](https://github.com/esp-rs/esp-wifi) for WiFi support on bare-metal ESP32 chips. Maintained by Espressif.
|
||||
- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W
|
||||
- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support.
|
||||
- [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5).
|
||||
- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip.
|
||||
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
|
||||
|
||||
## Examples
|
||||
|
||||
- For usage with Embassy HALs and network chip drivers, search [here](https://github.com/embassy-rs/embassy/tree/main/examples) for `eth` or `wifi`.
|
||||
- The [`esp-wifi` repo](https://github.com/esp-rs/esp-wifi) has examples for use on bare-metal ESP32 chips.
|
||||
- For usage on `std` platforms, see [the `std` examples](https://github.com/embassy-rs/embassy/tree/main/examples/std/src/bin)
|
||||
|
||||
## Adding support for new hardware
|
||||
|
||||
To add `embassy-net` support for new hardware (i.e. a new Ethernet or WiFi chip, or
|
||||
an Ethernet/WiFi MCU peripheral), you have to implement the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver)
|
||||
traits.
|
||||
|
||||
Alternatively, [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel) provides a higer-level API
|
||||
to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via
|
||||
packet queues for RX and TX.
|
||||
|
||||
Drivers should depend only on `embassy-net-driver` or `embassy-net-driver-channel`. Never on the main `embassy-net` crate.
|
||||
This allows existing drivers to continue working for newer `embassy-net` major versions, without needing an update, if the driver
|
||||
trait has not had breaking changes.
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
[`embassy-time`](https://crates.io/crates/embassy-time) is used for timekeeping and timeouts. You must
|
||||
link an `embassy-time` driver in your project to use this crate.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -51,6 +51,8 @@ where
|
||||
Medium::Ethernet => phy::Medium::Ethernet,
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => phy::Medium::Ip,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => phy::Medium::Ieee802154,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!(
|
||||
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
|
||||
|
@ -68,7 +68,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
|
||||
where
|
||||
D: Driver + 'static,
|
||||
|
@ -24,17 +24,23 @@ use embassy_net_driver::{Driver, LinkState, Medium};
|
||||
use embassy_sync::waitqueue::WakerRegistration;
|
||||
use embassy_time::{Instant, Timer};
|
||||
use futures::pin_mut;
|
||||
#[allow(unused_imports)]
|
||||
use heapless::Vec;
|
||||
#[cfg(feature = "igmp")]
|
||||
pub use smoltcp::iface::MulticastError;
|
||||
#[allow(unused_imports)]
|
||||
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
use smoltcp::socket::dhcpv4::{self, RetryConfig};
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
pub use smoltcp::wire::EthernetAddress;
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))]
|
||||
pub use smoltcp::wire::HardwareAddress;
|
||||
#[cfg(feature = "udp")]
|
||||
pub use smoltcp::wire::IpListenEndpoint;
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
pub use smoltcp::wire::{EthernetAddress, HardwareAddress};
|
||||
pub use smoltcp::wire::{IpAddress, IpCidr};
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame};
|
||||
pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
@ -57,7 +63,7 @@ pub struct StackResources<const SOCK: usize> {
|
||||
|
||||
impl<const SOCK: usize> StackResources<SOCK> {
|
||||
/// Create a new set of stack resources.
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
#[cfg(feature = "dns")]
|
||||
const INIT: Option<dns::DnsQuery> = None;
|
||||
Self {
|
||||
@ -224,6 +230,20 @@ pub(crate) struct SocketStack {
|
||||
next_local_port: u16,
|
||||
}
|
||||
|
||||
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress {
|
||||
match addr {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
driver::HardwareAddress::Ethernet(eth) => HardwareAddress::Ethernet(EthernetAddress(eth)),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
driver::HardwareAddress::Ieee802154(ieee) => HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
|
||||
#[cfg(feature = "medium-ip")]
|
||||
driver::HardwareAddress::Ip => HardwareAddress::Ip,
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("Unsupported address {:?}. Make sure to enable medium-ethernet or medium-ieee802154 in embassy-net's Cargo features.", addr),
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Driver + 'static> Stack<D> {
|
||||
/// Create a new network stack.
|
||||
pub fn new<const SOCK: usize>(
|
||||
@ -232,21 +252,7 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
resources: &'static mut StackResources<SOCK>,
|
||||
random_seed: u64,
|
||||
) -> Self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = device.capabilities().medium;
|
||||
|
||||
let hardware_addr = match medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())),
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => HardwareAddress::Ip,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!(
|
||||
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
|
||||
medium
|
||||
),
|
||||
};
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(to_smoltcp_hardware_address(device.hardware_address()));
|
||||
iface_cfg.random_seed = random_seed;
|
||||
|
||||
let iface = Interface::new(
|
||||
@ -262,6 +268,7 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
|
||||
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
|
||||
|
||||
#[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
|
||||
let mut socket = SocketStack {
|
||||
sockets,
|
||||
iface,
|
||||
@ -269,6 +276,7 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
next_local_port,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
|
||||
let mut inner = Inner {
|
||||
device,
|
||||
link_up: false,
|
||||
@ -287,6 +295,9 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
dns_waker: WakerRegistration::new(),
|
||||
};
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
let _ = config;
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
match config.ipv4 {
|
||||
ConfigV4::Static(config) => {
|
||||
@ -323,9 +334,9 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
|
||||
}
|
||||
|
||||
/// Get the MAC address of the network interface.
|
||||
pub fn ethernet_address(&self) -> [u8; 6] {
|
||||
self.with(|_s, i| i.device.ethernet_address())
|
||||
/// Get the hardware address of the network interface.
|
||||
pub fn hardware_address(&self) -> HardwareAddress {
|
||||
self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()))
|
||||
}
|
||||
|
||||
/// Get whether the link is up.
|
||||
@ -419,7 +430,29 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
})
|
||||
.await?;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
#[must_use = "to delay the drop handler invocation to the end of the scope"]
|
||||
struct OnDrop<F: FnOnce()> {
|
||||
f: core::mem::MaybeUninit<F>,
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> OnDrop<F> {
|
||||
fn new(f: F) -> Self {
|
||||
Self {
|
||||
f: core::mem::MaybeUninit::new(f),
|
||||
}
|
||||
}
|
||||
|
||||
fn defuse(self) {
|
||||
core::mem::forget(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.f.as_ptr().read()() }
|
||||
}
|
||||
}
|
||||
|
||||
let drop = OnDrop::new(|| {
|
||||
self.with_mut(|s, i| {
|
||||
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
@ -457,30 +490,78 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "igmp")]
|
||||
impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> {
|
||||
impl<D: Driver + 'static> Stack<D> {
|
||||
/// Join a multicast group.
|
||||
pub fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await
|
||||
}
|
||||
|
||||
/// Join a multicast group.
|
||||
///
|
||||
/// When the send queue is full, this method will return `Poll::Pending`
|
||||
/// and register the current task to be notified when the queue has space available.
|
||||
pub fn poll_join_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
self.with_mut(|s, i| {
|
||||
s.iface
|
||||
.join_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now()))
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
};
|
||||
|
||||
match s
|
||||
.iface
|
||||
.join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
|
||||
{
|
||||
Ok(announce_sent) => Poll::Ready(Ok(announce_sent)),
|
||||
Err(MulticastError::Exhausted) => Poll::Pending,
|
||||
Err(other) => Poll::Ready(Err(other)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Leave a multicast group.
|
||||
pub fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
pub async fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await
|
||||
}
|
||||
|
||||
/// Leave a multicast group.
|
||||
///
|
||||
/// When the send queue is full, this method will return `Poll::Pending`
|
||||
/// and register the current task to be notified when the queue has space available.
|
||||
pub fn poll_leave_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
self.with_mut(|s, i| {
|
||||
s.iface
|
||||
.leave_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now()))
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
};
|
||||
|
||||
match s
|
||||
.iface
|
||||
.leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
|
||||
{
|
||||
Ok(leave_sent) => Poll::Ready(Ok(leave_sent)),
|
||||
Err(MulticastError::Exhausted) => Poll::Pending,
|
||||
Err(other) => Poll::Ready(Err(other)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -502,22 +583,26 @@ impl SocketStack {
|
||||
impl<D: Driver + 'static> Inner<D> {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
fn apply_config_v4(&mut self, s: &mut SocketStack, config: StaticConfigV4) {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = self.device.capabilities().medium;
|
||||
|
||||
debug!("Acquired IP configuration:");
|
||||
|
||||
debug!(" IP address: {}", config.address);
|
||||
s.iface.update_ip_addrs(|addrs| {
|
||||
if addrs.is_empty() {
|
||||
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
|
||||
} else {
|
||||
addrs[0] = IpCidr::Ipv4(config.address);
|
||||
if let Some((index, _)) = addrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
|
||||
{
|
||||
addrs.remove(index);
|
||||
}
|
||||
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
|
||||
});
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if medium == Medium::Ethernet {
|
||||
#[cfg(feature = "medium-ip")]
|
||||
let skip_gateway = self.device.capabilities().medium != Medium::Ip;
|
||||
#[cfg(not(feature = "medium-ip"))]
|
||||
let skip_gateway = false;
|
||||
|
||||
if !skip_gateway {
|
||||
if let Some(gateway) = config.gateway {
|
||||
debug!(" Default gateway: {}", gateway);
|
||||
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
|
||||
@ -548,11 +633,14 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
|
||||
debug!(" IP address: {}", config.address);
|
||||
s.iface.update_ip_addrs(|addrs| {
|
||||
if addrs.is_empty() {
|
||||
addrs.push(IpCidr::Ipv6(config.address)).unwrap();
|
||||
} else {
|
||||
addrs[0] = IpCidr::Ipv6(config.address);
|
||||
if let Some((index, _)) = addrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv6(_)))
|
||||
{
|
||||
addrs.remove(index);
|
||||
}
|
||||
addrs.push(IpCidr::Ipv6(config.address)).unwrap();
|
||||
});
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
@ -620,13 +708,21 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
socket.set_retry_config(config.retry_config);
|
||||
}
|
||||
|
||||
#[allow(unused)] // used only with dhcp
|
||||
fn unapply_config(&mut self, s: &mut SocketStack) {
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
fn unapply_config_v4(&mut self, s: &mut SocketStack) {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = self.device.capabilities().medium;
|
||||
|
||||
debug!("Lost IP configuration");
|
||||
s.iface.update_ip_addrs(|ip_addrs| ip_addrs.clear());
|
||||
s.iface.update_ip_addrs(|ip_addrs| {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
if let Some((index, _)) = ip_addrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
|
||||
{
|
||||
ip_addrs.remove(index);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if medium == Medium::Ethernet {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
@ -643,11 +739,12 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
|
||||
s.waker.register(cx.waker());
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if self.device.capabilities().medium == Medium::Ethernet {
|
||||
s.iface.set_hardware_addr(HardwareAddress::Ethernet(EthernetAddress(
|
||||
self.device.ethernet_address(),
|
||||
)));
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
if self.device.capabilities().medium == Medium::Ethernet
|
||||
|| self.device.capabilities().medium == Medium::Ieee802154
|
||||
{
|
||||
s.iface
|
||||
.set_hardware_addr(to_smoltcp_hardware_address(self.device.hardware_address()));
|
||||
}
|
||||
|
||||
let timestamp = instant_to_smoltcp(Instant::now());
|
||||
@ -673,7 +770,7 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
if self.link_up {
|
||||
match socket.poll() {
|
||||
None => {}
|
||||
Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
|
||||
Some(dhcpv4::Event::Deconfigured) => self.unapply_config_v4(s),
|
||||
Some(dhcpv4::Event::Configured(config)) => {
|
||||
let config = StaticConfigV4 {
|
||||
address: config.address,
|
||||
@ -685,7 +782,7 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
}
|
||||
} else if old_link_up {
|
||||
socket.reset();
|
||||
self.unapply_config(s);
|
||||
self.unapply_config_v4(s);
|
||||
}
|
||||
}
|
||||
//if old_link_up || self.link_up {
|
||||
|
@ -382,29 +382,29 @@ impl<'d> TcpIo<'d> {
|
||||
mod embedded_io_impls {
|
||||
use super::*;
|
||||
|
||||
impl embedded_io::Error for ConnectError {
|
||||
fn kind(&self) -> embedded_io::ErrorKind {
|
||||
embedded_io::ErrorKind::Other
|
||||
impl embedded_io_async::Error for ConnectError {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
embedded_io_async::ErrorKind::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::Error for Error {
|
||||
fn kind(&self) -> embedded_io::ErrorKind {
|
||||
embedded_io::ErrorKind::Other
|
||||
impl embedded_io_async::Error for Error {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
embedded_io_async::ErrorKind::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::Io for TcpSocket<'d> {
|
||||
impl<'d> embedded_io_async::ErrorType for TcpSocket<'d> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Read for TcpSocket<'d> {
|
||||
impl<'d> embedded_io_async::Read for TcpSocket<'d> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Write for TcpSocket<'d> {
|
||||
impl<'d> embedded_io_async::Write for TcpSocket<'d> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.io.write(buf).await
|
||||
}
|
||||
@ -414,21 +414,21 @@ mod embedded_io_impls {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::Io for TcpReader<'d> {
|
||||
impl<'d> embedded_io_async::ErrorType for TcpReader<'d> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Read for TcpReader<'d> {
|
||||
impl<'d> embedded_io_async::Read for TcpReader<'d> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::Io for TcpWriter<'d> {
|
||||
impl<'d> embedded_io_async::ErrorType for TcpWriter<'d> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Write for TcpWriter<'d> {
|
||||
impl<'d> embedded_io_async::Write for TcpWriter<'d> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.io.write(buf).await
|
||||
}
|
||||
@ -440,7 +440,7 @@ mod embedded_io_impls {
|
||||
}
|
||||
|
||||
/// TCP client compatible with `embedded-nal-async` traits.
|
||||
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod client {
|
||||
use core::cell::UnsafeCell;
|
||||
use core::mem::MaybeUninit;
|
||||
@ -527,13 +527,13 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::Io
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType
|
||||
for TcpConnection<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Read
|
||||
for TcpConnection<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
@ -541,7 +541,7 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Write
|
||||
for TcpConnection<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
|
@ -3,7 +3,7 @@
|
||||
use core::cell::RefCell;
|
||||
use core::future::poll_fn;
|
||||
use core::mem;
|
||||
use core::task::Poll;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_net_driver::Driver;
|
||||
use smoltcp::iface::{Interface, SocketHandle};
|
||||
@ -102,37 +102,61 @@ impl<'a> UdpSocket<'a> {
|
||||
///
|
||||
/// Returns the number of bytes received and the remote endpoint.
|
||||
pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> {
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| match s.recv_slice(buf) {
|
||||
Ok((n, meta)) => Poll::Ready(Ok((n, meta.endpoint))),
|
||||
// No data ready
|
||||
Err(udp::RecvError::Exhausted) => {
|
||||
s.register_recv_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
poll_fn(move |cx| self.poll_recv_from(buf, cx)).await
|
||||
}
|
||||
|
||||
/// Receive a datagram.
|
||||
///
|
||||
/// When no datagram is available, this method will return `Poll::Pending` and
|
||||
/// register the current task to be notified when a datagram is received.
|
||||
///
|
||||
/// When a datagram is received, this method will return `Poll::Ready` with the
|
||||
/// number of bytes received and the remote endpoint.
|
||||
pub fn poll_recv_from(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll<Result<(usize, IpEndpoint), Error>> {
|
||||
self.with_mut(|s, _| match s.recv_slice(buf) {
|
||||
Ok((n, meta)) => Poll::Ready(Ok((n, meta.endpoint))),
|
||||
// No data ready
|
||||
Err(udp::RecvError::Exhausted) => {
|
||||
s.register_recv_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send a datagram to the specified remote endpoint.
|
||||
///
|
||||
/// This method will wait until the datagram has been sent.
|
||||
///
|
||||
/// When the remote endpoint is not reachable, this method will return `Err(Error::NoRoute)`
|
||||
pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error>
|
||||
where
|
||||
T: Into<IpEndpoint>,
|
||||
{
|
||||
let remote_endpoint = remote_endpoint.into();
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
|
||||
// Entire datagram has been sent
|
||||
Ok(()) => Poll::Ready(Ok(())),
|
||||
Err(udp::SendError::BufferFull) => {
|
||||
s.register_send_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)),
|
||||
})
|
||||
let remote_endpoint: IpEndpoint = remote_endpoint.into();
|
||||
poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await
|
||||
}
|
||||
|
||||
/// Send a datagram to the specified remote endpoint.
|
||||
///
|
||||
/// When the datagram has been sent, this method will return `Poll::Ready(Ok())`.
|
||||
///
|
||||
/// When the socket's send buffer is full, this method will return `Poll::Pending`
|
||||
/// and register the current task to be notified when the buffer has space available.
|
||||
///
|
||||
/// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`.
|
||||
pub fn poll_send_to<T>(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll<Result<(), Error>>
|
||||
where
|
||||
T: Into<IpEndpoint>,
|
||||
{
|
||||
self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
|
||||
// Entire datagram has been sent
|
||||
Ok(()) => Poll::Ready(Ok(())),
|
||||
Err(udp::SendError::BufferFull) => {
|
||||
s.register_send_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns the local endpoint of the socket.
|
||||
|
@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-nrf-v$VERSION/embassy-nrf/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-nrf/src/"
|
||||
|
||||
features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "gpiote", "time-driver-rtc1"]
|
||||
features = ["nightly", "time", "defmt", "unstable-pac", "unstable-traits", "gpiote", "time-driver-rtc1"]
|
||||
flavors = [
|
||||
{ regex_feature = "nrf52.*", target = "thumbv7em-none-eabihf" },
|
||||
{ regex_feature = "nrf53.*", target = "thumbv8m.main-none-eabihf" },
|
||||
@ -32,10 +32,10 @@ rt = [
|
||||
|
||||
time = ["dep:embassy-time"]
|
||||
|
||||
defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embedded-io?/defmt", "embassy-embedded-hal/defmt"]
|
||||
defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embassy-embedded-hal/defmt"]
|
||||
|
||||
# Enable nightly-only features
|
||||
nightly = ["embedded-hal-1", "embedded-hal-async", "dep:embassy-usb-driver", "embedded-storage-async", "dep:embedded-io", "embassy-embedded-hal/nightly"]
|
||||
nightly = ["embedded-hal-1", "embedded-hal-async", "dep:embassy-usb-driver", "embedded-storage-async", "dep:embedded-io-async", "embassy-embedded-hal/nightly"]
|
||||
|
||||
# Reexport the PAC for the currently enabled chip at `embassy_nrf::pac`.
|
||||
# This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version.
|
||||
@ -91,22 +91,23 @@ _dppi = []
|
||||
_gpio-p1 = []
|
||||
|
||||
[dependencies]
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-3"] }
|
||||
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
|
||||
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
|
||||
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true }
|
||||
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
|
||||
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true}
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true}
|
||||
embedded-io = { version = "0.5.0" }
|
||||
embedded-io-async = { version = "0.5.0", optional = true }
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
cortex-m-rt = ">=0.6.15,<0.8"
|
||||
cortex-m = "0.7.6"
|
||||
futures = { version = "0.3.17", default-features = false }
|
||||
futures = { version = "0.3.17", default-features = false }
|
||||
critical-section = "1.1"
|
||||
rand_core = "0.6.3"
|
||||
fixed = "1.10.0"
|
||||
@ -114,13 +115,13 @@ embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
nrf52805-pac = { version = "0.12.0", optional = true }
|
||||
nrf52810-pac = { version = "0.12.0", optional = true }
|
||||
nrf52811-pac = { version = "0.12.0", optional = true }
|
||||
nrf52820-pac = { version = "0.12.0", optional = true }
|
||||
nrf52832-pac = { version = "0.12.0", optional = true }
|
||||
nrf52833-pac = { version = "0.12.0", optional = true }
|
||||
nrf52840-pac = { version = "0.12.0", optional = true }
|
||||
nrf52805-pac = { version = "0.12.0", optional = true }
|
||||
nrf52810-pac = { version = "0.12.0", optional = true }
|
||||
nrf52811-pac = { version = "0.12.0", optional = true }
|
||||
nrf52820-pac = { version = "0.12.0", optional = true }
|
||||
nrf52832-pac = { version = "0.12.0", optional = true }
|
||||
nrf52833-pac = { version = "0.12.0", optional = true }
|
||||
nrf52840-pac = { version = "0.12.0", optional = true }
|
||||
nrf5340-app-pac = { version = "0.12.0", optional = true }
|
||||
nrf5340-net-pac = { version = "0.12.0", optional = true }
|
||||
nrf9160-pac = { version = "0.12.0", optional = true }
|
||||
|
@ -15,8 +15,8 @@ use core::slice;
|
||||
use core::sync::atomic::{compiler_fence, AtomicU8, AtomicUsize, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::atomic_ring_buffer::RingBuffer;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::atomic_ring_buffer::RingBuffer;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
// Re-export SVD variants to allow user to directly set values
|
||||
pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
|
||||
@ -572,37 +572,37 @@ impl<'u, 'd, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'u, 'd, U, T> {
|
||||
mod _embedded_io {
|
||||
use super::*;
|
||||
|
||||
impl embedded_io::Error for Error {
|
||||
fn kind(&self) -> embedded_io::ErrorKind {
|
||||
impl embedded_io_async::Error for Error {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarte<'d, U, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> {
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteRx<'u, 'd, U, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> {
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteTx<'u, 'd, U, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarte<'d, U, T> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.inner_read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> {
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarteRx<'u, 'd, U, T> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.inner.inner_read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarte<'d, U, T> {
|
||||
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
||||
self.inner_fill_buf().await
|
||||
}
|
||||
@ -612,7 +612,7 @@ mod _embedded_io {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> {
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarteRx<'u, 'd, U, T> {
|
||||
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
||||
self.inner.inner_fill_buf().await
|
||||
}
|
||||
@ -622,7 +622,7 @@ mod _embedded_io {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarte<'d, U, T> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.inner_write(buf).await
|
||||
}
|
||||
@ -632,7 +632,7 @@ mod _embedded_io {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> {
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarteTx<'u, 'd, U, T> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.inner.inner_write(buf).await
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 192 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -208,7 +208,7 @@ impl_ppi_channel!(PPI_CH31, 31 => static);
|
||||
impl_saadc_input!(P0_04, ANALOG_INPUT2);
|
||||
impl_saadc_input!(P0_05, ANALOG_INPUT3);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 192 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -234,7 +234,7 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_30, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 192 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -236,7 +236,7 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_30, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 256 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 18;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -224,7 +224,7 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
|
||||
impl_ppi_channel!(PPI_CH30, 30 => static);
|
||||
impl_ppi_channel!(PPI_CH31, 31 => static);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -12,7 +12,7 @@ pub const FLASH_SIZE: usize = 512 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -263,7 +263,7 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
impl_i2s!(I2S, I2S, I2S);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 512 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 18;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -306,7 +306,7 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
impl_i2s!(I2S, I2S, I2S);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 1024 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 18;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -311,7 +311,7 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
impl_i2s!(I2S, I2S, I2S);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -218,7 +218,7 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub const FLASH_SIZE: usize = 1024 * 1024;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -506,7 +506,7 @@ impl_saadc_input!(P0_18, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_19, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_20, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
FPU,
|
||||
CACHE,
|
||||
SPU,
|
||||
|
@ -109,7 +109,7 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub const FLASH_SIZE: usize = 256 * 1024;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -342,7 +342,7 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable);
|
||||
impl_ppi_channel!(PPI_CH30, 30 => configurable);
|
||||
impl_ppi_channel!(PPI_CH31, 31 => configurable);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
CLOCK_POWER,
|
||||
RADIO,
|
||||
RNG,
|
||||
|
@ -169,7 +169,7 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub const FLASH_SIZE: usize = 1024 * 1024;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -368,7 +368,7 @@ impl_saadc_input!(P0_18, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_19, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_20, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
SPU,
|
||||
CLOCK_POWER,
|
||||
UARTE0_SPIM0_SPIS0_TWIM0_TWIS0,
|
||||
|
@ -5,7 +5,7 @@ use core::convert::Infallible;
|
||||
use core::hint::unreachable_unchecked;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef};
|
||||
|
||||
use self::sealed::Pin as _;
|
||||
use crate::pac::p0 as gpio;
|
||||
|
@ -4,7 +4,7 @@ use core::convert::Infallible;
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::gpio::sealed::Pin as _;
|
||||
@ -221,7 +221,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
|
||||
}
|
||||
|
||||
/// Returns the IN event, for use with PPI.
|
||||
pub fn event_in(&self) -> Event {
|
||||
pub fn event_in(&self) -> Event<'d> {
|
||||
let g = regs();
|
||||
Event::from_reg(&g.events_in[self.ch.number()])
|
||||
}
|
||||
@ -292,21 +292,21 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
|
||||
}
|
||||
|
||||
/// Returns the OUT task, for use with PPI.
|
||||
pub fn task_out(&self) -> Task {
|
||||
pub fn task_out(&self) -> Task<'d> {
|
||||
let g = regs();
|
||||
Task::from_reg(&g.tasks_out[self.ch.number()])
|
||||
}
|
||||
|
||||
/// Returns the CLR task, for use with PPI.
|
||||
#[cfg(not(feature = "nrf51"))]
|
||||
pub fn task_clr(&self) -> Task {
|
||||
pub fn task_clr(&self) -> Task<'d> {
|
||||
let g = regs();
|
||||
Task::from_reg(&g.tasks_clr[self.ch.number()])
|
||||
}
|
||||
|
||||
/// Returns the SET task, for use with PPI.
|
||||
#[cfg(not(feature = "nrf51"))]
|
||||
pub fn task_set(&self) -> Task {
|
||||
pub fn task_set(&self) -> Task<'d> {
|
||||
let g = regs();
|
||||
Task::from_reg(&g.tasks_set[self.ch.number()])
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ use core::ops::{Deref, DerefMut};
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
|
@ -98,7 +98,7 @@ mod chip;
|
||||
/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`)
|
||||
/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to
|
||||
/// prove at compile-time that the right interrupts have been bound.
|
||||
// developer note: this macro can't be in `embassy-hal-common` due to the use of `$crate`.
|
||||
// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`.
|
||||
#[macro_export]
|
||||
macro_rules! bind_interrupts {
|
||||
($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => {
|
||||
@ -127,7 +127,7 @@ pub use chip::pac;
|
||||
#[cfg(not(feature = "unstable-pac"))]
|
||||
pub(crate) use chip::pac;
|
||||
pub use chip::{peripherals, Peripherals, EASY_DMA_SIZE};
|
||||
pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
pub use crate::chip::interrupt;
|
||||
pub use crate::pac::NVIC_PRIO_BITS;
|
||||
@ -410,13 +410,13 @@ pub fn init(config: config::Config) -> Peripherals {
|
||||
warn!(
|
||||
"You have requested enabling chip reset functionality on the reset pin, by not enabling the Cargo feature `reset-pin-as-gpio`.\n\
|
||||
However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\
|
||||
To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`."
|
||||
To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`."
|
||||
);
|
||||
#[cfg(feature = "reset-pin-as-gpio")]
|
||||
warn!(
|
||||
"You have requested using the reset pin as GPIO, by enabling the Cargo feature `reset-pin-as-gpio`.\n\
|
||||
However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\
|
||||
To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`."
|
||||
To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`."
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -432,7 +432,7 @@ pub fn init(config: config::Config) -> Peripherals {
|
||||
warn!(
|
||||
"You have requested to use P0.09 and P0.10 pins for NFC, by not enabling the Cargo feature `nfc-pins-as-gpio`.\n\
|
||||
However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\
|
||||
To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`."
|
||||
To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use core::{ptr, slice};
|
||||
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embedded_storage::nor_flash::{
|
||||
ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash,
|
||||
};
|
||||
|
@ -1,294 +1,500 @@
|
||||
//! Pulse Density Modulation (PDM) mirophone driver.
|
||||
|
||||
#![macro_use]
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use futures::future::poll_fn;
|
||||
|
||||
use crate::chip::EASY_DMA_SIZE;
|
||||
use crate::gpio::sealed::Pin;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::{interrupt, Peripheral};
|
||||
|
||||
/// Interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
T::regs().intenclr.write(|w| w.end().clear());
|
||||
T::state().waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM microphone interface
|
||||
pub struct Pdm<'d, T: Instance> {
|
||||
_peri: PeripheralRef<'d, T>,
|
||||
}
|
||||
|
||||
/// PDM error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// Buffer is too long.
|
||||
BufferTooLong,
|
||||
/// Buffer is empty
|
||||
BufferZeroLength,
|
||||
/// PDM is not running
|
||||
NotRunning,
|
||||
}
|
||||
|
||||
static DUMMY_BUFFER: [i16; 1] = [0; 1];
|
||||
|
||||
impl<'d, T: Instance> Pdm<'d, T> {
|
||||
/// Create PDM driver
|
||||
pub fn new(
|
||||
pdm: impl Peripheral<P = T> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
clk: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
din: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(pdm, clk, din);
|
||||
Self::new_inner(pdm, clk.map_into(), din.map_into(), config)
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
pdm: PeripheralRef<'d, T>,
|
||||
clk: PeripheralRef<'d, AnyPin>,
|
||||
din: PeripheralRef<'d, AnyPin>,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(pdm);
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
// setup gpio pins
|
||||
din.conf().write(|w| w.input().set_bit());
|
||||
r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) });
|
||||
clk.set_low();
|
||||
clk.conf().write(|w| w.dir().output());
|
||||
r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) });
|
||||
|
||||
// configure
|
||||
// use default for
|
||||
// - gain right
|
||||
// - gain left
|
||||
// - clk
|
||||
// - ratio
|
||||
r.mode.write(|w| {
|
||||
w.edge().bit(config.edge == Edge::LeftRising);
|
||||
w.operation().bit(config.operation_mode == OperationMode::Mono);
|
||||
w
|
||||
});
|
||||
r.gainl.write(|w| w.gainl().default_gain());
|
||||
r.gainr.write(|w| w.gainr().default_gain());
|
||||
|
||||
// IRQ
|
||||
T::Interrupt::unpend();
|
||||
unsafe { T::Interrupt::enable() };
|
||||
|
||||
r.enable.write(|w| w.enable().set_bit());
|
||||
|
||||
Self { _peri: pdm }
|
||||
}
|
||||
|
||||
/// Start sampling microphon data into a dummy buffer
|
||||
/// Usefull to start the microphon and keep it active between recording samples
|
||||
pub async fn start(&mut self) {
|
||||
let r = T::regs();
|
||||
|
||||
// start dummy sampling because microphon needs some setup time
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
|
||||
r.sample
|
||||
.maxcnt
|
||||
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
|
||||
|
||||
r.tasks_start.write(|w| unsafe { w.bits(1) });
|
||||
}
|
||||
|
||||
/// Stop sampling microphon data inta a dummy buffer
|
||||
pub async fn stop(&mut self) {
|
||||
let r = T::regs();
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
r.events_started.reset();
|
||||
}
|
||||
|
||||
/// Sample data into the given buffer.
|
||||
pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
|
||||
if buffer.len() == 0 {
|
||||
return Err(Error::BufferZeroLength);
|
||||
}
|
||||
if buffer.len() > EASY_DMA_SIZE {
|
||||
return Err(Error::BufferTooLong);
|
||||
}
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
if r.events_started.read().bits() == 0 {
|
||||
return Err(Error::NotRunning);
|
||||
}
|
||||
|
||||
let drop = OnDrop::new(move || {
|
||||
r.intenclr.write(|w| w.end().clear());
|
||||
r.events_stopped.reset();
|
||||
|
||||
// reset to dummy buffer
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
|
||||
r.sample
|
||||
.maxcnt
|
||||
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
|
||||
|
||||
while r.events_stopped.read().bits() == 0 {}
|
||||
});
|
||||
|
||||
// setup user buffer
|
||||
let ptr = buffer.as_ptr();
|
||||
let len = buffer.len();
|
||||
r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) });
|
||||
r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) });
|
||||
|
||||
// wait till the current sample is finished and the user buffer sample is started
|
||||
Self::wait_for_sample().await;
|
||||
|
||||
// reset the buffer back to the dummy buffer
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
|
||||
r.sample
|
||||
.maxcnt
|
||||
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
|
||||
|
||||
// wait till the user buffer is sampled
|
||||
Self::wait_for_sample().await;
|
||||
|
||||
drop.defuse();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_sample() {
|
||||
let r = T::regs();
|
||||
|
||||
r.events_end.reset();
|
||||
r.intenset.write(|w| w.end().set());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
poll_fn(|cx| {
|
||||
T::state().waker.register(cx.waker());
|
||||
if r.events_end.read().bits() != 0 {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
Poll::Pending
|
||||
})
|
||||
.await;
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM microphone driver Config
|
||||
pub struct Config {
|
||||
/// Use stero or mono operation
|
||||
pub operation_mode: OperationMode,
|
||||
/// On which edge the left channel should be samples
|
||||
pub edge: Edge,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
operation_mode: OperationMode::Mono,
|
||||
edge: Edge::LeftFalling,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM operation mode.
|
||||
#[derive(PartialEq)]
|
||||
pub enum OperationMode {
|
||||
/// Mono (1 channel)
|
||||
Mono,
|
||||
/// Stereo (2 channels)
|
||||
Stereo,
|
||||
}
|
||||
|
||||
/// PDM edge polarity
|
||||
#[derive(PartialEq)]
|
||||
pub enum Edge {
|
||||
/// Left edge is rising
|
||||
LeftRising,
|
||||
/// Left edge is falling
|
||||
LeftFalling,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for Pdm<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
let r = T::regs();
|
||||
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
|
||||
r.enable.write(|w| w.enable().disabled());
|
||||
|
||||
r.psel.din.reset();
|
||||
r.psel.clk.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
/// Peripheral static state
|
||||
pub struct State {
|
||||
pub waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Instance {
|
||||
fn regs() -> &'static crate::pac::pdm::RegisterBlock;
|
||||
fn state() -> &'static State;
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM peripheral instance.
|
||||
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
|
||||
/// Interrupt for this peripheral.
|
||||
type Interrupt: interrupt::typelevel::Interrupt;
|
||||
}
|
||||
|
||||
macro_rules! impl_pdm {
|
||||
($type:ident, $pac_type:ident, $irq:ident) => {
|
||||
impl crate::pdm::sealed::Instance for peripherals::$type {
|
||||
fn regs() -> &'static crate::pac::pdm::RegisterBlock {
|
||||
unsafe { &*pac::$pac_type::ptr() }
|
||||
}
|
||||
fn state() -> &'static crate::pdm::sealed::State {
|
||||
static STATE: crate::pdm::sealed::State = crate::pdm::sealed::State::new();
|
||||
&STATE
|
||||
}
|
||||
}
|
||||
impl crate::pdm::Instance for peripherals::$type {
|
||||
type Interrupt = crate::interrupt::typelevel::$irq;
|
||||
}
|
||||
};
|
||||
}
|
||||
//! Pulse Density Modulation (PDM) mirophone driver.
|
||||
|
||||
#![macro_use]
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use fixed::types::I7F1;
|
||||
use futures::future::poll_fn;
|
||||
|
||||
use crate::chip::EASY_DMA_SIZE;
|
||||
use crate::gpio::sealed::Pin;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::pac::pdm::mode::{EDGE_A, OPERATION_A};
|
||||
pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency;
|
||||
#[cfg(any(
|
||||
feature = "nrf52840",
|
||||
feature = "nrf52833",
|
||||
feature = "_nrf5340-app",
|
||||
feature = "_nrf9160",
|
||||
))]
|
||||
pub use crate::pac::pdm::ratio::RATIO_A as Ratio;
|
||||
use crate::{interrupt, Peripheral};
|
||||
|
||||
/// Interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
let r = T::regs();
|
||||
|
||||
if r.events_end.read().bits() != 0 {
|
||||
r.intenclr.write(|w| w.end().clear());
|
||||
}
|
||||
|
||||
if r.events_started.read().bits() != 0 {
|
||||
r.intenclr.write(|w| w.started().clear());
|
||||
}
|
||||
|
||||
if r.events_stopped.read().bits() != 0 {
|
||||
r.intenclr.write(|w| w.stopped().clear());
|
||||
}
|
||||
|
||||
T::state().waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM microphone interface
|
||||
pub struct Pdm<'d, T: Instance> {
|
||||
_peri: PeripheralRef<'d, T>,
|
||||
}
|
||||
|
||||
/// PDM error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// Buffer is too long.
|
||||
BufferTooLong,
|
||||
/// Buffer is empty
|
||||
BufferZeroLength,
|
||||
/// PDM is not running
|
||||
NotRunning,
|
||||
/// PDM is already running
|
||||
AlreadyRunning,
|
||||
}
|
||||
|
||||
static DUMMY_BUFFER: [i16; 1] = [0; 1];
|
||||
|
||||
/// The state of a continuously running sampler. While it reflects
|
||||
/// the progress of a sampler, it also signals what should be done
|
||||
/// next. For example, if the sampler has stopped then the Pdm implementation
|
||||
/// can then tear down its infrastructure.
|
||||
#[derive(PartialEq)]
|
||||
pub enum SamplerState {
|
||||
/// The sampler processed the samples and is ready for more.
|
||||
Sampled,
|
||||
/// The sampler is done processing samples.
|
||||
Stopped,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Pdm<'d, T> {
|
||||
/// Create PDM driver
|
||||
pub fn new(
|
||||
pdm: impl Peripheral<P = T> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
clk: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
din: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(pdm, clk, din);
|
||||
Self::new_inner(pdm, clk.map_into(), din.map_into(), config)
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
pdm: PeripheralRef<'d, T>,
|
||||
clk: PeripheralRef<'d, AnyPin>,
|
||||
din: PeripheralRef<'d, AnyPin>,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(pdm);
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
// setup gpio pins
|
||||
din.conf().write(|w| w.input().set_bit());
|
||||
r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) });
|
||||
clk.set_low();
|
||||
clk.conf().write(|w| w.dir().output());
|
||||
r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) });
|
||||
|
||||
// configure
|
||||
r.pdmclkctrl.write(|w| w.freq().variant(config.frequency));
|
||||
#[cfg(any(
|
||||
feature = "nrf52840",
|
||||
feature = "nrf52833",
|
||||
feature = "_nrf5340-app",
|
||||
feature = "_nrf9160",
|
||||
))]
|
||||
r.ratio.write(|w| w.ratio().variant(config.ratio));
|
||||
r.mode.write(|w| {
|
||||
w.operation().variant(config.operation_mode.into());
|
||||
w.edge().variant(config.edge.into());
|
||||
w
|
||||
});
|
||||
|
||||
Self::_set_gain(r, config.gain_left, config.gain_right);
|
||||
|
||||
// Disable all events interrupts
|
||||
r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) });
|
||||
|
||||
// IRQ
|
||||
T::Interrupt::unpend();
|
||||
unsafe { T::Interrupt::enable() };
|
||||
|
||||
r.enable.write(|w| w.enable().set_bit());
|
||||
|
||||
Self { _peri: pdm }
|
||||
}
|
||||
|
||||
fn _set_gain(r: &crate::pac::pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) {
|
||||
let gain_left = gain_left
|
||||
.saturating_add(I7F1::from_bits(40))
|
||||
.saturating_to_num::<u8>()
|
||||
.clamp(0, 0x50);
|
||||
let gain_right = gain_right
|
||||
.saturating_add(I7F1::from_bits(40))
|
||||
.saturating_to_num::<u8>()
|
||||
.clamp(0, 0x50);
|
||||
|
||||
r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) });
|
||||
r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) });
|
||||
}
|
||||
|
||||
/// Adjust the gain of the PDM microphone on the fly
|
||||
pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) {
|
||||
Self::_set_gain(T::regs(), gain_left, gain_right)
|
||||
}
|
||||
|
||||
/// Start sampling microphon data into a dummy buffer
|
||||
/// Usefull to start the microphon and keep it active between recording samples
|
||||
pub async fn start(&mut self) {
|
||||
let r = T::regs();
|
||||
|
||||
// start dummy sampling because microphon needs some setup time
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
|
||||
r.sample
|
||||
.maxcnt
|
||||
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
|
||||
|
||||
r.tasks_start.write(|w| unsafe { w.bits(1) });
|
||||
}
|
||||
|
||||
/// Stop sampling microphon data inta a dummy buffer
|
||||
pub async fn stop(&mut self) {
|
||||
let r = T::regs();
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
r.events_started.reset();
|
||||
}
|
||||
|
||||
/// Sample data into the given buffer.
|
||||
pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
|
||||
if buffer.len() == 0 {
|
||||
return Err(Error::BufferZeroLength);
|
||||
}
|
||||
if buffer.len() > EASY_DMA_SIZE {
|
||||
return Err(Error::BufferTooLong);
|
||||
}
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
if r.events_started.read().bits() == 0 {
|
||||
return Err(Error::NotRunning);
|
||||
}
|
||||
|
||||
let drop = OnDrop::new(move || {
|
||||
r.intenclr.write(|w| w.end().clear());
|
||||
r.events_stopped.reset();
|
||||
|
||||
// reset to dummy buffer
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
|
||||
r.sample
|
||||
.maxcnt
|
||||
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
|
||||
|
||||
while r.events_stopped.read().bits() == 0 {}
|
||||
});
|
||||
|
||||
// setup user buffer
|
||||
let ptr = buffer.as_ptr();
|
||||
let len = buffer.len();
|
||||
r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) });
|
||||
r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) });
|
||||
|
||||
// wait till the current sample is finished and the user buffer sample is started
|
||||
Self::wait_for_sample().await;
|
||||
|
||||
// reset the buffer back to the dummy buffer
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
|
||||
r.sample
|
||||
.maxcnt
|
||||
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
|
||||
|
||||
// wait till the user buffer is sampled
|
||||
Self::wait_for_sample().await;
|
||||
|
||||
drop.defuse();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_sample() {
|
||||
let r = T::regs();
|
||||
|
||||
r.events_end.reset();
|
||||
r.intenset.write(|w| w.end().set());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
poll_fn(|cx| {
|
||||
T::state().waker.register(cx.waker());
|
||||
if r.events_end.read().bits() != 0 {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
Poll::Pending
|
||||
})
|
||||
.await;
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Continuous sampling with double buffers.
|
||||
///
|
||||
/// A sampler closure is provided that receives the buffer of samples, noting
|
||||
/// that the size of this buffer can be less than the original buffer's size.
|
||||
/// A command is return from the closure that indicates whether the sampling
|
||||
/// should continue or stop.
|
||||
///
|
||||
/// NOTE: The time spent within the callback supplied should not exceed the time
|
||||
/// taken to acquire the samples into a single buffer. You should measure the
|
||||
/// time taken by the callback and set the sample buffer size accordingly.
|
||||
/// Exceeding this time can lead to samples becoming dropped.
|
||||
pub async fn run_task_sampler<S, const N: usize>(
|
||||
&mut self,
|
||||
bufs: &mut [[i16; N]; 2],
|
||||
mut sampler: S,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
S: FnMut(&[i16; N]) -> SamplerState,
|
||||
{
|
||||
let r = T::regs();
|
||||
|
||||
if r.events_started.read().bits() != 0 {
|
||||
return Err(Error::AlreadyRunning);
|
||||
}
|
||||
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) });
|
||||
r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) });
|
||||
|
||||
// Reset and enable the events
|
||||
r.events_end.reset();
|
||||
r.events_started.reset();
|
||||
r.events_stopped.reset();
|
||||
r.intenset.write(|w| {
|
||||
w.end().set();
|
||||
w.started().set();
|
||||
w.stopped().set();
|
||||
w
|
||||
});
|
||||
|
||||
// Don't reorder the start event before the previous writes. Hopefully self
|
||||
// wouldn't happen anyway.
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
r.tasks_start.write(|w| unsafe { w.bits(1) });
|
||||
|
||||
let mut current_buffer = 0;
|
||||
|
||||
let mut done = false;
|
||||
|
||||
let drop = OnDrop::new(|| {
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
// N.B. It would be better if this were async, but Drop only support sync code.
|
||||
while r.events_stopped.read().bits() != 0 {}
|
||||
});
|
||||
|
||||
// Wait for events and complete when the sampler indicates it has had enough.
|
||||
poll_fn(|cx| {
|
||||
let r = T::regs();
|
||||
|
||||
T::state().waker.register(cx.waker());
|
||||
|
||||
if r.events_end.read().bits() != 0 {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
r.events_end.reset();
|
||||
r.intenset.write(|w| w.end().set());
|
||||
|
||||
if !done {
|
||||
// Discard the last buffer after the user requested a stop.
|
||||
if sampler(&bufs[current_buffer]) == SamplerState::Sampled {
|
||||
let next_buffer = 1 - current_buffer;
|
||||
current_buffer = next_buffer;
|
||||
} else {
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
done = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
if r.events_started.read().bits() != 0 {
|
||||
r.events_started.reset();
|
||||
r.intenset.write(|w| w.started().set());
|
||||
|
||||
let next_buffer = 1 - current_buffer;
|
||||
r.sample
|
||||
.ptr
|
||||
.write(|w| unsafe { w.sampleptr().bits(bufs[next_buffer].as_mut_ptr() as u32) });
|
||||
}
|
||||
|
||||
if r.events_stopped.read().bits() != 0 {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
})
|
||||
.await;
|
||||
drop.defuse();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM microphone driver Config
|
||||
pub struct Config {
|
||||
/// Use stero or mono operation
|
||||
pub operation_mode: OperationMode,
|
||||
/// On which edge the left channel should be samples
|
||||
pub edge: Edge,
|
||||
/// Clock frequency
|
||||
pub frequency: Frequency,
|
||||
/// Clock ratio
|
||||
#[cfg(any(
|
||||
feature = "nrf52840",
|
||||
feature = "nrf52833",
|
||||
feature = "_nrf5340-app",
|
||||
feature = "_nrf9160",
|
||||
))]
|
||||
pub ratio: Ratio,
|
||||
/// Gain left in dB
|
||||
pub gain_left: I7F1,
|
||||
/// Gain right in dB
|
||||
pub gain_right: I7F1,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
operation_mode: OperationMode::Mono,
|
||||
edge: Edge::LeftFalling,
|
||||
frequency: Frequency::DEFAULT,
|
||||
#[cfg(any(
|
||||
feature = "nrf52840",
|
||||
feature = "nrf52833",
|
||||
feature = "_nrf5340-app",
|
||||
feature = "_nrf9160",
|
||||
))]
|
||||
ratio: Ratio::RATIO80,
|
||||
gain_left: I7F1::ZERO,
|
||||
gain_right: I7F1::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM operation mode.
|
||||
#[derive(PartialEq)]
|
||||
pub enum OperationMode {
|
||||
/// Mono (1 channel)
|
||||
Mono,
|
||||
/// Stereo (2 channels)
|
||||
Stereo,
|
||||
}
|
||||
|
||||
impl From<OperationMode> for OPERATION_A {
|
||||
fn from(mode: OperationMode) -> Self {
|
||||
match mode {
|
||||
OperationMode::Mono => OPERATION_A::MONO,
|
||||
OperationMode::Stereo => OPERATION_A::STEREO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM edge polarity
|
||||
#[derive(PartialEq)]
|
||||
pub enum Edge {
|
||||
/// Left edge is rising
|
||||
LeftRising,
|
||||
/// Left edge is falling
|
||||
LeftFalling,
|
||||
}
|
||||
|
||||
impl From<Edge> for EDGE_A {
|
||||
fn from(edge: Edge) -> Self {
|
||||
match edge {
|
||||
Edge::LeftRising => EDGE_A::LEFT_RISING,
|
||||
Edge::LeftFalling => EDGE_A::LEFT_FALLING,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for Pdm<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
let r = T::regs();
|
||||
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
|
||||
r.enable.write(|w| w.enable().disabled());
|
||||
|
||||
r.psel.din.reset();
|
||||
r.psel.clk.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
/// Peripheral static state
|
||||
pub struct State {
|
||||
pub waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Instance {
|
||||
fn regs() -> &'static crate::pac::pdm::RegisterBlock;
|
||||
fn state() -> &'static State;
|
||||
}
|
||||
}
|
||||
|
||||
/// PDM peripheral instance.
|
||||
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
|
||||
/// Interrupt for this peripheral.
|
||||
type Interrupt: interrupt::typelevel::Interrupt;
|
||||
}
|
||||
|
||||
macro_rules! impl_pdm {
|
||||
($type:ident, $pac_type:ident, $irq:ident) => {
|
||||
impl crate::pdm::sealed::Instance for peripherals::$type {
|
||||
fn regs() -> &'static crate::pac::pdm::RegisterBlock {
|
||||
unsafe { &*pac::$pac_type::ptr() }
|
||||
}
|
||||
fn state() -> &'static crate::pdm::sealed::State {
|
||||
static STATE: crate::pdm::sealed::State = crate::pdm::sealed::State::new();
|
||||
&STATE
|
||||
}
|
||||
}
|
||||
impl crate::pdm::Instance for peripherals::$type {
|
||||
type Interrupt = crate::interrupt::typelevel::$irq;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use embassy_hal_common::into_ref;
|
||||
use embassy_hal_internal::into_ref;
|
||||
|
||||
use super::{Channel, ConfigurableChannel, Event, Ppi, Task};
|
||||
use crate::{pac, Peripheral};
|
||||
@ -12,14 +12,14 @@ pub(crate) fn regs() -> &'static pac::dppic::RegisterBlock {
|
||||
|
||||
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
|
||||
/// Configure PPI channel to trigger `task` on `event`.
|
||||
pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self {
|
||||
pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task: Task<'d>) -> Self {
|
||||
Ppi::new_many_to_many(ch, [event], [task])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
|
||||
/// Configure PPI channel to trigger both `task1` and `task2` on `event`.
|
||||
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
|
||||
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self {
|
||||
Ppi::new_many_to_many(ch, [event], [task1, task2])
|
||||
}
|
||||
}
|
||||
@ -30,8 +30,8 @@ impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usi
|
||||
/// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires.
|
||||
pub fn new_many_to_many(
|
||||
ch: impl Peripheral<P = C> + 'd,
|
||||
events: [Event; EVENT_COUNT],
|
||||
tasks: [Task; TASK_COUNT],
|
||||
events: [Event<'d>; EVENT_COUNT],
|
||||
tasks: [Task<'d>; TASK_COUNT],
|
||||
) -> Self {
|
||||
into_ref!(ch);
|
||||
|
||||
|
@ -15,9 +15,10 @@
|
||||
//! many tasks and events, but any single task or event can only be coupled with one channel.
|
||||
//!
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef};
|
||||
|
||||
use crate::{peripherals, Peripheral};
|
||||
|
||||
@ -30,9 +31,9 @@ pub(crate) use _version::*;
|
||||
pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> {
|
||||
ch: PeripheralRef<'d, C>,
|
||||
#[cfg(feature = "_dppi")]
|
||||
events: [Event; EVENT_COUNT],
|
||||
events: [Event<'d>; EVENT_COUNT],
|
||||
#[cfg(feature = "_dppi")]
|
||||
tasks: [Task; TASK_COUNT],
|
||||
tasks: [Task<'d>; TASK_COUNT],
|
||||
}
|
||||
|
||||
/// PPI channel group driver.
|
||||
@ -95,7 +96,7 @@ impl<'d, G: Group> PpiGroup<'d, G> {
|
||||
/// Get a reference to the "enable all" task.
|
||||
///
|
||||
/// When triggered, it will enable all the channels in this group.
|
||||
pub fn task_enable_all(&self) -> Task {
|
||||
pub fn task_enable_all(&self) -> Task<'d> {
|
||||
let n = self.g.number();
|
||||
Task::from_reg(®s().tasks_chg[n].en)
|
||||
}
|
||||
@ -103,7 +104,7 @@ impl<'d, G: Group> PpiGroup<'d, G> {
|
||||
/// Get a reference to the "disable all" task.
|
||||
///
|
||||
/// When triggered, it will disable all the channels in this group.
|
||||
pub fn task_disable_all(&self) -> Task {
|
||||
pub fn task_disable_all(&self) -> Task<'d> {
|
||||
let n = self.g.number();
|
||||
Task::from_reg(®s().tasks_chg[n].dis)
|
||||
}
|
||||
@ -125,20 +126,28 @@ const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::<u32>();
|
||||
/// When a task is subscribed to a PPI channel, it will run when the channel is triggered by
|
||||
/// a published event.
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Task(NonNull<u32>);
|
||||
pub struct Task<'d>(NonNull<u32>, PhantomData<&'d ()>);
|
||||
|
||||
impl Task {
|
||||
impl<'d> Task<'d> {
|
||||
/// Create a new `Task` from a task register pointer
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `ptr` must be a pointer to a valid `TASKS_*` register from an nRF peripheral.
|
||||
pub unsafe fn new_unchecked(ptr: NonNull<u32>) -> Self {
|
||||
Self(ptr)
|
||||
Self(ptr, PhantomData)
|
||||
}
|
||||
|
||||
/// Triggers this task.
|
||||
pub fn trigger(&mut self) {
|
||||
unsafe { self.0.as_ptr().write_volatile(1) };
|
||||
}
|
||||
|
||||
pub(crate) fn from_reg<T>(reg: &T) -> Self {
|
||||
Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) })
|
||||
Self(
|
||||
unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) },
|
||||
PhantomData,
|
||||
)
|
||||
}
|
||||
|
||||
/// Address of subscription register for this task.
|
||||
@ -151,26 +160,39 @@ impl Task {
|
||||
/// # Safety
|
||||
///
|
||||
/// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core.
|
||||
unsafe impl Send for Task {}
|
||||
unsafe impl Send for Task<'_> {}
|
||||
|
||||
/// Represents an event that a peripheral can publish.
|
||||
///
|
||||
/// An event can be set to publish on a PPI channel when the event happens.
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Event(NonNull<u32>);
|
||||
pub struct Event<'d>(NonNull<u32>, PhantomData<&'d ()>);
|
||||
|
||||
impl Event {
|
||||
impl<'d> Event<'d> {
|
||||
/// Create a new `Event` from an event register pointer
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `ptr` must be a pointer to a valid `EVENTS_*` register from an nRF peripheral.
|
||||
pub unsafe fn new_unchecked(ptr: NonNull<u32>) -> Self {
|
||||
Self(ptr)
|
||||
Self(ptr, PhantomData)
|
||||
}
|
||||
|
||||
pub(crate) fn from_reg<T>(reg: &T) -> Self {
|
||||
Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) })
|
||||
pub(crate) fn from_reg<T>(reg: &'d T) -> Self {
|
||||
Self(
|
||||
unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) },
|
||||
PhantomData,
|
||||
)
|
||||
}
|
||||
|
||||
/// Describes whether this Event is currently in a triggered state.
|
||||
pub fn is_triggered(&self) -> bool {
|
||||
unsafe { self.0.as_ptr().read_volatile() == 1 }
|
||||
}
|
||||
|
||||
/// Clear the current register's triggered state, reverting it to 0.
|
||||
pub fn clear(&mut self) {
|
||||
unsafe { self.0.as_ptr().write_volatile(0) };
|
||||
}
|
||||
|
||||
/// Address of publish register for this event.
|
||||
@ -183,7 +205,7 @@ impl Event {
|
||||
/// # Safety
|
||||
///
|
||||
/// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core.
|
||||
unsafe impl Send for Event {}
|
||||
unsafe impl Send for Event<'_> {}
|
||||
|
||||
// ======================
|
||||
// traits
|
||||
|
@ -1,14 +1,14 @@
|
||||
use embassy_hal_common::into_ref;
|
||||
use embassy_hal_internal::into_ref;
|
||||
|
||||
use super::{Channel, ConfigurableChannel, Event, Ppi, StaticChannel, Task};
|
||||
use crate::{pac, Peripheral};
|
||||
|
||||
impl Task {
|
||||
impl<'d> Task<'d> {
|
||||
fn reg_val(&self) -> u32 {
|
||||
self.0.as_ptr() as _
|
||||
}
|
||||
}
|
||||
impl Event {
|
||||
impl<'d> Event<'d> {
|
||||
fn reg_val(&self) -> u32 {
|
||||
self.0.as_ptr() as _
|
||||
}
|
||||
@ -34,7 +34,7 @@ impl<'d, C: StaticChannel> Ppi<'d, C, 0, 1> {
|
||||
|
||||
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
|
||||
/// Configure PPI channel to trigger `task` on `event`.
|
||||
pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self {
|
||||
pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task: Task<'d>) -> Self {
|
||||
into_ref!(ch);
|
||||
|
||||
let r = regs();
|
||||
@ -49,7 +49,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
|
||||
#[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task
|
||||
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
|
||||
/// Configure PPI channel to trigger both `task1` and `task2` on `event`.
|
||||
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
|
||||
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self {
|
||||
into_ref!(ch);
|
||||
|
||||
let r = regs();
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
|
||||
use crate::gpio::sealed::Pin as _;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin, PselBits};
|
||||
@ -181,7 +181,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `Stopped` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_stopped(&self) -> Event {
|
||||
pub fn event_stopped(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_stopped)
|
||||
@ -189,7 +189,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `LoopsDone` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_loops_done(&self) -> Event {
|
||||
pub fn event_loops_done(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_loopsdone)
|
||||
@ -197,7 +197,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `PwmPeriodEnd` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_pwm_period_end(&self) -> Event {
|
||||
pub fn event_pwm_period_end(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_pwmperiodend)
|
||||
@ -205,7 +205,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `Seq0 End` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_seq_end(&self) -> Event {
|
||||
pub fn event_seq_end(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_seqend[0])
|
||||
@ -213,7 +213,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `Seq1 End` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_seq1_end(&self) -> Event {
|
||||
pub fn event_seq1_end(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_seqend[1])
|
||||
@ -221,7 +221,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `Seq0 Started` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_seq0_started(&self) -> Event {
|
||||
pub fn event_seq0_started(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_seqstarted[0])
|
||||
@ -229,7 +229,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
/// Returns reference to `Seq1 Started` event endpoint for PPI.
|
||||
#[inline(always)]
|
||||
pub fn event_seq1_started(&self) -> Event {
|
||||
pub fn event_seq1_started(&self) -> Event<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Event::from_reg(&r.events_seqstarted[1])
|
||||
@ -240,7 +240,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
///
|
||||
/// Interacting with the sequence while it runs puts it in an unknown state
|
||||
#[inline(always)]
|
||||
pub unsafe fn task_start_seq0(&self) -> Task {
|
||||
pub unsafe fn task_start_seq0(&self) -> Task<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Task::from_reg(&r.tasks_seqstart[0])
|
||||
@ -251,7 +251,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
///
|
||||
/// Interacting with the sequence while it runs puts it in an unknown state
|
||||
#[inline(always)]
|
||||
pub unsafe fn task_start_seq1(&self) -> Task {
|
||||
pub unsafe fn task_start_seq1(&self) -> Task<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Task::from_reg(&r.tasks_seqstart[1])
|
||||
@ -262,7 +262,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
///
|
||||
/// Interacting with the sequence while it runs puts it in an unknown state
|
||||
#[inline(always)]
|
||||
pub unsafe fn task_next_step(&self) -> Task {
|
||||
pub unsafe fn task_next_step(&self) -> Task<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Task::from_reg(&r.tasks_nextstep)
|
||||
@ -273,7 +273,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
///
|
||||
/// Interacting with the sequence while it runs puts it in an unknown state
|
||||
#[inline(always)]
|
||||
pub unsafe fn task_stop(&self) -> Task {
|
||||
pub unsafe fn task_stop(&self) -> Task<'d> {
|
||||
let r = T::regs();
|
||||
|
||||
Task::from_reg(&r.tasks_stop)
|
||||
|
@ -6,7 +6,7 @@ use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
|
||||
use crate::gpio::sealed::Pin as _;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
|
@ -7,8 +7,8 @@ use core::marker::PhantomData;
|
||||
use core::ptr;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
|
||||
|
||||
use crate::gpio::{self, Pin as GpioPin};
|
||||
|
@ -8,8 +8,8 @@ use core::ptr;
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
|
@ -6,8 +6,8 @@ use core::future::poll_fn;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use pac::{saadc, SAADC};
|
||||
use saadc::ch::config::{GAIN_A, REFSEL_A, RESP_A, TACQ_A};
|
||||
@ -320,7 +320,9 @@ impl<'d, const N: usize> Saadc<'d, N> {
|
||||
timer.cc(0).write(sample_counter);
|
||||
timer.cc(0).short_compare_clear();
|
||||
|
||||
let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer.cc(0).event_compare(), Task::from_reg(&r.tasks_sample));
|
||||
let timer_cc = timer.cc(0);
|
||||
|
||||
let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(&r.tasks_sample));
|
||||
|
||||
timer.start();
|
||||
|
||||
|
@ -8,7 +8,7 @@ use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
|
||||
pub use pac::spim0::frequency::FREQUENCY_A as Frequency;
|
||||
|
||||
@ -468,25 +468,19 @@ mod eh1 {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::spi::SpiBusFlush for Spim<'d, T> {
|
||||
impl<'d, T: Instance> embedded_hal_1::spi::SpiBus<u8> for Spim<'d, T> {
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::spi::SpiBusRead<u8> for Spim<'d, T> {
|
||||
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_transfer(words, &[])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::spi::SpiBusWrite<u8> for Spim<'d, T> {
|
||||
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(words)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::spi::SpiBus<u8> for Spim<'d, T> {
|
||||
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_transfer(read, write)
|
||||
}
|
||||
@ -502,25 +496,19 @@ mod eha {
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::spi::SpiBusFlush for Spim<'d, T> {
|
||||
impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spim<'d, T> {
|
||||
async fn flush(&mut self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::spi::SpiBusRead<u8> for Spim<'d, T> {
|
||||
async fn read(&mut self, words: &mut [u8]) -> Result<(), Error> {
|
||||
self.read(words).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::spi::SpiBusWrite<u8> for Spim<'d, T> {
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Error> {
|
||||
self.write(data).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spim<'d, T> {
|
||||
async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> {
|
||||
self.transfer(rx, tx).await
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
|
||||
|
||||
use crate::chip::FORCE_COPY_BUFFER_SIZE;
|
||||
|
@ -3,8 +3,8 @@
|
||||
use core::future::poll_fn;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use fixed::types::I30F2;
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#![macro_use]
|
||||
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
|
||||
use crate::ppi::{Event, Task};
|
||||
use crate::{pac, Peripheral};
|
||||
@ -168,21 +168,21 @@ impl<'d, T: Instance> Timer<'d, T> {
|
||||
/// Returns the START task, for use with PPI.
|
||||
///
|
||||
/// When triggered, this task starts the timer.
|
||||
pub fn task_start(&self) -> Task {
|
||||
pub fn task_start(&self) -> Task<'d> {
|
||||
Task::from_reg(&T::regs().tasks_start)
|
||||
}
|
||||
|
||||
/// Returns the STOP task, for use with PPI.
|
||||
///
|
||||
/// When triggered, this task stops the timer.
|
||||
pub fn task_stop(&self) -> Task {
|
||||
pub fn task_stop(&self) -> Task<'d> {
|
||||
Task::from_reg(&T::regs().tasks_stop)
|
||||
}
|
||||
|
||||
/// Returns the CLEAR task, for use with PPI.
|
||||
///
|
||||
/// When triggered, this task resets the timer's counter to 0.
|
||||
pub fn task_clear(&self) -> Task {
|
||||
pub fn task_clear(&self) -> Task<'d> {
|
||||
Task::from_reg(&T::regs().tasks_clear)
|
||||
}
|
||||
|
||||
@ -190,7 +190,7 @@ impl<'d, T: Instance> Timer<'d, T> {
|
||||
///
|
||||
/// When triggered, this task increments the timer's counter by 1.
|
||||
/// Only works in counter mode.
|
||||
pub fn task_count(&self) -> Task {
|
||||
pub fn task_count(&self) -> Task<'d> {
|
||||
Task::from_reg(&T::regs().tasks_count)
|
||||
}
|
||||
|
||||
@ -258,14 +258,14 @@ impl<'d, T: Instance> Cc<'d, T> {
|
||||
/// Returns this CC register's CAPTURE task, for use with PPI.
|
||||
///
|
||||
/// When triggered, this task will capture the current value of the timer's counter in this register.
|
||||
pub fn task_capture(&self) -> Task {
|
||||
pub fn task_capture(&self) -> Task<'d> {
|
||||
Task::from_reg(&T::regs().tasks_capture)
|
||||
}
|
||||
|
||||
/// Returns this CC register's COMPARE event, for use with PPI.
|
||||
///
|
||||
/// This event will fire when the timer's counter reaches the value in this CC register.
|
||||
pub fn event_compare(&self) -> Event {
|
||||
pub fn event_compare(&self) -> Event<'d> {
|
||||
Event::from_reg(&T::regs().events_compare[self.n])
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ use core::sync::atomic::Ordering::SeqCst;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant};
|
||||
|
@ -8,7 +8,7 @@ use core::sync::atomic::compiler_fence;
|
||||
use core::sync::atomic::Ordering::SeqCst;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user