Compare commits

...

855 Commits

Author SHA1 Message Date
b49bc449d7 repro on f4. 2023-05-03 22:52:59 +02:00
a21a2901ac hangs. 2023-05-02 22:45:54 +02:00
374c92a4f0 Merge #1420
1420: stm32/usart: add OVER8 and PRESC, add baudrate test. r=Dirbaio a=Dirbaio

Fixes #1183 
Fixes #1418 

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-05-02 20:14:12 +00:00
433422b9f2 stm32/test: remove adsfa 2023-05-02 22:13:38 +02:00
a85b34c1fe stm32/test: F1 no longer fits in RAM. 2023-05-02 22:13:38 +02:00
1078f6f4e7 stm32/test: workaround #1426 2023-05-02 22:13:38 +02:00
2bb6e93e86 stm32/usart: add baudrate calc test. 2023-05-02 20:10:09 +02:00
2afa08c923 Merge #1425
1425: rp pio, round 2 r=Dirbaio a=pennae

another round of bugfixes for pio, and some refactoring. in the end we'd like to make pio look like all the other modules and not expose traits that provide all the methods of a type, but put them onto the type itself. traits only make much sense, even if we added an AnyPio and merged the types for the member state machines (at the cost of at least a u8 per member of Pio).

Co-authored-by: pennae <github@quasiparticle.net>
2023-05-02 18:03:00 +00:00
a61701b756 stm32/usart: add OVER8 and PRESC support, update PAC 2023-05-02 19:36:00 +02:00
7a36072a15 rp/pio: drop SmInstance{,Base}
these are just overly convoluted ways of writing down numbers.
2023-05-02 18:01:18 +02:00
a167c77d39 rp/pio: make PioCommon a struct
the PioCommon trait does not serve much of a purpose; there can be only
two implementations and they only differ in a few associated constants.
2023-05-02 18:01:18 +02:00
8839f3f62a rp/pio: PioInstance::split -> Pio::new
not requiring a PioInstance for splitting lets us split from a
PeripheralRef or borrowed PIO as well, mirroring every other peripheral
in embassy_rp. pio pins still have to be constructed from owned pin
instances for now.
2023-05-02 15:52:50 +02:00
ac111f40d8 rp/pio: fix PioPin::set_pull, set_schmitt comment 2023-05-02 15:46:22 +02:00
3229b5e809 rp/pio: remove PioPeripheral
merge into PioInstance instead. PioPeripheral was mostly a wrapper
around PioInstance anyway, and the way the wrapping was done required
PioInstanceBase<N> types where PIO{N} could've been used instead.
2023-05-02 15:46:21 +02:00
b2047c4351 Merge #1423
1423: rp: fix gpio InputFuture and inefficiencies r=pennae a=pennae

InputFuture could not wait for edges without breaking due to a broken From impl, but even if the impl had been correct it would not have worked correctly because raw edge interrupts are sticky and must be cleared from software. also replace critical sections with atomic accesses, and do nvic setup only once.

Co-authored-by: pennae <github@quasiparticle.net>
2023-05-02 12:56:51 +00:00
849011b826 rp/gpio: set up gpio interrupts only once
doing this setup work repeatedly, on every wait, is unnecessary. with
nothing ever disabling the interrupt it is sufficient to enable it once
during device init and never touch it again.
2023-05-02 14:28:27 +02:00
6cec6fa09b rp/pio: don't use modify on shared registers
pio control registers are notionally shared between state machines as
well. state machine operations that change these registers must use
atomic accesses (or critical sections, which would be overkill).

notably PioPin::set_input_sync_bypass was even wrong, enabling the
bypass on a pin requires the corresponding bit to be set (not cleared).
the PioCommon function got it right.
2023-05-02 13:44:24 +02:00
0d224a00e1 rp/pio: avoid sm(SM_NO) indexing
accessing the current state machine is an extremely common operation
that shouldn't have its specifics repeated myriad times.
2023-05-02 13:44:24 +02:00
47ae9b7981 rp/pio: add funcsel values to PioInstance
makes code setting funcsels easier to read and should make it easier to
hook up more pio blocks, should they ever appear
2023-05-02 13:44:24 +02:00
8e22d57447 rp/pio: add hd44780 example
add an hd44780 example for pio. hd44780 with busy polling is a pretty
complicated protocol if the busy polling is to be done by the
peripheral, and this example exercises many pio features that we don't
have good examples for yet.
2023-05-02 13:44:24 +02:00
5f99ccf54c Merge #1422
1422: rp: remove leftovers from #1414 r=Dirbaio a=pennae

forgot to remove these when they were no longer necessary or useful. oops.

Co-authored-by: pennae <github@quasiparticle.net>
2023-05-02 10:07:32 +00:00
54e695b1b2 rp/pio: fix dma
fixing the dma word size to 32 makes it impossible to implement any
peripheral that takes its data in smaller chunks, eg uart, spi, i2c,
ws2812, the list goes on.

compiler barriers were also not set correctly; we need a SeqCst barrier
before starting a transfer as well to avoid reordering of accesses into
a buffer after dma has started.
2023-05-02 10:56:37 +02:00
8fc92fdf62 rp/gpio: drop critical_section use
we don't need critical sections if we just use atomic access aliases.
2023-05-02 08:43:04 +02:00
c6424fdc11 gp/gpio: fix InputFuture edge waits
InputFuture did not use and check edge interrupts correctly.
InterruptTrigger should've checked for not 1,2,3,4 but 1,2,4,8 since the
inte fields are bitmasks, and not clearing INTR would have repeatedly
triggered edge interrupts early.
2023-05-02 08:43:04 +02:00
3c31236c10 rp: remove leftovers from #1414
forgot to remove these when they were no longer necessary or useful. oops.
2023-05-02 07:40:12 +02:00
6096f0cf4b Merge #1404
1404: feat(stm32): Add DMA based, ring-buffer based rx uart, v3 r=Dirbaio a=rmja

This PR replaces #1150. Comparing to that PR, this one has the following changes:

* The implementation now aligns with the new stm32 dma module, thanks `@Dirbaio!`
* Calls to `read()` now returns on either 1) idle line, or 2) ring buffer is at most half full. This is different from the previous pr, which would return a lot of 1 byte reads. Thank you `@chemicstry` for making me realize that it was actually not what I wanted. This is accomplished using half-transfer completed and full-transfer completed interrupts. Both seems to be supported on both dma and bdma.

The implementation still have the issue mentioned here: https://github.com/embassy-rs/embassy/pull/1150#discussion_r1094627035

Regarding the todos here: https://github.com/embassy-rs/embassy/pull/1150#issuecomment-1513905925. I have removed the exposure of ndtr from `dma::RingBuffer` to the uart so that the uart now simply calls `ringbuf::reload_position()` to align the position within the ring buffer to that of the actual running dma controller. BDMA and GPDMA is not implemented. I do not have any chips with those dma controllers, so maybe someone else should to this so that it can be tested.

The `saturate_serial` test utility inside `tests/utils` has an `--idles` switch which can be used to saturate the uart from a pc, but with random idles.

Because embassy-stm32 now can have tests, we should probably run them in ci. I do this locally to test the DmaRingBuffer:  `cargo test --no-default-features --features stm32f429ig`.

cc `@chemicstry` `@Dirbaio` 


Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-05-01 21:36:10 +00:00
a1d45303c3 stm32/test: fix race condition in uart_dma. 2023-05-01 23:20:51 +02:00
7601779693 stm32/test: cleanup ringbuffer test, exit on success (transferring 100kb) 2023-05-01 23:20:51 +02:00
1806422763 stm32/test: add real defmt timestamp 2023-05-01 23:20:51 +02:00
00cde67abe stm32/dma: solve overlapping impl on DmaCtrl on stm32h7 2023-05-01 23:20:51 +02:00
96e8a7ddb9 stm32/uart: feature-gate ringbuffer out when using gpdma, not supported yet. 2023-05-01 22:43:23 +02:00
25864ae4dc stm32/bdma: add ringbuffer support. 2023-05-01 22:42:36 +02:00
14e0090cb1 stm32/dma: remove separate process_tcif. 2023-05-01 22:42:36 +02:00
45843034ec Actually clear idle flag 2023-05-01 22:42:36 +02:00
7757405908 Remove unused import 2023-05-01 22:42:36 +02:00
fc268df6f5 Support overflow detection for more than one ring-period 2023-05-01 22:42:36 +02:00
4ea6662e55 Do not disable dma request when idle line is detected 2023-05-01 22:42:36 +02:00
49455792cb Ring-buffered uart rx with one-period overrun detection 2023-05-01 22:42:36 +02:00
855c0d1423 Merge #1376
1376: rtc: cleanup and consolidate r=Dirbaio a=xoviat

This removes an extra file that I left in, adds an example, and consolidates the files into one 'v2' file.

Co-authored-by: xoviat <xoviat@users.noreply.github.com>
2023-05-01 19:32:06 +00:00
05c36e05f9 Merge #1414
1414: rp: report errors from buffered and dma uart receives r=Dirbaio a=pennae

neither of these reported errors so far, which is not ideal. add error reporting to both of them that matches the blocking error reporting as closely as is feasible, even allowing partial receives from buffered uarts before errors are reported where they would have been by the blocking code. dma transfers don't do this, if an errors applies to any byte in a transfer the entire transfer is nuked (though we probably could report how many bytes have been transferred).

Co-authored-by: pennae <github@quasiparticle.net>
2023-05-01 15:35:39 +00:00
b58b9ff390 rp/uart: report errors from dma receive 2023-05-01 15:36:53 +02:00
1d5adb8974 rp/uart: extract fifo draining from blocking_read
this will also be needed for dma operations.
2023-05-01 15:32:58 +02:00
be66e0f7ce rp/uart: make dma multicore-safe
running rx and tx on different cores could lead to hangs if the dmacr
register modifys run concurrently. this is bad.
2023-05-01 15:32:58 +02:00
861f49cfd4 rp/uart: report errors from buffered uart
this reports errors at the same location the blocking uart would, which
works out to being mostly exact (except in the case of overruns, where
one extra character is dropped). this is actually easier than going
nuclear in the case of errors and nuking both the buffer contents and
the rx fifo, both of which are things we'd have to do in addition to
what's added here, and neither are needed for correctness.
2023-05-01 15:32:58 +02:00
7ab9fe0522 rp/uart: extract common code from async and blocking buffered reads
once we add error propagation the common code will become even larger,
so it makes sense to move it out.
2023-05-01 15:24:03 +02:00
1c8492bab2 tests/rp: test error conditions for uart 2023-05-01 15:22:56 +02:00
19588a9e6f rp/uart: rename state to buffered_state
we'll add a dma state soon as well.
2023-05-01 15:22:39 +02:00
1d2f6667df rp/uart: add set-break functions
sending break conditions is necessary to implement some protocols, and
the hardware supports this natively. we do have to make sure that we
don't assert a break condition while the uart is busy though, otherwise
the break may be inserted before the last character in the tx fifo.
2023-05-01 15:16:30 +02:00
ac0ea406f9 Merge #1395
1395: rp/pio: bit of a rework r=Dirbaio a=pennae

the pio module is currently in a Bit of a State. this is far from all that's needed to make it more useful, but it's a start.

Co-authored-by: pennae <github@quasiparticle.net>
2023-05-01 11:00:48 +00:00
7336b8cd88 rp/uart: add UartRx::new_blocking 2023-05-01 13:00:40 +02:00
bcbe3040a1 tests/rp: fix buffered uart test
the rp uart receive fifo is 32 entries deep, so the 31 byte test data
fits into it without needing any buffering. extend to 48 bytes to fill
the entire fifo and the 16 byte test buffer.
2023-05-01 13:00:40 +02:00
f4ade6af8b rp/pio: write instr memory only from common
instruction memory is a shared resource. writing it only from PioCommon
clarifies this, and perhaps makes it more obvious that multiple state
machines can share the same instructions.

this also allows *freeing* of instruction memory to reprogram the
system, although this interface is not entirely safe yet. it's safe in
the sense rusts understands things, but state machines may misbehave if
their instruction memory is freed and rewritten while they are running.
fixing this is out of scope for now since it requires some larger
changes to how state machines are handled. the interface provided
currently is already unsafe in that it lets people execute instruction
memory that has never been written, so this isn't much of a drawback for now.
2023-05-01 12:58:57 +02:00
fa1ec29ae6 rp/pio: remove a bunch of unnecessary let _ = self 2023-05-01 12:58:57 +02:00
58e727d3b9 rp/pio: move non-sm-specific methods to PioCommon
pin and irq operations affect the entire pio block. with pins this is
not very problematic since pins themselves are resources, but irqs are
not treated like that and can thus interfere across state machines. the
ability to wait for an irq on a state machine is kept to make
synchronization with user code easier, and since we can't inspect loaded
programs at build time we wouldn't gain much from disallowing waits from
state machines anyway.
2023-05-01 12:58:57 +02:00
4cd5ed81aa rp/pio: remove top-level PIOS array
this mainly removes the need for explicit indexing to get the pac
object. runtime effect is zero, but arguably things are a bit easier to
read with less indexing.
2023-05-01 12:58:57 +02:00
4618b79b22 rp/pio: seal PioInstance, SmInstance
seems prudent to hide access to the internals.
2023-05-01 12:58:57 +02:00
db16b6ff3f rp/pio: don't call dma::init so much
this is already done during platform init. it wasn't even sound in the
original implementation because futures would meddle with the nvic in
critical sections, while another (interrupt) executor could meddle with
the nvic without critical sections here. it is only accidentally sound
now and only if irq1 of both pios isn't used by user code. luckily the
worst we can expect to happen is interrupt priorities being set wrong,
but wrong is wrong is wrong.
2023-05-01 12:58:57 +02:00
a9074fd09b rp/pio: enable pio interrupts only once
since we never actually *disable* these interrupts for any length of
time we can simply enable them globally. we also initialize all pio
interrupt flags to not cause system interrupts since state machine
irqa are not necessarily meant to cause a system interrupt when set. the
fifo interrupts are sticky and can likewise only be cleared inside the
handler by disabling them.
2023-05-01 12:58:57 +02:00
f2469776f4 rp/pio: use atomic accesses, not critical sections
atomic accesses are not only faster but also can't conflict with other
critical sections.
2023-05-01 12:53:32 +02:00
a10850a6da rp/pio: handle all pio irqs in one handler
dma does this too, also with 12 bits to check. this decreases code size
significantly (increasing speed when the cache is cold), frees up an
interrupt handler, and avoids read-modify-write cycles (which makes each
processed flag cheaper). due to more iterations per handler invocation
the actual runtime of the handler body remains roughly the
same (slightly faster at O2, slightly slower at Oz).

notably wakers are now kept in one large array indexed by the irq
register bit number instead of three different arrays, this allows for
machine code-level optimizations of waker lookups.
2023-05-01 12:53:32 +02:00
ce04b732d1 Merge #1407
1407: Remove legacy LoRa drivers r=Dirbaio a=ceekdee

Remove legacy LoRa drivers and associated configuration.

Co-authored-by: ceekdee <taigatensor@gmail.com>
Co-authored-by: Chuck Davis <taigatensor@gmail.com>
2023-04-30 19:36:36 +00:00
ff6748a0d8 Merge branch 'embassy-rs:master' into master 2023-04-30 11:09:43 -05:00
7646f18836 Merge #1405
1405: add IPCC peripheral for stm32wb r=xoviat a=OueslatiGhaith

Hello again,

This pull request is related to #1397 and #1401, inspired by #24, and was tested on an stm32wb55rg.

This pull request aims to add the IPCC peripheral for stm32wb microcontrollers.
I am debating whether this should be included in the public API, since the IPCC peripheral would be typically managed by the TL Mailbox, not by the app directly.

Co-authored-by: OueslatiGhaith <ghaith.oueslati@enis.tn>
2023-04-30 15:23:55 +00:00
41fe718ea8 Merge #1412
1412: stm32/uart: abort on error r=Dirbaio a=xoviat

This PR aborts the DMA transfer in the event of a UART error. Otherwise, the transfer will never complete, and an error will not be returned.

Co-authored-by: xoviat <xoviat@users.noreply.github.com>
2023-04-30 14:58:36 +00:00
94c6727b3f Merge #1410
1410: Add `Transactional` trait to rp's i2c impl r=Dirbaio a=DasLixou

Fixes #1409 

Co-authored-by: Lixou <82600264+DasLixou@users.noreply.github.com>
Co-authored-by: xoviat <49173759+xoviat@users.noreply.github.com>
2023-04-30 07:52:49 +00:00
b77794c9a7 stm32/uart: abort on error 2023-04-28 21:43:03 -05:00
ba886b45b8 rustfmt 2023-04-28 16:46:32 -05:00
2119b8e1ca Add Transactional trait to rp's i2c impl 2023-04-28 21:23:32 +02:00
49bed094a3 Merge branch 'embassy-rs:master' into master 2023-04-28 13:35:22 -05:00
49ecd8d7c5 Remove external-lora-phy feature. 2023-04-28 13:33:20 -05:00
29cc661dca removed constrain method 2023-04-28 10:17:01 +01:00
3e730aa8b0 Merge #1403
1403: Bump versions preparing for -macros and -executor release r=lulf a=lulf

I'd like to propose a new release of embassy-macros and embassy-executor, as there is a challenge with some of the features changing since 0.1.1 when using libraries that depend on 0.1.1 with applications that patch to use git versions.

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2023-04-27 18:20:10 +00:00
28a3454846 add changelog 2023-04-27 20:19:07 +02:00
91cddd50f6 reversed changes in Cargo.toml 2023-04-27 18:26:19 +01:00
9d610c6866 Remove legacy LoRa drivers. 2023-04-27 11:05:33 -05:00
03d6363d5a Merge #1406
1406: rp: DMA behaviour during flash operations r=Dirbaio a=kalkyl

This PR changes the old behaviour during flash operations where all DMA transfers were paused during the flash operation.
The new approach is to wait for any DMA operating in flash region to finish and let RAM transfers continue.

Co-authored-by: kalkyl <henrik.alser@me.com>
2023-04-27 15:28:11 +00:00
d960bf344a fixed missing imports 2023-04-27 16:22:41 +01:00
31b54e0fbd rustfmt 2023-04-27 17:09:16 +02:00
3ba73b5ff4 fixed mistake with casting channel to a usize 2023-04-27 16:08:57 +01:00
8c733c29cc add IPCC peripheral for stm32wb 2023-04-27 16:03:22 +01:00
278818395e rp: DMA behaviour during FLASH operations 2023-04-27 16:48:25 +02:00
42a8f1671d Bump versions preparing for -macros and -executor release 2023-04-27 11:54:22 +02:00
1cf26f0eb3 Merge #1402
1402: rp: remove pio Cargo feature. r=Dirbaio a=Dirbaio

We shouldn't have Cargo features if their only purpose is reduce cold build time a bit.

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-26 20:39:55 +00:00
d91c37dae3 rp: remove pio Cargo feature.
We shouldn't have Cargo features if their only purpose is reduce cold build time a bit.
2023-04-26 22:39:24 +02:00
759d911b50 Merge #1396
1396: Add an external LoRa physical layer feature r=Dirbaio a=ceekdee

The original LoRa drivers have been deprecated and examples associated with them deleted; however, the original LoRa drivers are still available to allow a gentle transition to the external lora-phy crate.

Co-authored-by: ceekdee <taigatensor@gmail.com>
Co-authored-by: Chuck Davis <taigatensor@gmail.com>
Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2023-04-26 20:10:40 +00:00
a277deeaa5 Merge pull request #1 from lulf/build-fixes
build fixes for stable
2023-04-26 14:57:33 -05:00
cb00fb18cb Merge #1400
1400: Add support for setting up the nRFs internal DCDCs r=Dirbaio a=korken89



Co-authored-by: Emil Fresk <emil.fresk@gmail.com>
2023-04-26 19:49:26 +00:00
f8b359dc5a Add support for setting up the nRFs internal DCDCs 2023-04-26 21:24:50 +02:00
edef790e1a build fixes for stable 2023-04-26 20:45:59 +02:00
18af9f304a Merge branch 'embassy-rs:master' into master 2023-04-26 12:26:19 -05:00
deadf40c85 Merge pull request #1399 from mbrieske/probe-rs-stm32f7example
fix stm32f7 example runner command for probe-rs-cli
2023-04-26 19:01:28 +02:00
405649ddc7 fix stm32f7 example runner command for probe-rs-cli 2023-04-26 18:58:28 +02:00
e2410cbb6a Merge pull request #1398 from embassy-rs/probe-rs
Switch from probe-run to probe-rs-cli.
2023-04-26 18:07:56 +02:00
52decfb16c Add nightly feature specification for lora-phy. 2023-04-26 10:51:02 -05:00
91047c61b9 Correct nightly feature specification. 2023-04-26 10:18:40 -05:00
054ca17f66 Switch from probe-run to probe-rs-cli.
- probe-run screwed up the last release 2 weeks ago and it's still not fixed (issue 391). Doesn't look well maintained.
- Even when it's not broken, it lags behind probe-rs-cli in new chips support because it's slow in updating probe-rs.
2023-04-26 17:00:51 +02:00
0d82ebea29 stm32/rtc: fix datetime and add f4 test 2023-04-25 17:35:01 -05:00
f729d2d060 Deprecate original LoRa drivers. Update rust-lorawan releases. 2023-04-25 13:51:19 -05:00
73f25093c7 Add lora-phy examples. 2023-04-23 18:32:34 -05:00
a3f727e2e1 Merge branch 'embassy-rs:master' into master 2023-04-23 16:43:45 -05:00
0dea7b02d6 Merge #1387
1387: rp: add PWM api r=Dirbaio a=pennae

add PWM api ~~including interrupts and async support.~~

depends on https://github.com/embassy-rs/rp-pac/pull/1

**TODO**:

- [x] example
- [x] test
- [x] move divmode to typelevel
- [x] deduplicate `new_*` functions

Co-authored-by: pennae <github@quasiparticle.net>
2023-04-23 20:50:57 +00:00
a4866ad278 rp: add PWM api 2023-04-23 22:49:15 +02:00
d78edba0d4 Merge #1392
1392: embassy-rp : Fix for division intrinsics clashing with rp2040-hal r=Dirbaio a=peterkrull

Commit [7a682ec](7a682ec02a (diff-f121955242a67342004444b26214e5d1d591c3182dcd0fedf4329ad472cd1200)) may break compilation if also using `rp2040-hal`. It seems that the rp2040-hal does have a feature flag for [disabling intrinsics](2c9921cdc5/rp2040-hal/src/sio.rs (L323)), but I still cannot seem to compile with that enabled. Adding these flags fixes it for me.

Co-authored-by: Peter Krull <peterkrullpeter@gmail.com>
2023-04-23 18:54:28 +00:00
b283f213d9 embassy-rs : @pennae Fix division intrinsics naming clash with rp2040-hal 2023-04-23 19:05:32 +02:00
ba47fe9c41 embassy-rp : Added feature flag to otherwise unused definitions 2023-04-23 16:37:44 +02:00
8285263fc2 embassy-rp : Added intrinsic feature flag to global_asm macro 2023-04-23 15:59:56 +02:00
cc5bca8e83 Added feature flag to embassy-rp intrinsics to avoid conflicts with rp2040-hal 2023-04-23 15:50:46 +02:00
0a2f7b4661 Use released lora-phy. 2023-04-21 17:41:25 -05:00
02c86bca52 Add external LoRa physical layer functionality. 2023-04-21 01:20:46 -05:00
fb27594b2e Merge #1383
1383: embassy-boot: Add nightly flag r=Dirbaio a=sawi97

This adds "nightly" as a flag to embassy-boot and embassy-boot-nrf which gates features requiring nightly, enabled by default.
Makes it possible to build the bootloader with the stable compiler when setting `default-features=false`.

It should be straight forward to do this for stm32 and rp as well, but I am not been able to test it.

Co-authored-by: sander <sander.wittwer@dengineering.no>
Co-authored-by: sawi97 <34313578+sawi97@users.noreply.github.com>
2023-04-20 09:19:54 +00:00
a73f9474a0 embassy-boot: ensure tests can run on the stable compiler 2023-04-20 10:56:59 +02:00
3bf41e9a06 ci: ad nightly flag to embassy-boot tests 2023-04-20 10:47:40 +02:00
0e01b28d5e embassy-boot: resolve conflicts 2023-04-20 10:40:40 +02:00
8aca324c2d Merge commit '2c1d572cf2e225be5f30435b133e96aa55c9d3af' 2023-04-20 10:38:54 +02:00
7ee9e8322c Merge commit '970a081aab0567a387463610eb204a3b003255f9' 2023-04-20 10:36:15 +02:00
43c20dbe65 Merge branch 'embassy-rs:master' into embassy-boot-stable 2023-04-20 10:29:16 +02:00
8cd117fd5d embassy-boot: update readme MSRV to stable 2023-04-20 10:26:02 +02:00
f64d1131b6 embassy-boot: update ci and examples to use the nightly flag 2023-04-20 10:22:44 +02:00
b153a5b0d7 embassy-boot: add nightly feature to stm32 and rp as well 2023-04-20 10:04:41 +02:00
9b51c8f4d4 Merge #1385
1385: feat: add embassy-boot-rp to the doc builder r=lulf a=elpiel



Co-authored-by: Lachezar Lechev <elpiel93@gmail.com>
2023-04-20 07:12:35 +00:00
510ae7e3dc Merge commit 'eecc41c2e4911c5f1cd232339999424760de9f06' 2023-04-20 08:45:58 +02:00
f67eb84ec7 chore: add embassy-boot-rp to README
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-04-20 09:20:02 +03:00
5de6bb3adf feat: add embassy-boot-rp to the doc builder
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-04-20 09:19:26 +03:00
54fe50c685 Merge #1384
1384: rp: optimize rom-func-cache for runtime r=Dirbaio a=pennae

storing a full function pointer initialized to a resolver trampoline lets us avoid the runtime cost of checking whether we need to do the initialization. this also slightly reduces flash usage due to a slightly more space-efficient initialization procedure.

Co-authored-by: pennae <github@quasiparticle.net>
2023-04-19 22:26:59 +00:00
837cdacd16 rp: optimize rom-func-cache for runtime
storing a full function pointer initialized to a resolver trampoline
lets us avoid the runtime cost of checking whether we need to do the
initialization.
2023-04-20 00:07:18 +02:00
41e90e22e2 Merge #1370
1370: stm32/i2c: fix races when using dma. r=Dirbaio a=xoviat

This change addresses two races:

1. It removes the `chunks_transferred` state variable that is modified inside the interrupt. Analysis of the code reveals that the only time the waker can be woken is when `chunks_transferred` is incremented. Therefore, waking is enough to signal the `poll_fn` that the `chunks_transferred` has incremented. Moving to `remaining_len` clarifies the code, since there is no need to track how many chunks are remaining.
2. It moves the start of the transfer until after the waker is registered, which could theoretically occur if the clock speed is very low, but probably never would even if this wasn't fixed.

There is another race that I noticed: between writes the waker may not yet be registered. In that case, the code would simply be stuck and the `poll_fn` would never be woken. There is no way to resolve this without broadening the scope of the analysis, and this will likely never occur. 

Co-authored-by: xoviat <xoviat@users.noreply.github.com>
2023-04-19 21:36:04 +00:00
64b80c2e4d stm32/i2c: ignore wakes without interrupt 2023-04-19 16:16:44 -05:00
26f4d7d283 Merge #1382
1382: rp: hook up softfloat intrinsics to bootrom r=Dirbaio a=pennae

rp-hal has done this very well already, so we'll just copy their entire impl again. only div.rs needed some massaging because our sio access works a little differently, everything else worked as is.

includes a minor bit of refactoring to make it easier to check which bits we've copied from rp2040-hal and which we haven't.

Co-authored-by: pennae <github@quasiparticle.net>
2023-04-19 21:10:35 +00:00
fdd6e08ed6 rp: hook up softfloat rom intrinsics
rp-hal has done this very well already, so we'll just copy their entire
impl again. only div.rs needed some massaging because our sio access
works a little differently, everything else worked as is.
2023-04-19 23:04:47 +02:00
7d64de153f Merge commit 'cbe076d763d97f715605d25d8f8815e299c45d46' 2023-04-19 15:41:00 +02:00
e24421a393 stm32/rtc: impl. functions on trait 2023-04-18 20:38:51 -05:00
4de4039417 stm32/rtc: build more chips 2023-04-18 20:38:28 -05:00
f589247c1f stm32/rtc: cleanup and consolidate 2023-04-18 20:38:18 -05:00
37181c79d9 Merge #1380
1380: Add embassy-net without dhcp to ci.sh r=Dirbaio a=royb3



Co-authored-by: Roy Buitenhuis <roy.buitenhuis94@gmail.com>
2023-04-18 20:28:39 +00:00
a2ac1eed1b Add extra feature flags to fix build without dhcp. 2023-04-18 22:11:15 +02:00
bfa3cbaf30 Add embassy-net without dhcp to ci.sh 2023-04-18 21:47:28 +02:00
216b120f15 Merge #1379
1379: enable inline-asm feature for cortex-m in examples r=Dirbaio a=pennae

inline assembly is supported since rust 1.59, we're way past that. enabling this makes the compiled code more compact, and on rp2040 even decreses memory usage by not needing thunks in sram.

Co-authored-by: pennae <github@quasiparticle.net>
2023-04-18 19:25:49 +00:00
08d9e5981e Merge #1345
1345: Added a neopixel constructor to spi, with an example in the stm32g0 d… r=Dirbaio a=smeenka

For Spi I added the possibility to use the Spi device as a Neopixel driver, with very precise timing, and not dependent on software during the transfer. Even without the --release flag, the timing is perfect.
Note that between the bursts of data the Mosi line should stay on low level. A resistor of 10k can guarantee that, as it seems the the pin is floating between the bursts (on the nucleo-G070RB platform I use).
I created an example for the STM32G0 family.
This example  does contain a very simple Neopixel driver, only for one type. But this Neopixel driver can easy be adapted to different types (for example RGBW types).


Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Co-authored-by: anton smeenk <asmeenk@planet.nl>
2023-04-18 19:08:04 +00:00
8a9136e4e4 enable inline-asm feature for cortex-m in examples
inline assembly is supported since rust 1.59, we're way past that.
enabling this makes the compiled code more compact, and on rp2040
even decreses memory usage by not needing thunks in sram.
2023-04-18 21:07:36 +02:00
3260f6b2af stm32/spi: add new_txonly_nosck constructor, for neopixels, with an example in the stm32g0 directory. 2023-04-18 20:59:25 +02:00
2080d8bb6d stm32/spi: add support for all word sizes.
Co-Authored-By: anton smeenk <asmeenk@planet.nl>
2023-04-18 20:56:23 +02:00
a673b9aa29 Merge pull request #1367 from embassy-rs/update-nightly3
Update nightly.
2023-04-18 18:06:46 +02:00
38c5b97df0 Merge #1378
1378: Add ability to invert UART pins, take 2 r=Dirbaio a=jakewins

Same PR as before, except this now works :) 

There was a minor hiccup in the UartRx code where the rx pin got passed as the tx argument, so the invert settings didn't get applied. With this fix, my local setup at least is happily reading inverted uart data.

Co-authored-by: Jacob Davis-Hansson <jake@davis-hansson.com>
2023-04-18 15:48:47 +00:00
21ea98810a Pass rx pin to right init arg 2023-04-18 17:44:19 +02:00
fbd6eeb748 Merge pull request #1348 from embassy-rs/h5-spi
stm32/dma: refactor
2023-04-18 17:03:24 +02:00
dbded7a6ce Update nightly.
Includes this TAIT breaking change. https://github.com/rust-lang/rust/pull/110237
2023-04-18 16:59:09 +02:00
efc70debb3 stm32/dma: add double buffered mode for DMA, update DCMI. 2023-04-18 16:41:24 +02:00
173c65b543 stm32/dma: refactor. 2023-04-18 16:37:35 +02:00
a86a100879 Merge #1377
1377: (embassy-stm32): implement embedded-storage traits for full flash struct r=MathiasKoch a=MathiasKoch



Co-authored-by: Mathias <mk@blackbird.online>
2023-04-18 14:05:15 +00:00
bba8b0ded5 Missing semi-colon 2023-04-18 16:03:55 +02:00
095f5ef279 Add MAX_ERASE_SIZE const in build script, and use it in flash-wide implementation of embedded-storage traits 2023-04-18 15:49:33 +02:00
1c68c62ebd Implement embedded-storage traits for full flash struct 2023-04-18 13:48:37 +02:00
f5216624bb stm32/i2c: fix races when using dma.
fixes #1341.
2023-04-17 15:24:24 -05:00
46227bec1e Merge pull request #1375 from embassy-rs/stm32-sdmmc-refactor
stm32/sdmmc: refactor, simplify code, add HIL test
2023-04-17 22:14:19 +02:00
e63a34ba21 stm32/sdmmc: add hil test for f4. 2023-04-17 21:49:34 +02:00
82dd7a5f8c stm32/sdmmc: add init_card retry. 2023-04-17 21:48:47 +02:00
0dfa192992 stm32/sdmmc: remove "inner" layer. 2023-04-17 19:23:18 +02:00
e14fa11fc3 stm32/sdmmc: remove unneeded pointer casts. 2023-04-17 17:52:02 +02:00
df7ef1d98f stm32/sdmmc: remove cfg_if. 2023-04-17 17:52:02 +02:00
9202dbf32a Merge #1369
1369: Lora AFIT r=Dirbaio a=Dirbaio

Extracted out of #1367 

Probably we should wait until `rust-lorawan` is merged+released?

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2023-04-17 13:50:49 +00:00
4044d728a6 update to released versions 2023-04-17 15:44:58 +02:00
6acc361109 Merge #1371 #1374
1371: RTC r=Dirbaio a=xoviat

This adds RTC for most of the stm32 chips. Nearly all of the work was not done by me, but I took it the last bit by disabling the chips that weren't working. I think it would be easier to enable them in future PRs if requested.

1374: stm32: remove TIMX singleton when used on timer driver r=Dirbaio a=xoviat

After multiple ways of looking at this, this is the best solution I could think of.

Co-authored-by: Mathias <mk@blackbird.online>
Co-authored-by: xoviat <xoviat@users.noreply.github.com>
2023-04-17 01:29:05 +00:00
27ec29e2c5 stm32/rtc: remove unused import 2023-04-16 19:32:15 -05:00
90c1422381 stm32/rtc: remove chrono datetime and add converters 2023-04-16 19:30:42 -05:00
9e1ddeac86 stm32: fix defective example 2023-04-16 18:32:55 -05:00
99dcbf00c4 Merge #1372
1372: rp: add division intrinsics r=Dirbaio a=pennae

rp2040-hal adds division intrinsics using the hardware divider unit in the SIO, as does the pico-sdk itself. using the hardware is faster than the compiler_rt implementations, and more compact too.

since embassy does not expose the hardware divider in any way (yet?) we could go even further an remove the state-saving code rp2040-hal needs, but that doesn't seem to be worth it.

Co-authored-by: pennae <github@quasiparticle.net>
2023-04-16 23:23:47 +00:00
776e001b5b stm32: remove TIMX singleton when used on timer driver
fixes #1316.
2023-04-16 17:47:25 -05:00
6ba2bb1a7f Merge #1373
1373: rp: switch to released rp-pac v1.0 r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-16 22:00:07 +00:00
a258e15c23 rp: switch to released rp-pac v1.0 2023-04-16 23:59:26 +02:00
7a682ec02a rp: add division intrinsics
rp2040-hal adds division intrinsics using the hardware divider unit in
the SIO, as does the pico-sdk itself. using the hardware is faster than
the compiler_rt implementations, and more compact too.
2023-04-16 19:45:18 +02:00
e9ede443bc stm32/rtc: disable nonworking versions 2023-04-16 11:14:17 -05:00
bc550cbfda adjust .vscode file 2023-04-16 11:06:24 -05:00
8da9c07a65 stm32/rtc: disable nonworking versions 2023-04-16 11:06:05 -05:00
bd6bb2d248 Merge branch 'embassy-stm32/rtc' of https://github.com/MathiasKoch/embassy into rtc 2023-04-16 10:06:00 -05:00
81f10e136a outover instead of inover 2023-04-15 15:13:44 +02:00
1fdce6e52a Merge #1360 #1361
1360: stm32/rcc: add i2s pll on some f4 micros r=Dirbaio a=xoviat

Adds the i2s pll on some f4 micros. 

1361: Executor: Replace unnecessary atomics in runqueue r=Dirbaio a=GrantM11235

Only the head pointer needs to be atomic. The `RunQueueItem` pointers are only loaded and stored, and never concurrently

Co-authored-by: xoviat <xoviat@users.noreply.github.com>
Co-authored-by: Grant Miller <GrantM11235@gmail.com>
2023-04-15 10:38:28 +00:00
f395ec44e8 stm32/rcc: add pllsai clock 2023-04-14 21:28:27 -05:00
63941432e3 Update to rust-lorawan with afit support 2023-04-15 01:00:12 +02:00
be0f93ff37 Merge #1368
1368: AFIT cleanup r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-14 22:59:49 +00:00
224eaaf797 stm32/sdmmc: switch to AFIT. 2023-04-15 00:58:58 +02:00
f681b9d4e5 Remove the _todo_embedded_hal_serial impls. EH will probably not have these serial traits. 2023-04-15 00:58:58 +02:00
650589ab3f stm32/rcc: add plli2s to Clocks and cfg directives 2023-04-14 16:30:36 -05:00
b9fc2a6b33 Add ability to invert UART pins
This is useful in some cases where the surrounding circuit
for some reason inverts the UART signal, for instance if you're talking
to a device via an optocoupler.
2023-04-14 21:08:24 +02:00
3002ee0dcf embassy-boot: add nightly feature gate for async usage 2023-04-14 11:27:23 +02:00
ce0e1a5db3 Merge commit '82f528927b2fde275c2e9b6fd737baf439cb296a' 2023-04-14 10:44:51 +02:00
a3ecf5caf6 Merge pull request #1363 from embassy-rs/embassy-time-released
time: remove embassy-sync dep, release v0.1.1
2023-04-14 00:19:33 +02:00
a7629299f4 Release embassy-time v0.1.1 2023-04-13 23:57:20 +02:00
e7ff759f1c time: remove dependency on embassy-sync. 2023-04-13 23:57:20 +02:00
62ecd97350 Merge pull request #1362 from embassy-rs/embassy-sync-v0.2.0
Release embassy-sync v0.2.0
2023-04-13 23:56:32 +02:00
577f060d24 Release embassy-sync v0.2.0 2023-04-13 23:40:49 +02:00
6a6c673c5f Executor: Replace unnecessary atomics in runqueue 2023-04-13 14:21:41 -05:00
c1d5f86871 stm32/rcc: fix warnings 2023-04-12 18:11:55 -05:00
0289630fe4 stm32/rcc: add i2s pll on some f4 micros 2023-04-12 18:04:44 -05:00
5a03b2e9e8 Merge #1359
1359: Make Hertz constructors `const` r=Dirbaio a=sgoll

This PR makes `Hertz` associated functions `hz()`, `khz()`, `mhz()` and their unassociated variants `const`, allowing `Hertz` to be used more easily in constant values:

```rust
const FREQ1: Hertz = Hertz::khz(120);
const FREQ2: Hertz = mhz(1);
```

This follows the pattern used for similar types such as `Duration` and `Instant`, from `embassy-time/src/duration.rs` and `embassy-time/src/instant.rs`, respectively.

ba8cafb20c/embassy-time/src/duration.rs (L44-L47)

ba8cafb20c/embassy-time/src/instant.rs (L29-L34)

Co-authored-by: Sebastian Goll <sebastian.goll@gmx.de>
2023-04-12 22:13:44 +00:00
4863f88d02 Make Hertz constructors const
This allows them to be used in constant values.
2023-04-13 00:06:14 +02:00
ba8cafb20c Merge #1358
1358: Fix typo in derivation of PLLP divisor for STM32F2 family r=Dirbaio a=sgoll

This PR fixes a typo in the derivation of the PLLP divisor for the STM32F2 family.

Fixes #1357 

Co-authored-by: Sebastian Goll <sebastian.goll@gmx.de>
2023-04-12 01:09:41 +00:00
f3699e67b9 Fix typo in derivation of PLLP divisor 2023-04-12 02:07:31 +02:00
201a038134 Merge pull request #1356 from embassy-rs/peripheralref-no-mut
Do not require mut in PeripheralRef clone_unchecked, make nRF timer `cc()` borrows less strict.
2023-04-12 00:03:46 +02:00
8fd8ef9ca7 nrf/timer: make cc() borrows less strict. 2023-04-11 23:09:02 +02:00
9a677ab618 common/peripheral: do not require mut in PeripheralRef clone_unchecked. 2023-04-11 23:09:02 +02:00
5c42ca13bd Merge #1353
1353: Add empty test binary for riscv r=Dirbaio a=royb3

As discussed with `@Dirbaio,` this empty test binary should cause a build to fail when it is not possible to build or link a riscv binary.

Co-authored-by: Roy Buitenhuis <roy.buitenhuis@technolution.nl>
2023-04-11 16:20:08 +00:00
f426c47747 Remove empty line, causing build issues. 2023-04-11 17:40:05 +02:00
6e947c83b6 Move linker flags to build script. 2023-04-11 17:22:47 +02:00
e183801957 Rustfmt 2023-04-11 17:04:25 +02:00
00258bca43 Add empty test binary for riscv 2023-04-11 16:53:04 +02:00
813bba200f Merge #1352
1352: re-export main_riscv macro as main for riscv arch. r=Dirbaio a=royb3

embassy_executor::main was missing for riscv targets.

Co-authored-by: Roy Buitenhuis <roy.buitenhuis@technolution.nl>
2023-04-11 13:05:31 +00:00
32836129f6 re-export main_riscv macro as main for riscv arch. 2023-04-11 14:59:38 +02:00
f06554cf1d Merge pull request #1351 from embassy-rs/c0-hil
stm32/tests: add C0 hil tests.
2023-04-11 14:53:36 +02:00
f5df567619 stm32/test: add C0 hil tests. 2023-04-11 14:16:32 +02:00
1b86570cfd embassy-boot: readd nightly feature as default 2023-04-11 13:55:19 +02:00
f51cbebffd embassy-boot: add nightly feature gates 2023-04-11 13:49:32 +02:00
c309797488 merge embassy/master 2023-04-11 13:48:34 +02:00
35a34afbc2 Update nightly. 2023-04-11 13:38:36 +02:00
b150b9506b Merge #1350
1350: Fix duplicate package name `embassy-stm32h7-examples` r=lulf a=sgoll

This uses the correct package name for the `stm32h5` example.

Fixes #1349 

Co-authored-by: Sebastian Goll <goll@hmi-project.com>
2023-04-11 10:08:58 +00:00
ab6179fb07 Fix duplicate package name 2023-04-11 12:03:08 +02:00
636a3d05c2 Merge #1331
1331: Let bootloader partition be u32 r=rmja a=rmja

This is probably controversial but hear me out:)

The idea about changing from usize to u32 is to enable support for 16 bit mcu's with large flash, e.g. MSP430F5529. Its usize is only 16 bit, but its flash is larger than 64k. Hence, to address its entire flash, it needs the flash address space to be u32.

Missing from the PR is `update_len` in the verification methods. There is currently [a different PR](https://github.com/embassy-rs/embassy/pull/1323) that contains changes in those methods, and I will align depending on the merge order of the two.

The general distinction between u32 and usize is:
* If it is a size or address that only ever lives in flash, then it is u32.
* If the offset or size is ever representable in memory, then usize.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-11 06:17:00 +00:00
d3ce64254a Let update_len be u32 2023-04-11 07:46:05 +02:00
d8c92c53d6 Merge remote-tracking branch 'upstream/master' into u32-partition 2023-04-11 07:36:23 +02:00
1f25d2ba83 Merge pull request #1347 from embassy-rs/h5-spi
stm32h5: add spi support, fix DMA hang, add HIL tests.
2023-04-10 21:27:44 +02:00
cae683cd41 stm32: update pac. 2023-04-10 21:12:48 +02:00
53c60df997 Merge #1346
1346: fix I2C controller problems after NACK r=Dirbaio a=Juravenator

While tinkering with I2C on a NUCLEO-H723ZG, I noticed that when trying to communicate with a non-existent device you do receive a proper NACK error, but afterwards any future communications with any device no longer works as expected.

The use case is auto-detection of devices, in this case a series of Adafruit 24LC32 I2C EEPROM boards.

On closer inspection with a logic analyzer, I observed that after the NACK, any data bytes sent out by the board to the devices are just zeros. Even though the embassy code specifies the correct data in `set_txdata` in `write_internal`. Something seems to be going wrong with the controller or buffers on the board itself.

Then I noticed what seems to be a logic error in `flush_txdr`, which is called when issuing a NACK.

After flipping the if statement, I2C communications keep working as expected after issuing a NACK.

Co-authored-by: Glenn Dirkx <glenn.dirkx@gmail.com>
2023-04-10 14:49:12 +00:00
6760258ec3 fix I2C controller problems after NACK 2023-04-10 16:20:47 +02:00
dbfd28130f stm32/test: add h5 hil tests. 2023-04-10 15:25:11 +02:00
4ef8e008e8 stm32/spi: add v4/v5 support (for H5). 2023-04-10 15:25:11 +02:00
44b7fe45e2 stm32/gpdma: fix race condition when resetting channel when done. 2023-04-10 15:11:07 +02:00
df17a88448 Merge #1342
1342: feat(rp): add `Wait` impl to `OutputOpenDrain` r=Dirbaio a=TheLostLambda

A while ago `OutputOpenDrain` was made to implement `InputPin`, something that allowed drivers for various one-wire protocols to be written, but it's been lacking a `Wait` implementation — something that's needed to write async versions of these drivers.

This commit also adds `get_level()` to `OutputOpenDrain`, since `is_high()` and `is_low()` were already implemented, but `get_level()` itself was missing.

Co-authored-by: Brooks J Rady <b.j.rady@gmail.com>
2023-04-09 20:50:08 +00:00
dee8c71a2d lora: fix embassy docs build. 2023-04-09 22:40:09 +02:00
1fbb8f0b32 feat(rp): add Wait impl to OutputOpenDrain
A while ago `OutputOpenDrain` was made to implement `InputPin`,
something that allowed drivers for various one-wire protocols to be
written, but it's been lacking a `Wait` implementation — something
that's needed to write async versions of these drivers.

This commit also adds `get_level()` to `OutputOpenDrain`, since
`is_high()` and `is_low()` were already implemented, but `get_level()`
itself was missing.
2023-04-09 09:15:57 +01:00
047ea9066f Merge #1335
1335: Misc stm32 fixes r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-07 00:29:18 +00:00
f38899728c stm32: add h5 flavor. 2023-04-07 02:28:36 +02:00
8469a2409c stm32/otg: add U5 support. 2023-04-07 02:28:36 +02:00
dee1d51ad3 stm32: remove subghz feature.
It's available only on WL. if you're using a WL, you want subghz for sure.
2023-04-07 02:28:36 +02:00
da8258b767 Merge #1330
1330: stm32/pwm: add complementary pwm r=Dirbaio a=xoviat

This implements complementary PWM with dead time on many supported targets. The specific dead-time programming functions are passed through directly to the user, which is a bit ugly but the best compromise I could reach for now.

Co-authored-by: xoviat <xoviat@users.noreply.github.com>
2023-04-06 21:33:17 +00:00
60809edf2c Merge #1306
1306: Update embedded-hal r=Dirbaio a=Dirbaio

- [x] Wait for merge https://github.com/rust-embedded/embedded-hal/pull/443
- [x] Wait for release
- [x] embassy-embedded-hal
- [x] embassy-nrf
- [x] embassy-stm32
- [x] embassy-rp

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-06 21:06:54 +00:00
be37eee13d Update embedded-hal crates. 2023-04-06 22:41:50 +02:00
c9b7dbc541 Merge #1334
1334: executor: fix doc features. r=Dirbaio a=Dirbaio

Forgot to update them in #1321 

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-06 20:40:17 +00:00
e2516bba09 executor: fix doc features. 2023-04-06 22:39:36 +02:00
f3ec6080bf Merge #1332
1332: executor: Replace unsound critical sections with atomics r=Dirbaio a=GrantM11235

I couldn't figure out the correct orderings, so I just left them as SeqCst for now.

Co-authored-by: Grant Miller <GrantM11235@gmail.com>
2023-04-06 18:01:21 +00:00
89279dcdc9 Merge #1333
1333: STM32: Adc V1 r=Dirbaio a=GrantM11235

Based on #947

Co-authored-by: Matthew W. Samsonoff <matt.samsonoff@gmail.com>
Co-authored-by: Grant Miller <GrantM11235@gmail.com>
2023-04-06 17:16:50 +00:00
d9696bd212 Merge pull request #1300 from embassy-rs/h5
Add support for STM32H5.
2023-04-06 19:14:02 +02:00
611d023829 stm32: add H5 support. 2023-04-06 18:59:37 +02:00
9f28d80977 stm32/usb: add support for 32bit usbram. 2023-04-06 18:59:37 +02:00
52cab3a9f4 Merge #1323
1323: Add hash functions to FirmwareUpdater r=Dirbaio a=rmja

This adds support for computing any hash over the update in the dtu area by providing a closure to the hash update function.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-05 23:43:24 +00:00
31ef783ac1 stm32/pwm: fix unused import 2023-04-05 18:18:05 -05:00
9f1dac3f5d stm32/pwm: add complementary pwm example 2023-04-05 18:07:07 -05:00
7677268319 stm32/pwm: cleanup and fix complementary pwm 2023-04-05 17:50:23 -05:00
0ef419bee4 Change ADC1 to ADC 2023-04-05 16:52:32 -05:00
92e96bd601 Fix typo 2023-04-05 16:38:06 -05:00
7c53ebd576 Fix example reference voltage 2023-04-05 16:28:28 -05:00
20e7b5e296 InternalChannel 2023-04-05 16:11:21 -05:00
37d8f2e512 Properly enable and reset adc 2023-04-05 15:28:42 -05:00
efd9e18321 Fix example 2023-04-05 15:12:27 -05:00
f588105429 wip 2023-04-05 15:01:31 -05:00
28b8ac4b62 Update STM32F0 ADC example to use read_internal 2023-04-05 14:34:24 -05:00
511a951246 Differentiate between read and read_internal for STM32F0 ADC
The internal channels (vbat, vref, and temperature) are not real pins and do
not have the `set_as_analog` method. They must be read using the
`read_internal` method.
2023-04-05 14:34:24 -05:00
a0b6096610 Put ADC input pin into analog mode 2023-04-05 14:34:24 -05:00
7e9e628eb9 Add ADC example for STM32F0 2023-04-05 14:34:24 -05:00
5d9ae3dbdb Add implementation of STM32 v1 ADC 2023-04-05 14:34:24 -05:00
8290236ed6 executor: Replace unsound critical sections with atomics 2023-04-05 13:23:12 -05:00
eed2b12325 Merge #1297
1297: feat(stm32): Support multiple flash regions r=Dirbaio a=rmja

This depends on https://github.com/embassy-rs/stm32-data/pull/176

This is a general overhaul of the flash module to support multiple erase sizes.
Overall this PR contains:
* Move complex sector computation to embassy-hal-common to allow for tests
* Implement `FlashRegion` trait for each available flash region
* Add Flash::into_regions() to get each region.
* Implement embedded-storage traits for each region to support different erase sizes
* Split family write operations into begin/do/end
* Protection against simultaneous writes/erases for each split region is done through a global mutex

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-05 11:12:40 +00:00
2a49e11cb0 Align flash examples 2023-04-05 10:55:31 +02:00
57d3d4d581 Align stm32 bootloader example 2023-04-05 10:29:45 +02:00
95b31cf2db Remove Drop on Flash and FlashLayout and propage lifetime to region types
This allows the user to "split" the FlashRegions struct into each region
2023-04-05 10:27:13 +02:00
05b2b2fb5f Align platform specific bootloaders 2023-04-05 09:56:40 +02:00
7e5ead78fe Remove firmware_len 2023-04-05 08:28:46 +02:00
2deb2c624c Let Partition range be u32 instead of usize 2023-04-05 08:28:31 +02:00
d8e2f82569 Let update_len be usize for now 2023-04-05 07:11:52 +02:00
043b3072c4 Merge remote-tracking branch 'upstream/master' into incremental-hash 2023-04-05 06:55:38 +02:00
991b22b6a1 stm32/pwm: add complementary pwm 2023-04-04 19:35:25 -05:00
e2e15e436a Merge remote-tracking branch 'upstream/master' into flash-regions 2023-04-04 23:16:58 +02:00
3deb65bc87 Merge branch 'master' into flash-regions 2023-04-04 23:16:01 +02:00
064ec9581e Merge #1329
1329: Reduce bootloader memory usage r=rmja a=rmja

By not requiring that the provided buffer must be able to contain one "erase-size" bytes.

This PR is the successor of #1314 and replaces it entirely.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-04 21:00:11 +00:00
84bfe9b8c9 Align examples with bootloader changes 2023-04-04 22:44:21 +02:00
a77ce1088d Align chip specific boot projects with new prepare_boot() signature 2023-04-04 22:22:25 +02:00
e962fe794c Add assertions about the aligned_buf % write sizes 2023-04-04 21:57:28 +02:00
78e6b4d261 Remove comment about equal erase size requirement 2023-04-04 21:43:18 +02:00
53efb02900 Allow different erase sizes for active and dfu 2023-04-04 21:30:49 +02:00
6c93309df4 Remove the Flash trait 2023-04-04 21:18:41 +02:00
25577e0eaf Assert active and dfu have same erase size and copy in smaller chunks
The copy from active to dfu (and vice versa) is now done in smaller portions depending on aligned_buf, which now does not need to be erase_size big.
2023-04-04 21:09:30 +02:00
9242ad89d4 Remove magic buffer argument from prepare_boot
and use the aligned page buffer instead
2023-04-04 20:25:55 +02:00
8256ac1044 Use MemFlash::default() in sha1 verify test 2023-04-04 19:07:45 +02:00
54b82d9966 Merge remote-tracking branch 'upstream/master' into incremental-hash 2023-04-04 19:06:14 +02:00
5923e143e3 Merge #1321
1321: executor: add Pender, rework Cargo features. r=Dirbaio a=Dirbaio

This introduces a `Pender` struct with enum cases for thread-mode, interrupt-mode and
custom callback executors. This avoids calls through function pointers when using only
the thread or interrupt executors. Faster, and friendlier to `cargo-call-stack`.

`embassy-executor` now has `arch-xxx` Cargo features to select the arch and to enable
the builtin executors (thread and interrupt).

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-04 15:50:12 +00:00
143105eeb6 Merge #1313
1313: (embassy-stm32): rework bufferedUart to get rid of PeripheralMutex r=Dirbaio a=MathiasKoch

New implementation is very similar to the implementation of embassy-nrf & embassy-rp. 

Also adds embedded-hal traits to bufferedUart.

**NB**: Still needs testing on actual hardware

Co-authored-by: Mathias <mk@blackbird.online>
2023-04-04 15:14:07 +00:00
3ede5667d4 Merge #1324 #1327
1324: Add MCO support for L4 and F4 families r=Dirbaio a=m-dupont

Add MCO support for L4 and F4 as already done in F7. 

When the 'HSI' source is selected as MCO source, 'HSI' is activated (`set_hsion(true)`) . This is done to operate the MCO in case 'MSI' is chosen as the clock source for the CPU. The same applies to PLL, etc.

1327: Avoid write before erase r=Dirbaio a=rmja

This introduces an additional marker to the state partition right after the magic which indicates whether the current progress is valid or not. Validation in tests that we never write without an erase is added.

There is currently a FIXME in the FirmwareUpdater. Let me know if we should take the erase value as a parameter. I opened a feature request in embedded-storage to get this value in the trait. Before this, the assumption about ERASE_VALUE=0xFF was the same.

I have made some thoughts about whether this is a breaking change between the app and firmware, i.e. whether adding the "Progress valid" field is breaking. My conclusion is that it is not a breaking change. For the case where an app uses this new FirmwareUpdater together with an old bootloader, what it now does, is that it:

1. Writes the progress valid field to all zeros. This field is not known in the old bootloader, so it actually writes a "current progress" index.
2. The entire state partition is erased - effectively removing any trace of 1.
3. Set magic

This should be compatible.


Co-authored-by: Mathieu Dupont <mdupont@cppm.in2p3.fr>
Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-04 14:59:10 +00:00
7140e97202 Merge #1328
1328: Expose read/write/erase on partition r=Dirbaio a=rmja

This is needed now where FirmwareWriter is removed.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-04 14:47:09 +00:00
803c09c300 Expose read/write/erase on partition 2023-04-04 12:50:53 +02:00
5e19fb6fb9 Fix compile error when verification is enabled 2023-04-04 12:36:50 +02:00
c38eb9660b Merge remote-tracking branch 'upstream/master' into avoid-write-before-erase 2023-04-04 12:29:24 +02:00
7c6936a2e3 Let hash functions take a digest::Digest trait
... and add adapters for current Sha512 implementations that does not inplement the Digest trait
2023-04-04 12:24:30 +02:00
c94f1e1450 Merge remote-tracking branch 'upstream/master' into incremental-hash 2023-04-04 07:58:16 +02:00
df3a1e1b9d Avoid write to not-erased magic
This introduces an additional marker to the state partition right after the magic which indicates whether the current progress is valid or not. Validation in tests that we never write without an erase is added.

There is currently a FIXME in the FirmwareUpdater. Let me know if we should take the erase value as a parameter. I opened a feature request in embedded-storage to get this value in the trait. Before this, the assumption about ERASE_VALUE=0xFF was the same.
2023-04-04 07:18:29 +02:00
36ad82a52b Merge #1322
1322: Remove FirmwareWriter r=Dirbaio a=rmja

FirmwareWriter currently has a "max-write-size" parameter, but this is a limitation that should be handled by chunking inside the NorFlash driver, and not "up here" in user code. In case that the driver (e.g. qspi driver) is unaware of any max-write limitations, one could simply add an intermediate NorFlash adapter providing the chunk'ing capability.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-04 00:33:23 +00:00
117fca84ea Merge #1315 #1325
1315: Add HIL test for timer on nrf r=Dirbaio a=lulf



1325: Update Rust nightly. r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-04-03 22:59:33 +00:00
ef361d2e88 Update Rust nightly. 2023-04-03 23:15:19 +02:00
ae26a08026 Add HIL test for timer on nrf 2023-04-03 22:55:21 +02:00
1349dabe1a add compilation time exclusion for stm32f410 2023-04-03 17:55:05 +02:00
932b80ca8a run fmt 2023-04-03 16:52:57 +02:00
4ce1c5f27d Add MCO support for L4 and F4 families 2023-04-03 16:41:25 +02:00
7c11d85e1e Move MemFlash to separate module and add verify_erased_before_write verification 2023-04-03 15:33:20 +02:00
8aaffe82e7 Add incremental hash to FirmwareUpdater
This adds support for computing any hash over the update in the dtu area by providing a closure to the hash update function.
2023-04-03 14:59:55 +02:00
b1e2195b49 Remove FirmwareWriter
FirmwareWriter currently has a "max-write-size" parameter, but this is a limitation that should be handled by chunking inside the NorFlash driver, and not "up here" in user code. In case that the driver (e.g. qspi driver) is unaware of any max-write limitations, one could simply add an intermediate NorFlash adapter providing the chunk'ing capability.
2023-04-03 14:50:41 +02:00
0909a6cd3f Merge #1312
1312: Let bootloader partition have read/write/erase operations r=Dirbaio a=rmja

This change should not have any breaking changes.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-04-03 11:49:44 +00:00
bfebf7a436 Fix formatting of sector erase log 2023-04-03 08:02:43 +02:00
5504fc54fe Merge branch 'embassy-rs:master' into flash-regions 2023-04-03 07:57:39 +02:00
d3c4e4a20a executor: add Pender, rework Cargo features.
This introduces a `Pender` struct with enum cases for thread-mode, interrupt-mode and
custom callback executors. This avoids calls through function pointers when using only
the thread or interrupt executors. Faster, and friendlier to `cargo-call-stack`.

`embassy-executor` now has `arch-xxx` Cargo features to select the arch and to enable
the builtin executors (thread and interrupt).
2023-04-03 03:09:11 +02:00
94890e544e Update stm32-metapac. 2023-04-03 02:01:06 +02:00
b41ee47115 executor: unify export mod. 2023-04-03 01:11:42 +02:00
08f911d25e Merge #1318
1318: rp: Allow zero len reads for buffered uart r=Dirbaio a=timokroeger

Prevents the read methods from getting stuck forever.

cc `@MathiasKoch` can you test if this fixes the problem you described in the chat?

Co-authored-by: Timo Kröger <timokroeger93@gmail.com>
2023-04-02 18:33:36 +00:00
7ef6a3cfb2 rp: Allow zero len writes for buffered uart
Prevents the write methods from getting stuck forever.
2023-04-02 14:36:32 +02:00
cd2ed065dc Merge #1317
1317: Fix set_baudrate on RP-PICO r=Dirbaio a=TFleury

See [comment](https://github.com/embassy-rs/embassy/pull/1243#issuecomment-1492894626)

Co-authored-by: Thierry Fleury <thierry@codinlab.fr>
2023-04-02 10:03:20 +00:00
dd88775871 Ensure that flash locking is defered to after write 2023-04-01 18:10:20 +02:00
e11eebfa57 Ensure that ranges are validated with the region size 2023-04-01 17:26:32 +02:00
268e29b153 Let the FlashRegion for region types be public 2023-04-01 16:59:21 +02:00
f78aa4f936 rp: Allow zero len reads for buffered uart
Prevents the read methods from getting stuck forever.
2023-04-01 14:31:24 +02:00
0e13fe9925 Fix set_baudrate on RP-PICO 2023-04-01 11:44:49 +02:00
472dc6b7d1 Fix interrupt handling so it is similar to before the rework, and fix examples 2023-03-31 15:57:35 +02:00
50b0fb1a37 Let get_flash_regions be public 2023-03-31 15:47:45 +02:00
cfbe93c280 Rework bufferedUart to get rid of PeripheralMutex in a similar fashion as nrf & rp. Also adds embedded-hal traits to bufferedUart 2023-03-31 10:43:30 +02:00
d9d6fd6d70 Add erase and wipe tests 2023-03-31 10:28:47 +02:00
42931b51f2 Let bootloader partition have read/write/erase operations
This change should not have any breaking changes.
2023-03-31 10:18:19 +02:00
e3efda2249 Merge #1311
1311: Split bootloader implementation into multiple files r=lulf a=rmja

No other change has been made. I think that I have all the types re-exported in lib.rs as before.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-03-31 06:56:26 +00:00
373760a56b Split bootloader implementation into multiple files 2023-03-31 08:05:37 +02:00
5955d81374 Merge pull request #1263 from embassy-rs/turbowakers
Turbo-wakers 🚀
2023-03-30 19:03:03 +02:00
80972f1e0e executor,sync: add support for turbo-wakers.
This is a `core` patch to make wakers 1 word (the task pointer) instead of 2 (task pointer + vtable). It allows having the "waker optimization" we had a while back on `WakerRegistration/AtomicWaker`, but EVERYWHERE, without patching all crates.

Advantages:
- Less memory usage.
- Faster.
- `AtomicWaker` can actually use atomics to load/store the waker, No critical section needed.
- No `dyn` call, which means `cargo-call-stack` can now see through wakes.

Disadvantages:
- You have to patch `core`...
- Breaks all executors and other things that create wakers, unless they opt in to using the new `from_ptr` API.

How to use:

- Run this shell script to patch `core`. https://gist.github.com/Dirbaio/c67da7cf318515181539122c9d32b395
- Enable `build-std`
- Enable `build-std-features = core/turbowakers`
- Enable feature `turbowakers` in `embassy-executor`, `embassy-sync`.
- Make sure you have no other crate creating wakers other than `embassy-executor`. These will panic at runtime.

Note that the patched `core` is equivalent to the unpached one when the `turbowakers` feature is not enabled, so it should be fine to leave it there.
2023-03-30 17:55:55 +02:00
f3dcb5eb22 Wrap write/erase operations in cs 2023-03-30 15:13:44 +02:00
6b2aaacf83 Update embassy
Merge commit '9dd3719f09835f646e3a8f3abaa33726a1e3f9ca'
2023-03-30 14:37:51 +02:00
a78e10e003 Add defmt support to new flash types 2023-03-30 09:17:14 +02:00
02caec9482 Skip unknown banks 2023-03-30 09:07:23 +02:00
760d4a72cb Ensure that embedded_storage traits are actually implemented 2023-03-30 09:05:13 +02:00
e7129371d0 Let sector computation be shared across families 2023-03-30 08:32:36 +02:00
e3c4e00be0 Align families 2023-03-30 06:01:56 +02:00
91d8afd371 Add AltFlashLayout for supported F4 chips 2023-03-30 05:27:57 +02:00
89129babf9 Merge remote-tracking branch 'upstream/master' into flash-regions 2023-03-30 04:53:07 +02:00
def576ac46 Remove FlashRegion trait and rename Settings to FlashRegion 2023-03-30 04:24:41 +02:00
ef1890e911 Remove flash operations from FlashRegion trait and move to common module 2023-03-29 15:45:18 +02:00
754bb802ba Merge #1294
1294: Add support for `QSPI` in `stm32` r=Dirbaio a=Mirror0

Implemented with help of Tomasz Grześ <tomasz.grzes@gmail.com>.

Tested only on stm32f777zi.

Co-authored-by: Mateusz Butkiewicz <mateusz@github.butkiewicz.dev>
2023-03-29 13:42:29 +00:00
68c260edeb Use stm32-metapac v2 2023-03-29 15:03:48 +02:00
fc8c83e00a Fix h7 compile error 2023-03-29 14:50:19 +02:00
87898501a2 feat(stm32:qspi): convert some u8 to enum variants 2023-03-29 14:28:25 +02:00
a0d089536a Merge branch 'flash-regions' of https://github.com/rmja/embassy into flash-regions 2023-03-29 14:10:33 +02:00
15e1747220 Fix build of not implemented family 2023-03-29 14:10:16 +02:00
0bbc3a3d81 Merge branch 'master' into flash-regions 2023-03-29 13:59:17 +02:00
5a12fd6c75 Add unimplemented family section 2023-03-29 13:57:33 +02:00
b7dfc8de10 Let flash module be conditionally included 2023-03-29 13:52:52 +02:00
ddbd509865 Move as much logic from families to shared module as possible 2023-03-29 13:37:45 +02:00
69944675a3 Expose get_sector in favor of is_eraseable_range 2023-03-29 12:49:13 +02:00
4ee3d15519 Keep peripheral lifetime when calling into_regions() 2023-03-29 12:10:24 +02:00
6806bb9692 Expose flash region settings as an array 2023-03-29 11:52:18 +02:00
d6ce1c4325 Support running tests in embassy-stm32 and move impl from common back to stm32 2023-03-29 11:31:45 +02:00
7a841b58d1 Merge #1307
1307: (embassy-stm32): add embedded-io blocking Read + Write for BufferedUart r=MathiasKoch a=MathiasKoch



Co-authored-by: Mathias <mk@blackbird.online>
2023-03-28 12:35:07 +00:00
14f6bc88ea Remove unnecessary lifetime 2023-03-28 14:34:36 +02:00
2d7f35cf57 Add embedded-io blocking Read + Write for BufferedUart 2023-03-28 14:28:44 +02:00
cf179f3076 Merge #1303
1303: Add logging and interface for debugging USB buffer usage r=Dirbaio a=jamesmunns



Co-authored-by: James Munns <james@onevariable.com>
2023-03-27 16:31:13 +00:00
20aa86d63e Address review comments 2023-03-27 18:21:11 +02:00
a77fdefd7c Correct copy/paste errors 2023-03-27 15:37:12 +02:00
a6cef4baf2 Add logging and interface for debugging buffer usage 2023-03-27 14:19:00 +02:00
6a802c4708 feat(stm32:qspi): add support for QSPI in stm32
Implemented with help of Tomasz Grześ <tomasz.grzes@gmail.com>.
2023-03-27 13:20:00 +02:00
732614579b Merge pull request #1302 from embassy-rs/update-stm32-pac
Update stm32-metapac
2023-03-27 13:09:11 +02:00
a33774ec51 Update stm32-metapac 2023-03-27 12:36:31 +02:00
8a3a7c65a8 Merge #1291
1291: executor: Allow TaskStorage to auto-implement `Sync` r=Dirbaio a=GrantM11235



Co-authored-by: Grant Miller <GrantM11235@gmail.com>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-26 22:23:00 +00:00
21400da073 executor: Use AtomicPtr for signal_ctx, removes 1 unsafe. 2023-03-27 00:22:00 +02:00
805bca1f5a executor: deduplicate doc comments. 2023-03-27 00:20:24 +02:00
7186e03801 Merge #1299
1299: sync/pipe: update to clarify docs that it is byte-oriented. r=Dirbaio a=Dirbaio

There was some language copypasted from Channel talking about "messages" or "values", that is not really accurate with Pipe.

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-26 21:34:01 +00:00
2c45b5c519 sync/pipe: update to clarify docs that it is byte-oriented.
There was some language copypasted from Channel talking about "messages"
or "values", that is not really accurate with Pipe.
2023-03-26 23:32:12 +02:00
9c7b9b7848 Merge #1288
1288: fix(rp): spi transfer r=elpiel a=elpiel

Fixes #1181 

Co-authored-by: Lachezar Lechev <elpiel93@gmail.com>
2023-03-26 15:55:58 +00:00
7be63b3468 fix: spi transfer bug and additions to test
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-03-26 18:14:17 +03:00
e9a5b31fa8 Implement drop for FlashRegions 2023-03-25 17:00:52 +01:00
e8fc7a66a3 Ensure flash module and FlashRegion trait is always defined 2023-03-25 16:32:32 +01:00
bc69eb596e Add is_eraseable_range and split write into consecutive parts 2023-03-25 16:04:45 +01:00
245147634b Add region start to flash sectors 2023-03-25 16:03:06 +01:00
73ccc04231 Change region type name 2023-03-25 13:47:28 +01:00
47d5f127bb Align L family 2023-03-25 13:30:24 +01:00
47e07584ca Align H7 family 2023-03-25 13:03:00 +01:00
c848bd9c9c Align with removal of MemoryRegionKind::Otp 2023-03-25 13:02:42 +01:00
a8567f0617 Align F7 family 2023-03-25 06:26:00 +01:00
99c4346579 Add f7 computation to hal common and add tests 2023-03-25 06:25:12 +01:00
7edd72f8f5 Align F3 family 2023-03-25 06:07:57 +01:00
6c73b23f38 Align F4 family 2023-03-25 05:59:40 +01:00
6b44027eab Add FlashRegion trait and implement embedded_storage traits for each region 2023-03-25 05:58:40 +01:00
cccceb88f2 Generate flash regions during build 2023-03-25 05:57:15 +01:00
d8b265856f Add f4 flash sector computation to hal-common to allow for tests 2023-03-25 05:52:48 +01:00
cd2f28d2ab chore: add spi_async tests for uneven buffers
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-03-24 12:14:38 +02:00
9939d43800 fix: PR comment
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-03-24 12:14:23 +02:00
299689dfa2 Merge #1295
1295: (embassy-rp): Add embedded-io blocking Read + Write for BufferedUart r=MathiasKoch a=MathiasKoch



Co-authored-by: Mathias <mk@blackbird.online>
2023-03-23 13:27:14 +00:00
88483b5abe Fix return type for EH-nb traits 2023-03-23 14:26:37 +01:00
04f90e3a9d Add embedded-io blocking Read + Write for BufferedUart 2023-03-23 14:18:19 +01:00
ba9afbc26d embassy-boot: add default nightly feature, makes it possible to compile with the stable compiler 2023-03-22 16:49:49 +01:00
aa77a06d58 Merge #1287
1287: stm32: use stm32-metapac from crates.io, remove stm32-data submodule. r=Dirbaio a=Dirbaio

- `embassy-stm32` uses `stm32-metapac` from `crates.io`.
- Remove `stm32-data` submodule.
- Remove `stm32-metapac`, `stm32-metapac-gen`. These are now in the `stm32-data` repo.
- Remove `stm32-gen-features`, since it used `stm32-data` directly.
  - The feature list in `stm32-metapac` is generated by `stm32-metapac-gen` now.
  - The feature list in `embassy-stm32` can be updated by hand by copypasting from `stm32-metapac`'s `Cargo.toml`. We can add a script to do this automatically later if that's too annoying.
- Remove outdated `xtask` scripts that no longer work. Not needed anymore, you can run CI locally by doing `./ci.sh`.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-21 14:04:10 +00:00
425a35bf8a Merge #1292
1292: perf(pubsub): Skip not needed clone of last message r=Dirbaio a=rmja

... return the pop'd message instead

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-03-21 12:29:29 +00:00
ce7bd6955f perf(pubsub): Skip clone on last message 2023-03-21 13:25:49 +01:00
41d558a5f4 executor: Allow TaskStorage to auto-implement Sync 2023-03-20 17:08:15 -05:00
7a4db1da26 fix(rp): spi transfer
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-03-20 16:34:30 +02:00
0b49b588a2 stm32: use stm32-metapac from crates.io, remove stm32-data submodule. 2023-03-20 02:38:12 +01:00
b6663a013f Merge #1274
1274: Example using the PIO to drive WS2812 aka Neopixel RGB leds r=Dirbaio a=CBJamo

This example also uses a pio program compiled at runtime, rather than one built at compile time. There's no reason to do that, but it's probably useful to have an example that does this as well.

Co-authored-by: Caleb Jamison <Caleb@hellbender.com>
2023-03-19 23:43:41 +00:00
3e541c43e7 Merge #1282
1282: fix(pubsub): Pop messages with count=0 after unsubscribe r=Dirbaio a=rmja



Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-03-19 23:19:23 +00:00
fcd24adba9 vscode: recommend extensions, disable toml formatting, update. 2023-03-19 22:38:34 +01:00
4bfe624893 Merge #1285
1285: Add must_use to OnDrop r=Dirbaio a=rmja



Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-03-17 14:38:19 +00:00
7be385dbb1 Add must_use to OnDrop 2023-03-17 11:40:19 +01:00
f9c0c53e12 Merge #1284
1284: Fix APB clock calculation for several STM32 families r=Dirbaio a=ericyanush

This PR fixes #1283

Co-authored-by: Eric Yanush <eric@yanu.sh>
2023-03-17 07:49:09 +00:00
13f0c64a8c Fix APB clock calculation for several STM32 families 2023-03-16 21:21:39 -06:00
472df3fad6 fix(pubsub): Pop messages which count is 0 after unsubscribe 2023-03-15 16:45:18 +01:00
2c9f289f40 Merge #1281
1281: Wait for waker when no data is available on UDP socket. r=Dirbaio a=royb3



Co-authored-by: Roy Buitenhuis <roy.buitenhuis@technolution.nl>
2023-03-15 12:59:11 +00:00
46b437dea0 Wait for waker when no data is available on UDP socket. 2023-03-15 13:56:40 +01:00
e9a161b462 Merge #1279
1279: stm32: remove unused embedded-storage-async. r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-14 16:28:47 +00:00
43462947ed stm32: remove unused embedded-storage-async. 2023-03-14 17:27:40 +01:00
e73c6c9d90 Merge #1277
1277: (embassy-rp): Allow upgrading a blocking uart to a BufferedUart r=MathiasKoch a=MathiasKoch

Also implement blocking embedded-nal serial traits for BufferedUart

Co-authored-by: Mathias <mk@blackbird.online>
2023-03-14 12:08:51 +00:00
89a371d10c Add HIL test for into_buffered uart on embassy-rp 2023-03-14 12:46:58 +01:00
bce1ce7dcb Allow upgrading a blocking uart to a BufferedUart, and implement blocking serial traits for BufferedUart 2023-03-14 10:36:30 +01:00
12d6e37b3f Example using the PIO to drive WS2812 aka Neopixel RGB leds
This example also uses a pio program compiled at runtime, rather than one built at compile time. There's no reason to do that, but it's probably useful to have an example that does this as well.
2023-03-11 02:58:28 -05:00
055597063f Merge #1270
1270: time: add power-of-2 kHz tick rates. r=Dirbaio a=Dirbaio

Fixes #1269

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-09 22:32:51 +00:00
79061021f9 time: add power-of-2 kHz tick rates.
Fixes #1269
2023-03-09 23:32:00 +01:00
c9d89f391b Merge #1264
1264: Add multicast to udp socket r=Dirbaio a=28Smiles

This should make it possible to implement mDNS for embassy-net, which in turn is essential for smart home

Co-authored-by: Leon Camus <leon.c@gmx.de>
Co-authored-by: 28Smiles <Leon.c@gmx.de>
2023-03-08 12:16:23 +00:00
e484cb1b87 refactor: Multicast method modifiers on stack to public
revert: udp.rs
2023-03-08 12:37:00 +01:00
8fd30e407c Merge #1267
1267: macros: better validation of function signatures. r=Dirbaio a=Dirbaio

Fixes #1266

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-08 08:17:44 +00:00
b2c6dc45e3 Fix examples broken by the macro fix. 2023-03-08 09:17:01 +01:00
969e85150c Merge #1262
1262: bump embedded-storage-async to 0.4 r=Dirbaio a=mehmetalianil

I just haven't found a way to revert the altered stm-metapac contents due to building. 

Co-authored-by: Mehmet Ali Anil <mehmet@grusbv.com>
2023-03-08 01:26:45 +00:00
e7a19a9725 macros/main: copy fn return to task.
This prevents this bad code from compiling.

```rust
async fn main(_spawner: Spawner) -> ! {
    // not really noreturn!
}
```
2023-03-08 02:03:39 +01:00
a614e697d0 macros: better validation of function signatures.
Fixes #1266
2023-03-08 01:59:06 +01:00
993875e11f fix: Add qualified imports 2023-03-07 23:52:25 +01:00
2087561003 lint: Remove unused imports 2023-03-07 23:51:10 +01:00
b62e3e1d47 lint: Cargo fmt 2023-03-07 23:40:31 +01:00
468c4266c8 Merge branch 'embassy-rs:master' into master 2023-03-07 23:39:16 +01:00
18646c579c Merge branch 'emb-storage-async-0p4' of https://github.com/Grus-BV/embassy into emb-storage-async-0p4 2023-03-07 23:36:41 +01:00
b1bc034a7e Merge branch 'emb-storage-async-0p4' of https://github.com/Grus-BV/embassy into emb-storage-async-0p4 2023-03-07 23:35:56 +01:00
fded9fa52a Merge branch 'emb-storage-async-0p4' of https://github.com/Grus-BV/embassy into emb-storage-async-0p4 2023-03-07 23:21:39 +01:00
6bf8d090a1 Merge branch 'emb-storage-async-0p4' of https://github.com/Grus-BV/embassy into emb-storage-async-0p4 2023-03-07 23:21:19 +01:00
4054fb8779 Merge branch 'emb-storage-async-0p4' of https://github.com/Grus-BV/embassy into emb-storage-async-0p4 2023-03-07 23:17:28 +01:00
935633c90b Merge upstream 2023-03-07 23:16:54 +01:00
bd4c4209af Merge #1265
1265: nrf/uicr: only check lowest bit. r=Dirbaio a=Dirbaio

This mirrors what nrfx does. Also it won't reboot/warn if NFCPINS is set to either 0xFFFF_FFFE or 0x0000_0000, which are all valid.


bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-07 14:30:22 +00:00
27e989afa9 nrf/uicr: only check lowest bit.
This mirrors what nrfx does. Also it won't reboot/warn if NFCPINS is set to either
0xFFFF_FFFE or 0x0000_0000, which are all valid.
2023-03-07 15:28:27 +01:00
b0e26440ee Merge upstream 2023-03-07 10:47:56 +01:00
bc0cb43307 Bump embedded-storage-async to 0.4 2023-03-06 22:16:36 +01:00
c22218c72e feat: Add multicast to udp socket 2023-03-06 18:43:37 +01:00
18fe398673 Merge pull request #1224 from embassy-rs/interrupt-binding
nrf: new interrupt binding traits/macro
2023-03-06 00:41:47 +01:00
f5e09a8f4a nrf/interrupt: do not reexport take! macro. 2023-03-06 00:17:51 +01:00
5249996d28 nrf/usb: switch to new interrupt binding, fix vbus detect on nrf53. 2023-03-06 00:17:51 +01:00
5913553cb1 nrf/twis: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
36319fc121 nrf/temp: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
9e58d9274c nrf/twim: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
9f5762d365 nrf/spis: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
a32e82029a nrf/spim: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
2dc5608203 nrf/saadc: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
d113fcfe32 nrf/rng: make available on all chips, use Instance trait, switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
96788ac93a nrf/qspi: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
c66b28e759 nrf/qdec: make available on all chips, use Instance trait, switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
f8f1d3bcf0 nrf/pdm: make available on all chips, use Instance trait, switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
34563b74aa nrf/i2s: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
63b75eaf64 nrf/timer: remove awaitable. 2023-03-06 00:17:51 +01:00
9cf000ef4e nrf/uart: switch to new interrupt binding. 2023-03-06 00:17:51 +01:00
42c13c8c3d nrf: add new interrupt binding traits and macro. 2023-03-06 00:17:51 +01:00
a054891263 cortex-m: rename Handler to DynHandler.
I want to use the name Handler for the new interrupt binding macro.
2023-03-06 00:17:51 +01:00
403a83e08d Merge #1260
1260: time/ticker: make sure the future for .next() is Unpin. r=Dirbaio a=Dirbaio

It was Unpin before #1248 

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-05 22:15:14 +00:00
c88bbaa5ec time/ticker: make sure the future for .next() is Unpin. 2023-03-05 23:13:22 +01:00
6dfda69cc4 readme: add embassy-rp 2023-03-05 20:19:18 +01:00
bf013be9ba Merge pull request #1232 from embassy-rs/nrf-qspi-fixes
nrf/qspi: nrf53 support, u32 addrs, remove const generic, add raw read/write.
2023-03-05 03:19:11 +01:00
d91efe3e62 Merge pull request #1208 from embassy-rs/nrf-uarte-lockfree
nrf/buffered_uarte: make it work without rts/cts, and lock-free.
2023-03-05 02:56:15 +01:00
f7dfc49c5c nrf/qspi: add _raw variants of methods that don't do bounds checks.
Useful for the nRF7002, which presents as a "fake" QSPI flash, and
the "capacity" concept doesn't really apply to it.
2023-03-05 02:55:00 +01:00
8eb8ea6174 nrf/qspi: remove FLASH_SIZE const generic param. 2023-03-05 02:33:02 +01:00
75f69803af nrf/qspi: always use u32 for addresses. 2023-03-05 02:30:53 +01:00
1955a225e8 nrf/qspi: add nrf53 support. 2023-03-05 02:17:59 +01:00
9eb65b11cb nrf/qspi: remove cfg_if hack 2023-03-05 02:08:29 +01:00
7650fea5f2 nrf/buffered_uarte: add HIL tests. 2023-03-04 15:12:49 +01:00
916f94b366 nrf/buffered_uarte: make available on stable. 2023-03-04 15:12:49 +01:00
ccc224c81f nrf/buffered_uarte: remove PeripheralMutex, make it work without rts/cts.
> dirbaio: so I was checking how zephyr does UARTE RX on nRF
> dirbaio: because currently we have the ugly "restart DMA on line idle to flush it" hack
> dirbaio: because according to the docs "For each byte received over the RXD line, an RXDRDY event will be generated. This event is likely to occur before the corresponding data has been transferred to Data RAM."
> dirbaio: so as I understood it, the only way to guarantee the data is actually transferred to RAM is to stop+restart DMA
> dirbaio: well, guess what?
> dirbaio: they just count RXDRDY's, and process that amount of data without restarting DMA
> dirbaio: with a timer configured as counter https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/serial/uart_nrfx_uarte.c#L650-L692
> dirbaio: 🤔🤷⁉️
> dirbaio: someone saying you can do the "hook up rxdrdy to a counter" trick, someone else saying it's wrong 🤪 https://devzone.nordicsemi.com/f/nordic-q-a/28420/uarte-in-circular-mode

So we're going to do just that!

- BufferedUarte is lock-free now. No PeripheralMutex.
- The "restart DMA on line idle to flush it" hack is GONE. This means
  - It'll work correctly without RTS/CTS now.
  - It'll have better throughput when using RTS/CTS.
2023-03-04 15:12:49 +01:00
bef559307c Merge pull request #1259 from dalegaard/master
embassy_usb: Add split() for cdc_acm
2023-03-04 15:08:02 +01:00
7b9075130e embassy_usb: Add split() for cdc_acm 2023-03-04 10:36:10 +01:00
51478caad8 nrf/timer: add support for counter mode. 2023-03-04 05:37:33 +01:00
4314b823aa nrf: add PPI channel group driver. 2023-03-04 05:37:33 +01:00
78d733fc73 Merge pull request #1258 from CBJamo/rp-gpio-loglevel
Swap debug! for trace! in rp gpio
2023-03-02 22:18:01 +01:00
7bdb3abad7 Swap debug! for trace! in rp gpio
When using gpio pin changes for things like peripheral interrupts these
debug! calls flood defmt, making it difficult to find what you're
actually looking for.
2023-03-02 13:59:52 -05:00
14ed0b90b8 Merge pull request #1257 from MabezDev/docs/add-esp-hal
docs: add esp-hal
2023-03-02 17:57:39 +01:00
64003cdfd6 Add embassy-esp README 2023-03-02 16:56:58 +00:00
8bd8fbd131 Merge pull request #1256 from pattop/hal_common_atomic_ring_buffer_push_slices
hal-common/atomic_ring_buffer: add push_bufs() push_slices()
2023-03-02 17:26:43 +01:00
78974dfeb4 hal-common/atomic_ring_buffer: add push_bufs() push_slices()
This allows a writer to access all of the free space in the buffer, even
when it's wrapping around.
2023-03-02 14:11:49 +11:00
4ac257adb9 Merge #1255
1255: common: allow atomic ringbuf to fill up to N instead of just N-1. r=Dirbaio a=Dirbaio

Extracted out of #1208. Since I don't think that'll end up using the ringbuf in the end, I've separated it.

This allows the ringbuf to be filled up to `N` instead of just `N-1`, using some fun tricks on the indices. 

The advantage is better performance: Before, the first write would fill N-1 bytes, The second would write just the 1 byte left before wrapping, then N-2. Then 2, then N-3, and so on. This would result in more smaller chunks, so worse perf. This problem is gone now.

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-03-02 00:01:49 +00:00
f95aafc90e common: allow atomic ringbuf to fill up to N instead of just N-1.
This allows the ringbuf to be filled up to `N` instead of just `N-1`, using some fun tricks on the indices.

The advantage is better performance: Before, the first write would fill N-1 bytes, The second would write just the 1 byte left before wrapping, then N-2. Then 2, then N-3, and so on. This would result in more smaller chunks, so worse perf. This problem is gone now.
2023-03-02 01:01:19 +01:00
c4f4aa10f9 Merge pull request #1244 from embassy-rs/interruptexecutor
cortex-m/executor: don't use the owned interrupts system.
2023-03-01 22:38:27 +01:00
206b4b597e Merge pull request #1254 from davidedellagiustina/fix
FIx `PacketQueue::init()`
2023-03-01 22:23:03 +01:00
c0e40b887b Apply fix 2023-03-01 20:57:13 +00:00
351e4407ef Merge pull request #1252 from pattop/stm32_spi_fifo_fix
stm32/spi: fix occasional data corruption
2023-03-01 03:08:05 +01:00
aabc275186 stm32/spi: fix occasional data corruption
Need to clear the rx fifo before enabling rx dma.
2023-03-01 12:24:22 +11:00
66ca57312f Merge pull request #1251 from embassy-rs/fix-examples
Example fixes.
2023-03-01 01:59:41 +01:00
6dbb631f1e Example fixes. 2023-03-01 01:32:42 +01:00
4dfa32b1e0 cortex-m/executor: don't use the owned interrupts system.
Preparation for #1224.
2023-02-28 23:07:20 +01:00
711ce10145 Merge #1245
1245: fix: rp - disable Pull-down/up resistors for ADC read r=Dirbaio a=elpiel



Co-authored-by: Lachezar Lechev <elpiel93@gmail.com>
2023-02-28 19:31:58 +00:00
b16b3b0dbb Merge #1247
1247: `PacketQueue::init()` r=davidedellagiustina a=davidedellagiustina

`PacketQueue` is pretty big, so I added a method to initialize it without requiring an allocation on the stack (which could in fact overflow). Before this PR, the only solution would be to declare a `PacketQueue` instance as a `static mut`, while now one could for example have a `Box<MaybeUninit<PacketQueue<...>>>` and then use `init()` on it.

Ideally, the same work would need to be done for all those structures that own big arrays which could overflow the stack.

Co-authored-by: Davide Della Giustina <davide@dellagiustina.com>
2023-02-28 18:26:37 +00:00
3c601bf8d2 PacketQueue::init() does not need to be unsafe 2023-02-28 18:04:43 +00:00
485bb76e46 Implemented suggestions from @Dirbaio 2023-02-28 17:39:02 +00:00
d719f8bc03 Merge #1248
1248: embassy-time: add async tick() method to Ticker r=Dirbaio a=kbleeke

Small QOL change so you don't have to add a direct dependency on futures-util to use the Ticker

Co-authored-by: kbleeke <pluth@0t.re>
2023-02-28 17:12:13 +00:00
4e212c7a0b embassy-time: add async tick() method to Ticker 2023-02-28 17:25:42 +01:00
c1e93c0904 PacketQueue::new() uses ::init() when in nightly 2023-02-28 14:34:26 +00:00
90f2939bf6 Added PacketQueue::init() 2023-02-28 14:22:54 +00:00
5cb0c8cc01 fix: rp - disable Pull-down/up resistors for ADC read
Signed-off-by: Lachezar Lechev <elpiel93@gmail.com>
2023-02-28 09:22:38 +02:00
28b695e7c9 Merge #1243
1243: RP-PICO UART adding set_baudrate r=Dirbaio a=andres-hurtado-lopez

The following PR attepts to bring fuctionality to allow change od UART baudrate changes during runtime.

Changes where created under `@Dirbaio` supervision and discussed on issue:

[https://github.com/embassy-rs/embassy/issues/1241#issuecomment-1445500175]( https://github.com/embassy-rs/embassy/issues/1241#issuecomment-1445500175)

Co-authored-by: Andres Hurtado Lopez <andresh@cultivate-agri.com>
2023-02-27 02:26:20 +00:00
2331d58aa6 RP-PICO UART adding set_baudrate: missing to run rust-fmt 2023-02-26 21:23:51 -05:00
482ba835c4 RP-PICO UART adding set_baudrate: Changing static call from specific type to a Self (requires adding lifetime specifier) 2023-02-26 19:20:08 -05:00
7172dfd083 RP-PICO UART adding set_baudrate: refactoring of methods 2023-02-26 19:14:25 -05:00
8fb380b180 RP-PICO UART adding set_baudrate 2023-02-26 18:40:23 -05:00
bc71230cd0 examples/std: fix net running out of sockets. 2023-02-26 21:50:12 +01:00
5367baa845 Merge #1238
1238: embassy-net: DNS resolver detects when name is just an IP address r=lulf a=kbleeke

fixes #1237 

I am not sure, if this is the right place to put this code. Alternatively, It could be in dns::DnsSocket or reqwless (and all other libraries that need to maybe resolve hostnames).

Are there other DNS query-types where it would make sense to try to parse an IP address?

Co-authored-by: kbleeke <pluth@0t.re>
2023-02-26 08:22:38 +00:00
d5f88e578c embassy-net: DNS resolver detects when name is just an IP address and returns immediately 2023-02-25 21:52:15 +01:00
64247ae456 Merge #1236
1236: Add `#[must_use]` to all futures r=Dirbaio a=GrantM11235

I think that's all of them, I just grep'd for `impl.* Future for`

Co-authored-by: Grant Miller <GrantM11235@gmail.com>
2023-02-24 19:20:32 +00:00
7be4337de9 Add #[must_use] to all futures 2023-02-24 13:01:41 -06:00
2209bef4f2 Merge #1231
1231: embassy-time: Implement conversions to/from core::time::Duration for embassy-time::Duration r=Dirbaio a=kbleeke

I chose microseconds for the conversion as the lowest resolution that embassy provides. A new Error-type did not seem that useful but I can add one, if necessary.

Co-authored-by: kbleeke <pluth@0t.re>
2023-02-23 18:33:47 +00:00
43a4409405 embassy-time: Implement conversions to/from core::time::Duration for embassy-time::Duration 2023-02-23 19:25:22 +01:00
3255e0a172 Merge #1228
1228: stm32/sdmmc: Implement proper clock configuration r=chemicstry a=chemicstry

This implements proper clock configuration for sdmmc based on chip family, because `RccPeripheral::frequency()` is almost always incorrect. This can't be fixed in PAC, because sdmmc uses two clock domains, one for memory bus and one for sd card. `RccPeripheral::frequency()` usually returns the memory bus clock, but SDIO clock calculations need sd card domain clock. Moreover, chips have multiple clock source selection bits, which makes this even more complicated. I'm not sure if it's worth implementing all this logic in `RccPeripheral::frequency()` instead of cfg's in sdmmc.

Some chips (Lx, U5, H7) require RCC updates to expose required clocks. I didn't want to mash everything in a single PR so left a TODO comment. I also left a `T::frequency()` fallback, which seemed to work in H7 case even though the clock is most certainly incorrect.

In addition, added support for clock divider bypass for sdmmc_v1, which allows reaching a maximum clock of 48 MHz. The peripheral theoretically supports up to 50 MHz, but for that ST recommends setting pll48 frequency to 50 MHz 🤔

Co-authored-by: chemicstry <chemicstry@gmail.com>
2023-02-23 16:22:31 +00:00
73ef85b765 stm32/sdmmc: Fix compile errors 2023-02-23 18:00:55 +02:00
f0f92909c1 Merge #1227
1227: stm32/dma: fix spurious transfer complete interrupts r=Dirbaio a=pattop

DMA interrupts must be acknowledged by writing to the DMA_{L,H}IFCR
register.

Writing to the CR register is unnecessary as the channel (EN bit) is
disabled by hardware on completion of the transfer.


Co-authored-by: Patrick Oppenlander <patrick.oppenlander@gmail.com>
2023-02-23 15:44:43 +00:00
896764bb85 stm32/sdmmc: Refactor TypeId into a macro 2023-02-23 17:38:52 +02:00
42462681bd stm32/sdmmc: Implement proper clock configuration 2023-02-23 16:57:21 +02:00
4e884ee2d2 stm32/dma: fix spurious transfer complete interrupts
DMA interrupts must be acknowledged by writing to the DMA_{L,H}IFCR
register.

Writing to the CR register is unnecessary as the channel (EN bit) is
disabled by hardware on completion of the transfer.
2023-02-23 10:12:48 +11:00
dda5a4cc9d Merge #1225
1225: nrf: rename UARTETWISPIn -> SERIALn r=Dirbaio a=Dirbaio

The UARTETWISPIn naming is quite horrible. With the nRF53, Nordic realized this and renamed the interrupts to SERIALn. Let's copy that for our peripheral names, in nrf53 and nrf91.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-22 14:43:17 +00:00
464faa2a04 Merge #1226
1226: embassy-net: Implement flush for TcpSocket r=Dirbaio a=kbleeke

Implements flush for TcpSocket by checking the send queue. 

Flushing is implemented by checking if smoltcp's send_queue/tx_buffer is empty. The flush is completed when all outstanding octets are acknowledged. Smoltcp wakes the send waker [here](https://docs.rs/smoltcp/latest/src/smoltcp/socket/tcp.rs.html#1712) when ACKs are processed and data is removed from the send buffer. So we can re-check in our flush implementation, if the buffer is now empty.

fixes #1223



Co-authored-by: kbleeke <pluth@0t.re>
2023-02-22 14:27:19 +00:00
035de6f3ff embassy-net: add flush to TcpSocket and TcpWriter as an inherent method 2023-02-22 14:45:17 +01:00
f1a4db44c4 Implement flush for TcpSocket 2023-02-22 13:57:40 +01:00
ada3d5be7c nrf: rename UARTETWISPIn -> SERIALn
The UARTETWISPIn naming is quite horrible. With the nRF53, Nordic realized this
and renamed the interrupts to SERIALn. Let's copy that for our peripheral names, in nrf53 and nrf91.
2023-02-21 22:41:23 +01:00
b05cd77a62 Merge #1170
1170: nrf: add support for UICR configuration. r=Dirbaio a=Dirbaio

- APPROTECT enable/disable. Notably this fixes issues with nrf52-rev3 and nrf53 from locking itself at reset.
- Use NFC pins as GPIO.
- Use RESET pin as GPIO.

NFC and RESET pins singletons are made available only when usable as GPIO, for compile-time checking.

TODO: test
- [x] nrf52 rev1
- [x] nrf52 rev3
- [x] nrf53
- [x] nrf91

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-20 00:47:15 +00:00
7fa478358a nrf: warn if uicr configuration could not be written.
If the user requests some configuration, but UICR is already programmed
to something else, detect this and warn the user.

We don't do it for the debug port settings, because if they are wrong
then the user will simply not be able to read debug logs.
2023-02-20 01:31:02 +01:00
3f88bf6f9b nrf: add support for UICR configuration.
- APPROTECT enable/disable. Notably this fixes issues with nrf52-rev3 and nrf53 from locking itself at reset.
- Use NFC pins as GPIO.
- Use RESET pin as GPIO.

NFC and RESET pins singletons are made available only when usable as GPIO,
for compile-time checking.
2023-02-20 01:28:45 +01:00
a2bd37be40 Merge #1221
1221: examples/stm32wb: do not reserve words at start of RAM. r=Dirbaio a=Dirbaio

They're used to communicate from the app to ST's OTA bootloader. See AN5247. 

This bootloader is optional, must be flashed by the user, and requires changing the FLASH start address as well, so the current memory regions still require modifications to use it. Therefore there's no point in reserving these words.

Thanks `@adamgreig` for investigating the purpose.

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-20 00:01:22 +00:00
13328c58d3 examples/stm32wb: do not reserve words at start of RAM.
They're used to communicate from the app to ST's OTA bootloader. See AN5247. 

This bootloader is optional, must be flashed by the user, and requires changing the FLASH start address as well, so the current memory regions still require modifications to use it. Therefore there's no point in reserving these words.

Thanks @adamgreig for investigating the purpose.
2023-02-20 01:01:01 +01:00
1567e724f9 Merge #1218 #1219
1218: Lora: sx126x: Change timing window to match values found experimentally. r=Dirbaio a=CBJamo

As mentioned in #1188.

1219: stm32/sdmmc: Fix SDIOv1 writes r=Dirbaio a=chemicstry

This fixes writes on sdmmc v1 (SDIO). I'm pretty sure I tested writes in #669, but maybe I was just lucky or I just forgot.

There were two problems:
- Writes require DMA FIFO mode, otherwise SDIO FIFO is under/overrun depending on sdio/pclk2 clock ratio.
- Hardware flow control is broken for sdmmc v1 (I checked F1 and F4 erratas). This causes clock glitches above 12 MHz and results in write CRC errors.

Co-authored-by: Caleb Jamison <caleb@cbjamo.com>
Co-authored-by: chemicstry <chemicstry@gmail.com>
2023-02-19 23:01:44 +00:00
4ad255b34b Merge #1217
1217: Fix a typo in "PioPeripheral" r=Dirbaio a=SekoiaTree

Renames "PioPeripherial" to "PioPeripheral" (without the second i).

Co-authored-by: sekoia <sequoia.1009@gmail.com>
2023-02-19 22:46:57 +00:00
4fd59f26fb Merge #1220
1220: examples/stm32wb: fix linker script. r=Dirbaio a=Dirbaio

cortex-m-rt 0.7.2 now enforces the stack is 8-byte aligned. Stack is placed at `ORIGIN(RAM) + LENGTH(RAM)` by default, which wasn't 8-byte-aligned. See https://github.com/rust-embedded/cortex-m/discussions/469

ST trims 8 bytes from start of RAM, and uses the whole 192kb, so let's just copy that:

bceb1dae09/Drivers/CMSIS/Device/ST/STM32WBxx/Source/Templates/gcc/linker/stm32wb55xx_flash_cm4.ld (L48)

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-19 22:03:13 +00:00
272982ee54 examples/stm32wb: fix linker script.
cortex-m-rt 0.7.2 now enforces the stack is 8-byte aligned. Stack is placed
at `ORIGIN(RAM) + LENGTH(RAM)` by default, which wasn't 8-byte-aligned. See https://github.com/rust-embedded/cortex-m/discussions/469

ST trims 8 bytes from start of RAM, and uses the whole 192kb, so let's just
copy that:

bceb1dae09/Drivers/CMSIS/Device/ST/STM32WBxx/Source/Templates/gcc/linker/stm32wb55xx_flash_cm4.ld (L48)
2023-02-19 23:02:31 +01:00
a53f525f51 stm32/sdmmc: Fix SDIOv1 writes 2023-02-18 01:37:06 +02:00
7783e0ebb1 Change timing window to match values found experimentally. 2023-02-17 07:43:19 -05:00
e641db1f75 Fix a typo in "PioPeripheral" 2023-02-15 14:10:07 +01:00
e3f8020c3b Merge #1215
1215: Add clone to embassy_rp::gpio::Level r=Dirbaio a=Slushee-a

Allows you to wite a cleaner state change detector. Example:
```rs
let mut button_state: Level = Level::Low;
let mut prev_button_state: Level = button_state;

loop {
    button_state = button.get_level();

    if prev_button_state != button_state {
        led.set_level(button_state);  // Takes ownership of button_state. 
    }

    prev_button_state = button_state; // Can't be done since the ownership has been moved.
                                      // Adding Clone makes this code possible
}
```

Co-authored-by: Slushee <55996847+Slushee-a@users.noreply.github.com>
2023-02-13 17:31:47 +00:00
dfc58ad3a2 Add copy to Level enum in embassy-rp gpio module 2023-02-13 17:29:35 +00:00
1626a4a74b Add clone to embassy_rp::gpio::Level 2023-02-13 17:12:50 +00:00
5e74926907 feature-gate variants without vals defined 2023-02-13 15:46:49 +01:00
218b44652c Rebase on master 2023-02-13 14:55:15 +01:00
41a563aae3 net: document all features 2023-02-13 03:11:16 +01:00
363054de98 stm32: doc all chips. 2023-02-13 03:02:12 +01:00
06abde8676 Merge #1213
1213: stm32: fix fmc-related build failures on some F4's r=Dirbaio a=Dirbaio

f413vh has a peripheral named `FSMC` but using the `FMC` regs. This might be a mistake? `@rmja`

Fix build for now, we can investigate later if the regs are OK.

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-13 01:40:58 +00:00
4e15043fc2 add stm32f413vh to CI 2023-02-13 02:40:29 +01:00
951f208915 Add more crates to docs. 2023-02-13 02:39:03 +01:00
1e36c91bf8 stm32: fix fmc-related build failures on some F4's 2023-02-13 02:22:06 +01:00
80b7c3cf69 Fix doc build. 2023-02-13 01:30:53 +01:00
d159a6c62d Merge #1211
1211: Fix rcc prescaler for wl55 HCLK1 r=lulf a=chrenderle

fix "prescaler none" which incorrectly set "prescaler divided by 3"
Issue: #1168 

Co-authored-by: Christian Enderle <mail@chrenderle.de>
2023-02-12 11:00:22 +00:00
d21643c060 fix "prescaler none" which incorrectly set "prescaler divided by 3" 2023-02-12 11:36:57 +01:00
e1eac15c42 Merge pull request #1185 from embassy-rs/dns-impl
Add DNS socket to embassy-net
2023-02-10 23:53:25 +01:00
76642b3a3c fix h7 examples 2023-02-10 23:35:44 +01:00
20c1dd112c Merge #1210
1210: nrf/qspi: do not panic when canceling futures. r=Dirbaio a=Dirbaio

QSPI can't cancel DMA transfers. Before we'd panic on cancel, now we blocking-wait instead.

Blocking is not great, but it's better than panicking, especially when using code that's hardware-agnostic through the embedded-storage traits.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-10 22:13:56 +00:00
4c4e923e05 nrf/qspi: do not panic when canceling futures. 2023-02-10 23:03:16 +01:00
a509af4bc0 exmaples/dns: don't use the socket. 2023-02-10 23:00:16 +01:00
a2b8921ff3 fix: cfg guard for ipv6 2023-02-10 19:38:17 +01:00
128a453163 remove unneeded features 2023-02-10 19:04:54 +01:00
a7d3ef9122 scope dns operations within a cfged block 2023-02-10 19:00:00 +01:00
32c3725631 add waker for DNS slots 2023-02-10 18:44:51 +01:00
48dff04d64 Bump max queries 2023-02-10 18:34:21 +01:00
472473d8c1 Create slice using ::Owned 2023-02-10 18:32:35 +01:00
6e68353a93 attempt removing option 2023-02-10 18:30:17 +01:00
7ae47cb1d8 Expose api on Stack and add doc
Make it work with smoltcp 0.9
2023-02-10 18:20:50 +01:00
cd440a49d6 Rewrite to use a single socket 2023-02-10 17:46:08 +01:00
614740a1b2 cargo fmt 2023-02-10 17:45:25 +01:00
c203cefe01 Handle cancellation 2023-02-10 17:45:19 +01:00
9cfea693ed Add DNS socket to embassy-net 2023-02-10 17:45:03 +01:00
023b0d5b22 Merge #1209
1209: Time: Add from_hz function for Duration. r=Dirbaio a=CBJamo

I found myself doing things like this

```rust
    let rate_us = 1_000_000 / rate_hz;
    let mut ticker = Ticker::every(Duration::from_micros(rate_us));
```

Several times, and figured it was worth adding a little convenience function to handle that. This also makes the calculation const, which is a nice little upside. The compiler might have been doing that already, but this makes sure. 

Speaking of const, would it be better to give hz as a float? Obviously we'd want to avoid that at runtime since many targets don't have a fpu, but if it's at compile time that doesn't matter and a float may be more ergonomic.

Co-authored-by: Caleb Jamison <caleb@cbjamo.com>
2023-02-10 02:19:31 +00:00
bd7b3bd455 Clamp ticks to 1 and round to nearest. 2023-02-09 20:57:27 -05:00
a4371e9544 Add from_hz function for Duration. 2023-02-09 19:22:06 -05:00
e1a0df7d46 Merge #1205
1205: stm32/rng Fix rng generation lock-up r=Dirbaio a=lucasgranberg

This PR fixes a problem where the device gets locked in case of rng errors.

The PR also includes a hack for stm32wl based devices where the more complicated RNG peripheral can get stuck on seed errors.

Co-authored-by: Lucas Granberg <lukkeg@gmail.com>
2023-02-09 11:39:52 +00:00
2b6654541d rustfmt 2023-02-09 13:01:44 +02:00
43d018b67f Use rng_v2 cfg instead of chip specific for seed error recover hack 2023-02-09 12:44:20 +02:00
ab4b3fa96d update stm32-data to include rng_v2 2023-02-09 12:42:57 +02:00
26474ce6eb Merge pull request #1207 from embassy-rs/net-driver-reexport
net: reexport driver crate.
2023-02-08 18:25:51 +01:00
5edb3052e6 net: reexport driver crate. 2023-02-08 17:52:02 +01:00
13666c9f12 Merge pull request #1206 from embassy-rs/update-nightly
Update Rust nightly.
2023-02-08 17:34:01 +01:00
ac3e225988 Update Rust nightly. 2023-02-08 17:13:19 +01:00
bab4277a86 hack for STM32WL, rcc reset in case of seed error
The STM32WL series has a more complicated rng device that gets stuck
when there is a seed error.
2023-02-08 17:57:37 +02:00
da6b1e8399 Reset rng in case of clock or seed error 2023-02-08 17:52:49 +02:00
9d637070a5 Merge #1203
1203: usb: unify ControlHandler+DeviceStateHandler, route all control requests to all handlers. r=Dirbaio a=Dirbaio

depends on #1202 

- Allows classes to handle vendor requests. (fixes #1078)
- Allows classes to use a single handler for multiple interfaces.
- Allows classes to access the other events (previously only `reset` was available).

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-07 23:31:24 +00:00
86487db5d1 usb: use InterfaceNumber in msos. 2023-02-08 00:30:53 +01:00
3af991ab63 usb: unify ControlHandler+DeviceStateHandler, route all control requests to all handlers.
- Allows classes to handle vendor requests.
- Allows classes to use a single handler for multiple interfaces.
- Allows classes to access the other events (previously only `reset` was available).
2023-02-08 00:17:08 +01:00
1d841cc8ac usb: make max interface count configurable at compile time. 2023-02-08 00:16:04 +01:00
4a224efe75 Merge #1200
1200: feat(stm32): Add 16 data bit fmc ctor r=Dirbaio a=rmja

This has been validated with the Is42s16400j sdram on stm32f429.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-02-07 22:24:15 +00:00
c4a2c62096 Merge #1199
1199: STM32 SPI: Set clk-pin pull-up/-down to match spi clock polarity r=Dirbaio a=jr-oss

Fixes #1094 

There are some proposed solutions in #1094 

> Keep the DMA transaction open across calls to read/write
        This may be problematic if the user changes bus settings between calls, and also the reference manual says the chip should not be placed into low power mode while SPI is enabled

As already described, this is problematic and against reference manual recommendation

>    Set the CLK (and maybe MOSI) pins as pull-down on setup (or pull-up, depending on config - and this would need to be updated if the user modified the config)
        This is less good than driving the pin to the correct value, but may be better than nothing

That is also my preferred solution. See below citation from reference manual.

>    Document this and require users fix it themselves (add a pull-up/down resistor - or configure the pins as pull-up/pull-down before passing them into SPI setup)

Setting internal pull-up/-down won't work, because `sck.set_as_af()` will change the gpio pull mode to none: https://github.com/embassy-rs/embassy/blob/master/embassy-stm32/src/gpio.rs#L552-L555

>    Dig around in the reference manual and determine if there is a better way to start/stop a DMA transaction while keeping active control of the clock the whole time

I haven't found a better way

------
From ST reference manual RM0394 (L4) 
(Same note in RM0399 (H7) / RM0038 (L1) / RM0316 /F3)):

    40.4.6
    Communication formats
    ...
    The idle state of SCK must correspond to the polarity selected in the SPIx_CR1 register (by
    pulling up SCK if CPOL=1 or pulling down SCK if CPOL=0).


Co-authored-by: Ralf <jr-oss@gmx.net>
2023-02-07 22:06:25 +00:00
366fab5b87 Merge #1189
1189: USB: Add MS OS Descriptors (alternate implementation) r=Dirbaio a=alexmoon

This is an alternate API for #1152 based on the work of `@mattico.` By switching to a writer-style API instead of a builder API some compile-time guarantees are lost, but it integrates better into the usb `Builder` and makes an api that can be used by USB device class implementations.

It also adds a feature flag so there is zero cost to the MS OS descriptors for devices that don't need to use them.

I've added an example based on `usb_serial` which tells Windows to use the generic `WinUSB` driver instead of the serial port driver for the device.

Comments are welcome. It would be nice to see either this or #1152 merged as my project is going to require the MS OS Descriptors soon.

Co-authored-by: Matt Ickstadt <matt@beckenterprises.com>
Co-authored-by: alexmoon <alex@moonspot.org>
2023-02-07 19:31:10 +00:00
aa21aebb0b Lazily encode UTF16 values and add docs 2023-02-07 14:24:35 -05:00
9f9230ae7a Convert MS OS descriptor builder to a writer API
This brings it inline with the other embassy-usb descriptor APIs and allows it to integrate well with the Builder to allow class constructors to add MS OS descriptors.

Also adds a `usb_serial_winusb` example to demonstrate how to use the API.
2023-02-07 14:24:35 -05:00
b9ecdb72bb usb: remove msos dead code 2023-02-07 14:24:35 -05:00
617b0a03b9 usb: fix descriptor set length and DeviceInterfaceGUIDs 2023-02-07 14:24:35 -05:00
f5ff3c4ac3 usb: add support for MS OS Descriptors 2023-02-07 14:24:35 -05:00
a7fa7d0de2 Merge #1201
1201: net: use released smoltcp 0.9.0 r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-07 17:16:08 +00:00
102b2e52cb net: use released smoltcp 0.9.0 2023-02-07 18:15:26 +01:00
7b11e339bd feat(fmc): Add 16 data bit ctor 2023-02-07 16:06:59 +01:00
dadd6aafe9 Merge #1197
1197: fix(stm32): Align FMC with new versions from stm32-data r=lulf a=rmja



Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-02-07 14:48:26 +00:00
1b6aae9dde Also exclude fsmc_v1x3 2023-02-07 15:06:16 +01:00
e4dc473e04 Update stm32-data 2023-02-07 14:46:36 +01:00
562432ad8b Update stm32-data 2023-02-07 14:16:13 +01:00
494a76a0f1 React to updated fsmc versions 2023-02-07 14:14:47 +01:00
36ca18132d Update stm32-data 2023-02-07 12:35:59 +01:00
218f8e0490 fix(stm32): Align FMC with new versions from stm32-data 2023-02-07 12:17:37 +01:00
ba18656e94 Merge #1177
1177: STD driver needs a reentrant mutex; logic fixed to be reentrancy-safe r=Dirbaio a=ivmarkov

...or to summarize it in another way, the code in the alarm thread loop is written as if - when calling the user-supplied callback - the callback will *never, ever* call `alarm.set_alarm()`.

But this happens of course - at least with the generic timer queue implementation. Not sure if that would happen with `embassy-executor`'s own queue, but probably yes?

The end result on Linux is that the code deadlocks because when calling the user-supplied callback, the mutex of the alarms is locked, yet - the code in `set_alarm` tries to take the lock again leading to UB. (I suspect on Windows this will crash rather than deadlock but that's a bit irrelevant.)

(Note also that calling the user-supplied callback *outside* of the alarms' lock is also NOK, because at that time, the callback and/or context itself might be invalid as well, as the user might had changed it with a new one by calling `set_callback`. Right?)

I also had to fix the logic that computed the next timestamp when the alarm should fire; it was running a simple `for {}` loop, not anticipating that the just-traversed alarm might get a new timestamp.

The new code is slightly less efficient, in that on each `loop {}` iteration it always starts traversing the alarms from the beginning, whereas in reality only the timestamp of the alarm that just-fired could've changed, but given the complexities introduced by `RefCell`, I don't think we should bother with these micro-optimizations, for just 4 alarms in total.


Co-authored-by: ivmarkov <ivan.markov@gmail.com>
2023-02-06 18:05:22 +00:00
c8a7b74bc2 Merge #1192 #1193
1192: stm32/usart: implement stop_bits configuration r=Dirbaio a=pattop



1193: stm32/usart: fix LPUART clock multiplier r=Dirbaio a=pattop

According to RM0351 Rev 9 (L4) and RM0399 Rev 3 (H7):

baud = (256 * clock) / LPUARTDIV


Co-authored-by: Patrick Oppenlander <patrick.oppenlander@gmail.com>
2023-02-06 13:39:37 +00:00
e3174d7a99 STM32 SPI: Set clk-pin pull-up/-down to match spi clock polarity
RM0394:

    40.4.6
    Communication formats
    ...
    The idle state of SCK must correspond to the polarity selected in the SPIx_CR1 register (by
    pulling up SCK if CPOL=1 or pulling down SCK if CPOL=0).
2023-02-06 13:23:35 +01:00
fda36fd81b stm32/usart: fix LPUART clock multiplier
According to RM0351 Rev 9 (L4) and RM0399 Rev 3 (H7):

baud = (256 * clock) / LPUARTDIV
2023-02-06 11:22:41 +11:00
64ebb9b7fe stm32/usart: implement stop_bits configuration 2023-02-06 09:44:15 +11:00
7d8e6649b7 Merge #1187
1187: executor: Minor refactoring r=Dirbaio a=GrantM11235

The third commit may be slightly more controversial than the first two. Personally, I think it makes the code more readable and easier to reason about, but I can drop it if you disagree.

Co-authored-by: Grant Miller <GrantM11235@gmail.com>
2023-02-03 06:33:22 +00:00
662a02a557 Merge #1191
1191: stm32 gpio implement degrade to AnyPin r=Dirbaio a=JoshMcguigan

This PR implements a `degrade` method on the STM32 GPIO structs `Flex`/`Input`/`Output`/`OutputOpenDrain`. This allows, for example, transforming some `Input<T>` to an `Input<AnyPin>`.

Co-authored-by: Josh Mcguigan <joshmcg88@gmail.com>
2023-02-03 06:00:50 +00:00
0bb6000e5c stm32 gpio implement degrade to AnyPin 2023-02-02 21:42:42 -08:00
a432d91d82 PeripheralRef docs improvements. 2023-02-03 06:36:10 +01:00
9af25c3396 Merge #1188
1188: LoRa timings for SX126x/STM32WL r=lulf a=samueltardieu

Those timings open Rx time windows covering 99.7% of the one expected
by the antenna while allowing 3ms for the Rx subsystem to start listening.


Co-authored-by: Samuel Tardieu <sam@rfc1149.net>
2023-02-02 12:09:22 +00:00
ef4a20f67b LoRa/STM32WL: adjust Rx window offset and duration
Those timings open Rx time windows covering 99.7% of the one expected
by the antenna while allowing 3ms for the Rx subsystem to start listening.
2023-02-02 13:01:18 +01:00
c4cbb89fcd LoRa/SX126x: adjust Rx window offset and duration
Those timings open Rx time windows covering 99.7% of the one expected
by the antenna while allowing 3ms for the Rx subsystem to start listening.
2023-02-02 13:01:18 +01:00
95cff35a91 Merge #1179
1179: LoRa/SX1276: adjust Rx window offset and duration r=lulf a=samueltardieu

After a transmission, two receive windows Rx1 and Rx2 are opened for one second each, one right after the other, after a fixed delay (for example 5s). The Rx window offset is added to the starting date of each window and the Rx window duration represents the maximum delay we will wait for an incoming message before declaring that a timeout occurred.

A value of -500ms for the offset and 800ms for the duration means that instead of having Rx1 = [5000, 6000[ and Rx2 = [6000, 7000[ we get Rx1 = [4500, 5300[ and Rx2 = [5500, 6300[. We only cover 30% of the expected windows.

The maximum time a SX127x can take before the Rx side is ready is TS_HOP + TS_RE = 50µs + 2.33ms. Using 3ms for the offset and 1003ms for the duration will give much better time windows: Rx1 = [4997, 5997[ and Rx2 = [5997, 7000]. Note that the
lorawan-device crate caps Rx1 end date to Rx2 start date.

This change allows a previously failing Murata CMWX1ZZABZ-091 module (STM32L + SX1276) to connect to the TTN LoRa network.


Co-authored-by: Samuel Tardieu <sam@rfc1149.net>
2023-02-02 10:09:33 +00:00
cb88dd285d nrf/twis: FIx doc typo 2023-02-01 20:54:32 +01:00
791fbb3ca0 Make poll_fn lazily initialized again 2023-01-31 21:46:25 -06:00
4a8e9cf4d9 Add internal AvailableTask type 2023-01-31 19:04:41 -06:00
fb1946be7f Replace the pointer in TaskHeader with an Option<&Executor> 2023-01-31 18:59:03 -06:00
a697f1517a Set poll_fn in TaskStorage::new 2023-01-31 18:59:03 -06:00
465e4c8b19 Merge #1151
1151: USB: allow setting the interface string for interface alt settings r=Dirbaio a=mattico

This is a breaking change to embassy-usb's API.

Co-authored-by: Matt Ickstadt <matt@beckenterprises.com>
2023-02-01 00:36:22 +00:00
594969f281 Merge #1186
1186: Add some docs r=Dirbaio a=Dirbaio

This also does some renames of things to more intuitive/consistent names.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-02-01 00:18:01 +00:00
b5cf332cc0 nrf: docs. 2023-02-01 01:17:41 +01:00
ca10fe7135 usb: docs 2023-01-31 22:27:19 +01:00
4c19464548 Merge #1184
1184: Pass the correct buffer when creating TcpSocket r=lulf a=lulf



Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2023-01-31 18:38:16 +00:00
768fe699cf Pass the correct buffer when creating TcpSocket 2023-01-31 19:36:41 +01:00
c21cc21c62 Merge #1182
1182: executor: Replace `NonNull<TaskHeader>` with `TaskRef` r=Dirbaio a=GrantM11235



Co-authored-by: Grant Miller <GrantM11235@gmail.com>
2023-01-29 22:34:48 +00:00
b6ca6d699a Make wake_task safe 2023-01-29 16:32:12 -06:00
48e1aab762 executor: Replace NonNull<TaskHeader> with TaskRef 2023-01-29 15:52:13 -06:00
7e251a2550 Merge #1180
1180: usb: allow adding isochronous endpoints r=Dirbaio a=nitroxis

This adds (basic) support for isochronous endpoints. In theory, isochronous endpoints have guaranteed bandwidths and are support to adhere to strict timings. But it seems that nothing bad happens if you don't follow the specs closely in this regard. Better handling could also still be added in the future.

Co-authored-by: nitroxis <n@nxs.re>
2023-01-27 15:06:35 +00:00
e453334870 LoRa/SX1276: adjust Rx window offset and duration
After a transmission, two receive windows Rx1 and Rx2 are opened
for one second each, one right after the other, after a fixed delay
(for example 5s). The Rx window offset is added to the starting date
of each window and the Rx window duration represents the maximum
delay we will wait for an incoming message before declaring that
a timeout occurred.

A value of -500ms for the offset and 800ms for the duration means
that instead of having Rx1 = [5000, 6000[ and Rx2 = [6000, 7000[
we get Rx1 = [4500, 5300[ and Rx2 = [5500, 6300[. We only cover
30% of the expected windows.

The maximum time a SX127x can take before the Rx side is ready is
TS_HOP + TS_RE = 50µs + 2.33ms. Using 3ms for the offset and
1003ms for the duration will give much better time windows:
Rx1 = [4997, 5997[ and Rx2 = [5997, 7000]. Note that the
lorawan-device crate caps Rx1 end date to Rx2 start date.

This change allows a previously failing Murata CMWX1ZZABZ-091
module (STM32L + SX1276) to connect to the TTN LoRa network.
2023-01-27 16:01:41 +01:00
c9e2cd6dd4 usb: allow adding isochronous endpoints 2023-01-27 15:53:13 +01:00
7ec15f2def Merge #1178
1178: rp: allow isochronous USB endpoints to be up to 1023 bytes in size r=Dirbaio a=nitroxis

The datasheet allows isochronous USB endpoints to be up to 1023 bytes in size (see "4.1.2.5. DPSRAM"). This PR changes the check to allow this and also changes the length computation to align to 64 bytes (instead of hardcoded 64 bytes).

Embassy does not yet support isochronous USB endpoints, however I'm investigating adding support. This change is simple enough and should be correct according to the datasheet, so maybe future implementers don't run into this issue.

Co-authored-by: nitroxis <n@nxs.re>
2023-01-27 14:44:51 +00:00
1e60c60afd rp: allow isochronous USB endpoints to be up to 1023 in size 2023-01-27 07:24:49 +01:00
34b67fe137 STD driver needs a reentrant mutex; logic fixed to be reentrancy-safe 2023-01-26 20:41:18 +00:00
ffa75e1e39 Merge #1173 #1174
1173: nRF examples crates names r=lulf a=davidedellagiustina

Fixed nRF examples crates' names: they had the same names and they were conflicting during compilation (Cargo warning).

1174: add missing copy of icmpv6 checksum r=lulf a=lulf

add proto-ipv6 feature to stm32h7 example to catch issues in CI

Co-authored-by: Davide Della Giustina <davide@dellagiustina.com>
Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2023-01-24 09:26:21 +00:00
2a0ea52878 add missing copy of icmpv6 checksum
add proto-ipv6 feature to stm32h7 example to catch issues in CI
2023-01-24 10:25:37 +01:00
5ee8626d72 Merge #1172
1172: IPv6 has no checksum r=lulf a=davidedellagiustina

As title says. Fixes `embassy-net` not compiling when feature `proto-ipv6` is enabled.

Co-authored-by: Davide Della Giustina <davide@dellagiustina.com>
2023-01-24 08:56:01 +00:00
32bdc54ccb Changed crates' names for nrf examples since they were conflicting 2023-01-24 08:27:53 +00:00
f38d54a6a6 IPv6 has no checksum 2023-01-24 08:15:22 +00:00
4dadfb41ea Merge #1171
1171: Implement From<gpio::Level> for bool r=Dirbaio a=hulthe

From implies Into, so this won't break anything.

Co-authored-by: Joakim Hulthe <joakim@hulthe.net>
2023-01-23 17:38:05 +00:00
64e610fef7 Replace Level: Into<bool> with From<Level>
This automatically implements Into<bool> for Level
2023-01-23 13:35:24 +01:00
f98ba4ebac Merge #1168
1168: Fix rcc prescaler for wb55 HCLK1 r=lulf a=chrenderle

- fix prescaler not divided which incorrectly set prescaler divided by 3
Issue: #1137 

Co-authored-by: Christian Enderle <mail@chrenderle.de>
2023-01-21 14:10:02 +00:00
5e3c33b777 Fix rcc prescaler for wb55 HCLK1
- fix prescaler not divided which incorrectly set prescaler divided by 3
2023-01-21 14:39:25 +01:00
6ad2bcf97a Merge pull request #1166 from embassy-rs/sdmmc-fix
fix embedded-sdmmc integration.
2023-01-21 01:22:17 +01:00
0412d1922c fix embedded-sdmmc integration.
- Rename feature to `embedded-sdmmc`.
- Move embedded-sdmmc fork repo to the embassy-rs org.
- Remove unused features in the fork
- Fix impl in embassy-stm32
- Add to CI so it doesn't break again.
2023-01-21 00:32:34 +01:00
825b67101b Merge pull request #1164 from embassy-rs/stm32-print-rcc
stm32/rcc: print actual freqs on boot.
2023-01-20 17:08:20 +01:00
f604153f05 stm32/rcc: print actual freqs on boot. 2023-01-20 16:31:04 +01:00
539a8107e2 Merge #1161
1161: WIP: Smoltcp socket config r=Dirbaio a=Czocher

This PR updates the smoltcp version to the newest master one as well as implements the feature specified in #1154 - to allow the dhcpv4 socket to be configured.

Currently it should be considered a WIP PR - requires testing.

`@Dirbaio` can you have a look and check if this is compatible with what we discussed on the matrix channel?

Co-authored-by: Paweł Jan Czochański <pawel@czochanski.pl>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-01-19 13:57:21 +00:00
78c2c1709b net: update smoltcp. 2023-01-19 14:44:01 +01:00
fe15a7beee net: allocate space for 2 sockets, needed for dhcp. 2023-01-19 14:44:01 +01:00
570ffab670 net: poll returning false is not an error. No need to repoll. 2023-01-19 14:44:01 +01:00
8f4fae9b36 Add smoltcp dhcp socket configuration 2023-01-19 14:44:01 +01:00
2eae12b7f1 Update smoltcp to the newest master 2023-01-19 14:44:01 +01:00
65ab714fae Merge #1162
1162: rp gpio: make pin_bank() inline r=Dirbaio a=mkj

This allows set_high() etc to be inlined, toggling pins should be much faster.

Co-authored-by: Matt Johnston <matt@ucc.asn.au>
2023-01-19 10:59:22 +00:00
83af513424 rp gpio: make pin_bank() inline
This allows set_high() etc to be inlined, toggling pins
should be much faster.
2023-01-19 13:36:40 +08:00
f0ae1f9133 Merge #1159
1159: stm32 usb otg bug fixes r=Dirbaio a=chemicstry

This fixes a couple of usb otg bugs that surfaced with `usb_ethernet` example from nrf:
- Properly implemented `Endpoint::wait_enabled()`
- Return `EndpointError::Disabled` when neccessary in `Endpoint::write()`


Co-authored-by: chemicstry <chemicstry@gmail.com>
2023-01-18 11:30:44 +00:00
db8e9efe73 Merge #1160
1160: Stop sampling when exiting the Saadc methods r=huntc a=huntc

Prior to this commit, the onDrop function was being dropped immediately and not on exiting the Saadc sampling methods.

A few other places have been corrected also.

Thanks to Peter Hansen for pointing out this issue.

Co-authored-by: huntc <huntchr@gmail.com>
2023-01-18 04:17:08 +00:00
6ab4ecaf83 Stop sampling when exiting the Saadc methods
Prior to this commit, the onDrop function was being dropped immediately and not on exiting the Saadc sampling methods.
2023-01-18 14:51:46 +11:00
b1203bf036 stm32/usb_otg: fix core formatter 2023-01-18 03:06:32 +02:00
7d34f4f538 stm32/usb_otg: Add F4 usb_ethernet example 2023-01-18 02:37:02 +02:00
f07e59b24a stm32/usb_otg: prevent writes on disabled endpoint 2023-01-18 02:31:28 +02:00
d2f2b451d0 stm32/usb_otg: implement endpoint wait_enabled 2023-01-18 02:29:49 +02:00
15e3f42b7c Merge pull request #1158 from embassy-rs/stm32c0
Add STM32C0 support.
2023-01-17 21:45:36 +01:00
355761fd68 stm32: add stm32c0 examples. 2023-01-17 21:28:16 +01:00
2a349afea7 stm32: add stm32c0 support. 2023-01-17 21:28:16 +01:00
aea5a0fd96 Merge #1140
1140: feat(stm32): Let uart implement embedded-io Write trait r=Dirbaio a=rmja



Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2023-01-14 19:45:28 +00:00
634704bff4 Merge #1153
1153: Documentation on verifying firmware r=lulf a=huntc

The documentation has been enhanced to describe the verification of firmware with the firmware updater. Examples have also been provided that describe how keys can be generated and how firmware can be signed.

Co-authored-by: huntc <huntchr@gmail.com>
2023-01-14 08:26:30 +00:00
868d01889b Documentation on verifying firmware
The documentation has been enhanced to describe the verification of firmware with the firmware updater. Examples have also been provided that describe how keys can be generated and how firmware can be signed.
2023-01-14 17:36:22 +11:00
16590732f8 Update mod.rs 2023-01-14 07:13:29 +01:00
816b214403 Only implement Write 2023-01-14 07:12:43 +01:00
b6c8505697 Merge #1142
1142: More rp2040 BufferedUart fixes r=Dirbaio a=timokroeger

* Refactor init code
* Make it possible to drop RX without breaking TX (or vice versa)
* Correctly handle RX buffer full scenario

Co-authored-by: Timo Kröger <timokroeger93@gmail.com>
2023-01-14 00:07:02 +00:00
7ecb05ff77 usb: allow setting the interface string for interface alt settings 2023-01-13 12:10:36 -06:00
b0c8c688c7 Merge #1147
1147: Support codesigning in the firmware updater r=lulf a=huntc

This PR provides a method to verify that firmware has been SHA-512 hashed and signed with a private key given its public key. The implementation provides both [`ed25519-dalek`](https://github.com/dalek-cryptography/ed25519-dalek/blob/main/Cargo.toml) and [`salty`](https://github.com/ycrypto/salty) as the signature verifiers. Either of the `ed25519-dalek` and `ed25519-salty` features is required to enable the functionality from `embassy-boot`.

The `verify_and_mark_updated` method is used in place of `mark_updated` when signing is used via its feature. This avoids the accidental omission of validation where it has been declared as required at compile time. It also keeps the parity of calls at the same number to the previous situation.

The PR permits other types of signature verifiers in the future on the proviso that the [Signature trait](https://github.com/RustCrypto/traits/tree/master/signature) is supported.

Finally, I've updated the CI to include testing `embassy-boot`, which it was doing before. In addition, I've included a unit test for verification based on a `ed25519-dalek` documentation example. This tests both the `dalek` and `salty` implementations.

In terms of code size comparisons, `dalek` adds about 68KiB and `salty` adds about 20KiB. I'm using `salty` myself. I've also tested this out by signing my code with the OpenBSD `signify` utility and then verify it during firmware upload using `salty`.


Co-authored-by: huntc <huntchr@gmail.com>
2023-01-12 20:43:24 +00:00
b0529bc943 Support codesigning in the firmware updater
This commit provides a method to verify that firmware has been signed with a private key given its public key. The implementation uses ed25519-dalek as the signature verifier. An "ed25519" feature is required to enable the functionality. When disabled (the default), calling the firmware updater's verify method will return a failure.
2023-01-12 13:30:58 +11:00
88fd521b01 Merge #1145
1145: STM32 USB OTG #2 r=Dirbaio a=chemicstry

This is a continuation of #799

The usb serial example is already working!

TODO:
- [x] Add critical sections to registers shared with IRQ
- [x] Fix `disable()`
- [x] ~Implement cable disconnect detection~ - postponed
- [x] Fix `endpoint_set_enabled()`
- [x] ~Endpoint `wait_enabled`~ USB OTG does not have enable delay (?)
- [x] HS internal and HS ULPI PHY - untested

Co-authored-by: chemicstry <chemicstry@gmail.com>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-01-11 17:05:59 +00:00
1af102a1aa stm32 otg: add examples. 2023-01-11 17:58:15 +01:00
041531c829 stm32/rcc: fix u5 pll, add hsi48. 2023-01-11 17:57:22 +01:00
0feecd5cde stm32: add USB OTG support.
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2023-01-11 17:56:47 +01:00
065a0a1ee7 Update stm32-data. 2023-01-11 17:51:30 +01:00
ce842fe28c Refactor embassy-usb address handling to allow reordering of status resoponse 2023-01-11 17:47:12 +01:00
96b97c4711 Update vscode settings for latest RA. 2023-01-11 17:43:12 +01:00
dbf7493708 Merge #1149
1149: Add samples for nrf5340 r=lulf a=Tiwalun

Samples for the nrf5340, copied from the existing nrf samples.

Not sure if copying them is the best way of adding support, or using features in the existing samples would be better?
The code is mostly the same, with some different peripherals and pin mappings for the DK.

Co-authored-by: Dominik Boehi <dominik.boehi@gmail.com>
2023-01-10 11:06:22 +00:00
2baebabf4d Reduce amount of samples for nrf5340 2023-01-09 22:57:40 +01:00
0a27b6cedb Rename examples/nrf to examples/nrf52840 2023-01-09 22:30:02 +01:00
401185b1d9 Change UART pins for nRF5340 DK 2023-01-08 16:25:51 +01:00
f8afc3c882 Add samples for nrf5340 2023-01-08 15:36:35 +01:00
4c4b47f78a feat(stm32): Add embedded-io traits for UartRx and UartTx 2023-01-06 14:24:29 +01:00
539f97da53 rp: Fix formatting string to please CI 2023-01-05 22:00:44 +01:00
1096a9746c rp: Improve BufferedUart interrupt handling
* Only clear interrupt flags that have fired (so that we do not lose any error flags)
* Enable RX interrupt when a read is requested, disable it when the RX buffer is full
* Rework TX interrupt handling: its "edge" triggered by a FIFO threshold
2023-01-05 18:45:58 +01:00
3fbedd7c09 Merge #1141
1141: feat: compile bootloader examples for nRF91 r=lulf a=lulf

* Add nRF91 as target in CI builds
* Add example linker scripts for nrf91
* Make less nRF52 assumptions example config
* Add llvm-tools-preview required for cargo objcopy example

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2023-01-05 14:06:45 +00:00
3c537a9fae usb/driver: fix STATUS -> SETUP 2023-01-05 14:46:31 +01:00
b72da125eb Merge pull request #1144 from embassy-rs/usb-control-docs
usb/driver: document ControlPipe.
2023-01-05 02:20:05 +01:00
0ecc54f58c usb/driver: document ControlPipe. 2023-01-05 01:46:35 +01:00
f339e8518f Merge #1143
1143: rp2040: add {tx,rx}-only constructors to UART r=Dirbaio a=pferreir

As discussed with `@henrik-alser` on Matrix. I also added an example, feel free to remove it if it's too much.

Co-authored-by: Pedro Ferreira <pedro@dete.st>
2023-01-04 20:18:02 +00:00
6d4c6e0481 rp2040: add {tx,rx}-only constructors to UART 2023-01-04 21:11:19 +01:00
840a75674b rp: Disable RX interrupts when ring buffer is full
When data is in the RX fifo the RX timeout interrupt goes high again even after clearing it.
The result is a deadlock because execution is stuck in the interrupt handler. No other code
can run to clear the receive buffer.
Enable and disable RX interrupts based on the buffer fill level.
Use the same approach for the TX code path.
2023-01-04 16:53:43 +01:00
a24037edf9 rp: Fix BufferedUart drop code
Only unregister the interrupt handler when both parts are inactive
2023-01-04 15:59:03 +01:00
68c186309f rp: Common init function for BufferedUart
BufferedUart, BufferedUartRx and BufferedUartTX can all use the same init code.
2023-01-04 15:58:26 +01:00
2332d8cd23 feat: compile bootloader examples for nRF91
* Add nRF91 as target in CI builds
* Add example linker scripts for nrf91
* Make less nRF52 assumptions example config
* Add llvm-tools-preview required for cargo objcopy example
2023-01-04 13:38:43 +01:00
5aa59e9737 feat(stm32): Let uart implement embedded-io Read/Write 2023-01-04 12:57:19 +01:00
bf4c0de16a Merge #1139
1139: Wdt config changes r=lulf a=huntc

Per commits:

* By passing WDT config around we can control it more easily and promote sharing it between files.

* The memory layout of the s140 crept into a number of memory files, which can cause confusion (well, it did for me!).

* Obtaining the current WDT config is useful so that we do not have to duplicate configurations around the place. A constructor method has been introduced that attempts to return the current running WDT config from the WDT peripheral. The bootloader example has also been updated to show how the watchdog can be obtained and used.

Co-authored-by: huntc <huntchr@gmail.com>
2023-01-04 07:44:23 +00:00
8497f98de2 Provides a means of obtaining the current WDT config
Obtaining the current WDT config is important so that we do not have to duplication configuration around the place. A constructor method has been introduced that returns WDT config in accordance with how the register is presently configured. The bootloader example has also been updated to show the watchdog can be obtained and used.
2023-01-04 12:13:44 +11:00
6e6c3cbebc Cleaned up some doc and memory layout
The memory layout of the s140 crept into a number of memory files, which can cause confusion.
2023-01-04 10:27:16 +11:00
35afb60dd4 Merge #1138
1138: embassy-boot (rp): Add WatchdogFlash r=Dirbaio a=kalkyl



Co-authored-by: kalkyl <henrik.alser@me.com>
2023-01-03 23:25:52 +00:00
651eec0242 Pass WDT config around
By passing WDT config around we can control it more easily and promote sharing it between files.
2023-01-04 10:19:39 +11:00
413f339489 Cleanup 2023-01-03 23:34:50 +01:00
9428c40c8d embassy-boot (rp): Add WatchdogFlash 2023-01-03 22:58:56 +01:00
0aa2a9ac27 Merge #1093 #1127
1093: Add random example r=Dirbaio a=miathedev

Thanks Lulf for the help!

This should be added as example so other people can look it up easily.

With love,
Mia



1127: clean up doc comment generation r=Dirbaio a=Weshnaw

I noticed that when I created doc comments for my tasks that the doc comments got included on the inner function but not the outer functions, I personally prefer keeping the documentation as clean as possible so this PR aims to hide the inner function and then add doc comments to the outer function.

The actual changes include:
* adding #[doc(hidden)] onto the `task_inner` function
  * I flip flopped on this one because I could imagine someone may want this in their docs, but decided to include but I think arguments could be made either way
* copy the attributes from `task_inner` to `task_outer`
  * I don't work with proc_macros often so I am not entirely sure if the way I went about it is correct but it seems to work fine
    * specifically: using `parse_quote` to create the `task_outer` as a `ItemFn` then duplicating the attributes from `task_inner` to `task_outer`
  * I also am not sure if it's a good idea to duplicate all attributes over, but I honestly wasn't sure how to just get the just doc comment attributes

![OLD doc](https://user-images.githubusercontent.com/3748858/209456006-bfa6d40d-d3bf-4c1d-a2de-cf40828b58e5.png)
![NEW doc](https://user-images.githubusercontent.com/3748858/209456011-995363a3-f5b1-4ea3-9db9-1c566643efcb.png)


Co-authored-by: miathedev <mia@metzler.systems>
Co-authored-by: Brendon Fallquist <bfallquist@gmail.com>
2023-01-02 23:13:41 +00:00
a6b52bde58 Merge #1130
1130: USB serial (CDC-ACM) improvements r=Dirbaio a=timokroeger

* Remove unused call management descriptor
* Set flag for supported capabilities

The rp `usb_serial` example still works with windows hosts.

Co-authored-by: Timo Kröger <timokroeger93@gmail.com>
2023-01-02 22:51:58 +00:00
42f1b3ac74 Merge #1136
1136: Fix a typo r=Dirbaio a=lonesometraveler

Many thanks for maintaining this project. Here is a minor typo fix.

Co-authored-by: Kentaro Okuda <lonesometraveler@mac.com>
2022-12-30 17:54:21 +00:00
72bb4f8798 Fix a typo 2022-12-30 08:53:16 -05:00
3478004b4d Merge #1135
1135: Add Clone & Copy on embassy_nrf::gpio::Level r=Dirbaio a=mdevlamynck

This simply adds the Clone and Copy derive traits on embassy_nrf::gpio::Level.

Co-authored-by: Matthias Devlamynck <matthias.devlamynck@mailoo.org>
2022-12-29 16:31:36 +00:00
2c8080b0ae Add Clone & Copy on embassy_nrf::gpio::Level 2022-12-29 17:26:49 +01:00
7add0eafb8 Merge #1133
1133: rp: Fill and empty FIFOs in buffered uart interrupt r=Dirbaio a=timokroeger

Fixes an issue where only the first byte was transmitted. Should improve throughput aswell.

Co-authored-by: Timo Kröger <timokroeger93@gmail.com>
2022-12-27 15:55:51 +00:00
e4f457646f rp: Fill and empty FIFOs in buffered uart interrupt
Fixes an issue where only the first byte was transmitted.
Should improve throughput aswell.
2022-12-27 11:28:52 +01:00
3afb62d8d6 Merge #1132
1132: net: allow changing mac addr at runtime r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-12-27 00:14:16 +00:00
771806be79 net/chan: split state runner. 2022-12-27 01:07:58 +01:00
4a4b593694 net; allow changing MAC addr at runtime. 2022-12-27 01:04:55 +01:00
4297eb27ff Merge #1131
1131: rp: switch to released 0.2.1 pio crate. r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-12-26 21:31:20 +00:00
41d6316984 rp: switch to released 0.2.1 pio crate. 2022-12-26 22:30:22 +01:00
4e0d563997 usb cdc-acm: Set flag for supported capabilities 2022-12-26 09:36:04 +01:00
97f9f248f4 usb cdc-acm: Remove unused call management descriptor 2022-12-26 09:35:23 +01:00
147609d3bd Merge pull request #1129 from embassy-rs/net-driver
net: driver crate split
2022-12-26 05:06:15 +01:00
007246b160 net: split channel-based driver impl from usb cdc-ncm into a separate crate. 2022-12-26 04:49:08 +01:00
1f033d509a net: split driver trait to a separate crate. 2022-12-26 04:49:08 +01:00
639b3f1d5b usb-driver: remove unused log feature. 2022-12-26 03:34:05 +01:00
5655c6093f net: use atomic-polyfill on tcp client pool, for thumbv6m support. 2022-12-26 03:34:05 +01:00
72bb9b53a2 net: remove unused pool-x features 2022-12-26 03:34:05 +01:00
c29657f95a Merge #1128
1128: Add missing SPI pins r=Dirbaio a=pferreir

The SPI definitions lack the pins which are not accessible on the pico (but are so e.g. on the stamp).

Co-authored-by: Pedro Ferreira <pedro@dete.st>
2022-12-25 23:52:33 +00:00
f2fb9a2ca6 Add missing SPI pins 2022-12-25 23:49:04 +00:00
056eac998a Hide doc comments from inner function include doc comments on outer function 2022-12-24 21:21:07 -06:00
d1dd66cfca Merge #1126
1126: embassy-rp: Add Watchdog r=kalkyl a=kalkyl



Co-authored-by: kalkyl <henrik.alser@me.com>
2022-12-24 02:23:57 +00:00
e090ab1915 Remove lifetime, use pac fields 2022-12-24 03:22:51 +01:00
eaad0cc1dc embassy-rp: Add Watchdog 2022-12-24 02:51:06 +01:00
67a6e5accf Merge #1122
1122: embassy-rp: Add split() to BufferedUart r=kalkyl a=kalkyl



Co-authored-by: kalkyl <henrik.alser@me.com>
2022-12-23 22:16:58 +00:00
787745188c Change log level to debug 2022-12-23 23:14:58 +01:00
74fdd4c03c Merge pull request #1125 from embassy-rs/remove-atomic-polyfill
Remove unnecessary use of atomic-polyfill.
2022-12-23 21:04:49 +01:00
10c9cc31b1 Remove unnecessary use of atomic-polyfill.
Only use it when CAS is actually needed.
2022-12-23 20:46:49 +01:00
cd9a65ba39 stm32/usb: use separate irq flags.
- Fixes race condition that could cause losing irqs (because `if flags != 0` was clearing all)
- Doesn't need CAS, which is nice for thumbv6.
2022-12-23 20:45:51 +01:00
40ef66cdfb Merge #1124
1124: Fix two SPI bugs for stm32 r=Dirbaio a=rmja

This PR fixes two bugs:
* It fixes #1095 by ensuring that pin speed is VeryHigh for all spi versions. I am on stm32f429 which seems to be spi_v1, and it also needs the VeryHigh pin speed. Otherwise bit errors on the "last bit in every byte" can happen.
* It also fixes a lifetime bug for the tx buffer when sending "write_repeated". The issue can be seen when doing spi.write where the clock byte changes during a transmission because the buffer handled to the dma must live throughout the entire transfer.

Co-authored-by: Rasmus Melchior Jacobsen <rmja@laesoe.org>
2022-12-23 15:53:59 +00:00
47a0769fc2 Let repeated clock byte be singular pointer and not array pointer 2022-12-23 15:49:22 +01:00
e9a2c4a9e3 Let start_write_repeated accept pointer instead of slice 2022-12-23 15:40:09 +01:00
662bb5797f fix(stm32): Ensure that gpio speed is VeryHigh for all spi versions
This fixes #1095
2022-12-23 09:34:42 +01:00
2457fcaa35 fix(stm32): Align with updated dma::write_repeated signature 2022-12-23 09:33:34 +01:00
da9ee83756 fix(stm32): Fix write buffer lifetime for repeated writes 2022-12-23 09:32:18 +01:00
aa92ce6dc7 embassy-rp: Add split() to BufferedUart 2022-12-22 23:03:05 +01:00
1bd6c954c2 Merge #1121
1121: Add examples for stm32f0 r=lulf a=imrank03

Hello `@lulf,`

I added some more examples to stm32f0 and tested on hardware.

With love,
Imran

Co-authored-by: @imrank03 <immu0396@gmail.com>
2022-12-21 14:48:04 +00:00
55d9af71e3 enabled interrupt feature 2022-12-21 11:56:30 +05:30
395b5fed64 added watchdog example 2022-12-21 11:55:09 +05:30
0db3837dcc added priority based example 2022-12-21 11:54:36 +05:30
c0f3610581 added interrupt example 2022-12-21 11:53:55 +05:30
63122f6d7e button controlled example 2022-12-21 11:52:40 +05:30
46fd82d184 Merge #1119
1119: add convert_to_celsius function in the adc module r=lulf a=overheat

    modify RP2040 adc example to get inside biased bipolar diode voltage,
    then convert this temperature sensor data into Celsius degree,
    according to chapter 4.9.5. Temperature Sensor in RP2040 datasheet.

Co-authored-by: Aaron Tsui <aaron.tsui@outlook.com>
2022-12-20 17:25:23 +00:00
5ae91ed3b6 cargo fmt 2022-12-20 14:59:49 +08:00
849a0e174f add convert_to_celsius function in the adc module
modify RP2040 adc example to get inside biased bipolar diode voltage,
    then convert this temperature sensor data into Celsius degree,
    according to chapter 4.9.5. Temperature Sensor in RP2040 datasheet.
2022-12-20 09:12:01 +08:00
e221d91330 Merge #1118
1118: Atomic ringbuf fixes r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-12-19 00:23:46 +00:00
5b72410828 hal-common/atomic_ring_buffer: Add push_slice, pop_slice. 2022-12-19 01:22:41 +01:00
feaeb533fb hal-common/atomic_ring_buffer: fix crashes when len=0 2022-12-19 01:22:41 +01:00
ebc735008f Merge #1114
1114: nrf/uart: do not error on zero length transfers. r=Dirbaio a=Dirbaio

It's a perfectly fine thing to do, should be just a noop. Erroring is really annoying when you're writing a payload to uart that might be zero-length or not.

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-12-15 18:50:21 +00:00
bffa5be2f4 nrf/uart: do not error on zero length transfers.
It's a perfectly fine thing to do, should be just a noop. Erroring is
really annoying when you're writing a payload to uart that might
be zero-length or not.
2022-12-15 19:47:36 +01:00
5b65b0e843 Merge pull request #1096 from embassy-rs/net-refactor2
net: remove packet pool
2022-12-13 17:03:51 +01:00
790e4e1594 examples/std: update to new embassy-net trait. 2022-12-13 16:43:25 +01:00
5eae295c8a stm32: rename feature net to embassy-net.
The eth code is always built and available, but has no own API (other
than the embassy-net Device impl) to rx/tx packets. We could add this
API in the future, so the feature only means "embassy-net support".
2022-12-13 16:43:25 +01:00
3005ee0178 stm32/eth_v2: update to new embassy-net trait, remove PeripheralMutex. 2022-12-13 16:43:25 +01:00
8f30652109 stm32/eth_v1: update to new embassy-net trait, remove PeripheralMutex. 2022-12-13 16:43:25 +01:00
e9219405ca usb/cdc-ncm: add embassy-net Device implementation. 2022-12-13 16:43:25 +01:00
aaaf5f23a8 net: move stack into lib.rs 2022-12-13 16:18:39 +01:00
ac74613b5a net: remove packet pool.
The pool was prone to deadlocks, especially due to having a single pool
for rx+tx. If the pool got full with rx'd packets it would deadlock because
processing a rx packet requires doing another allocation on the pool, for
the possibly tx'd response, before deallocating the rx'd packet.

This also allows Device impls to allocate the packet memory in a particular
RAM kind, if needed for example to do DMA.

The `Device` trait is now token-based, like smoltcp's. In the end, this
is better because it allows callers to manage memory however they want (including
with a pool if they want to).
2022-12-13 16:18:39 +01:00
47747d3b73 Merge #1105
1105: embassy-rp: Add multicore support r=Dirbaio a=kalkyl

This PR adds multicore support + critical-section impl using hardware spinlocks.
Based on the rp2040-hal implementation.

Co-authored-by: kalkyl <henrik.alser@me.com>
Co-authored-by: Henrik Alsér <henrik.alser@me.com>
2022-12-13 14:55:34 +00:00
c4d8f3579e Update usage in docs 2022-12-13 14:15:04 +01:00
731eb3c6e3 fmt 2022-12-13 13:55:23 +01:00
3d68c0400b Merge branch 'master' into multicore 2022-12-13 13:51:48 +01:00
13d9d8fde1 Refactor after review 2022-12-13 13:49:51 +01:00
36639e5262 Merge #1043 #1103
1043: Rpi Pico PIO driver r=Dirbaio a=fluffware

This is a driver for the two PIOs in the Pico. I've tried using the type system to get as much compile time checks as possible. There is asynchronous support for FIFOs an Irqs. No DMA support yet. There's an example that shows how to use the driver asynchronously.

1103: embassy-stm32: add rs485 driver enable to uart r=Dirbaio a=FrozenDroid



Co-authored-by: Simon Berg <ksb@fluffware.se>
Co-authored-by: Vincent Stakenburg <v.stakenburg@sinewave.nl>
2022-12-13 11:47:27 +00:00
4fbd03a908 Merge pull request #1111 from overheat/embassy_net_readme
fix net README.md
2022-12-13 12:27:10 +01:00
aea28c8aa0 Add usage in to docs 2022-12-13 09:45:11 +01:00
95fdc7c552 fix net README.md 2022-12-13 14:06:37 +08:00
eb1d2e1295 Pause CORE1 execution during flash operations 2022-12-13 04:02:28 +01:00
96d6c7243b Cleanup 2022-12-10 13:43:29 +01:00
d8821cfd41 Feature gate critical-section-impl 2022-12-10 12:57:45 +01:00
cc0248d83a Select critical-section in tests 2022-12-10 12:42:08 +01:00
34eaade14f fmt 2022-12-10 08:33:09 +01:00
1ee58492fb embassy-rp: Add multicore support 2022-12-10 08:26:35 +01:00
5d4f09156a nrf: add manually_create_executor example. 2022-12-09 23:33:53 +01:00
488e322478 Clear unused pulls in set_pull. 2022-12-09 20:30:17 +01:00
cd59046e6c Added RelocateProgram class for adjusting PIO-programs for different origins. 2022-12-09 20:18:41 +01:00
35db6e639b PIO support for RPi Pico 2022-12-09 20:18:41 +01:00
e94ca0efd6 Merge #1051
1051: Adapted nvmc so it can be used for all nrf targets r=diondokter a=diondokter

Title says it all pretty much.
It's a bit annoying with the configs though.

I've made this a draft because I haven't really tested this yet.

Co-authored-by: Dion Dokter <dion@tweedegolf.com>
Co-authored-by: Dion Dokter <diondokter@gmail.com>
2022-12-09 16:45:55 +00:00
236d104844 embassy-stm32: add rs485 driver enable to uart 2022-12-09 14:26:09 +01:00
f22297e3d6 Merge branch 'master' into nrf91/53-nvmc 2022-12-09 11:04:55 +01:00
1d2f97b4e2 Fixed erase bug 2022-12-09 11:02:16 +01:00
58ab829049 Merge #1049
1049: embassy-nrf: Add I2S module r=lulf a=chris-zen

This PR adds I2S support for the nrf52 series (`nrf52832`, `nrf52833`, `nrf52840`).

We could only test it in a `nrf52840` in master mode for an output stream (see `i2s_waveform` example), using a clone of the [Adafruit I2S Stereo Decoder - UDA1334A](https://learn.adafruit.com/adafruit-i2s-stereo-decoder-uda1334a/overview).

We were wondering if this could be a welcome addition to embassy, as we are working on this very informally and don't have much free time for it.

<img src="https://user-images.githubusercontent.com/932644/202316127-a8cf90ef-1e1a-4e1d-b796-961b8ad6cef5.png" width="600">

https://user-images.githubusercontent.com/932644/202316609-e53cd912-e463-4e01-839e-0bbdf37020da.mp4


Co-authored-by: `@brainstorm` <brainstorm@nopcode.org>

Co-authored-by: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com>
Co-authored-by: Roman Valls Guimera <brainstorm@users.noreply.github.com>
2022-12-09 07:49:40 +00:00
5fdd521a76 Move the responsibility to manage buffers to the I2S stream 2022-12-08 20:22:50 +01:00
1b8c0733e6 Merge #1098
1098: Reset sdmmc clock on card init r=Dirbaio a=chemicstry

Set clock back to 400kHz in `init_card()`, which allows card reinitialization if it was removed

Co-authored-by: chemicstry <chemicstry@gmail.com>
2022-12-07 15:34:49 +00:00
94010d3362 Merge #1100
1100: net: remove unsafe, update smoltcp. r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-12-06 23:29:15 +00:00
f7fe0c1441 net: update smoltcp 2022-12-07 00:28:38 +01:00
7bda01ec24 Fix comment 2022-12-06 23:31:58 +02:00
40f0272dd0 Merge #1099
1099: rp: implement input for OutputOpenDrain r=Dirbaio a=Dirbaio

Needed for onewire and similar protocols.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-12-06 20:12:29 +00:00
54c153673d rp: add OutputOpenDrain input test. 2022-12-06 21:09:27 +01:00
7cbc3aefe6 rp: implement input for OutputOpenDrain 2022-12-06 19:54:39 +01:00
ef2b83cc03 Reset sdmmc clock on card init 2022-12-06 16:36:07 +02:00
5e94b8060b Merge #1089
1089: feat: embassy-boot for rp2040 r=Dirbaio a=lulf

Add embassy-boot support for RP2040, with examples for the Raspberry Pi Pico.

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-12-04 19:58:57 +00:00
a2712caab1 Add random example 2022-12-04 09:38:57 +01:00
02abe00439 net: don't use UnsafeCell.
The "must not be called reentrantly" invariant is too "global" to
maintain comfortably, and the cost of the RefCell is negligible,
so this was a case of premature optimization.
2022-12-03 00:56:16 +01:00
bb89a2341c feat: embassy-boot for rp2040
Add embassy-boot support for RP2040, with examples for the Raspberry Pi
Pico.

Co-authored-by: Mathias Koch <mk@blackbird.online>
2022-12-02 11:28:33 +01:00
f109e73c6d Merge #1074
1074: Added blinky example for stm32f0 r=lulf a=imrank03

Hi, I have added **blinky** example for `stm32f0` and tested with Nucleo board `STM32F091RC`.

- Can I add more example for stm32f0?

Co-authored-by: @imrank03 <immu0396@gmail.com>
2022-12-02 08:10:12 +00:00
9f854110f2 Merge #1082 #1083
1082: stm32: Add basic support for DMA priority settings r=lulf a=matoushybl

This adds very basic support for specifying priority for DMA interrupts. Unfortunately, the patch now doesn't allow for specifying different priorities for DMA1/DMA2, or BDMA1/BDMA2, which I didn't know how to support.

1083: stm32: Fix H7 unaligned erase r=lulf a=matoushybl

This PR simplifies erasing sectors on the H7, which was buggy.

Co-authored-by: Matous Hybl <hyblmatous@gmail.com>
2022-12-01 13:49:59 +00:00
d8ea297d6a Merge #1088
1088: stm32: Enable fifo for buffered uart r=lulf a=matoushybl

This PR enables fifo for buffered uart where it is available. This should hopfully get rid of some overrun errors. I tried it in my application where it worked, but more intensive testing is probably required.

Co-authored-by: Matous Hybl <hyblmatous@gmail.com>
2022-12-01 13:34:52 +00:00
e1d7d8d841 stm32: Enable fifo for buffered uart 2022-11-30 22:17:51 +01:00
eb010fbe33 Merge #1086
1086: rp: Add an RngCore impl based on ROSC.RANDOMBIT r=Dirbaio a=yodaldevoid

This has the potential to not be random, but it should not be an issue if default clock settings are used.

Co-authored-by: Gabriel Smith <ga29smith@gmail.com>
2022-11-30 19:10:44 +00:00
71df28e269 rp: Add an RngCore impl based on ROSC.RANDOMBIT
This has the potential to not be random, but it should not be an issue
if default clock settings are used.
2022-11-30 14:06:05 -05:00
645fb66a51 Merge #1087
1087: embassy-stm32: Allow SPI with DMA to implement blocking embbeded-hal traits r=Dirbaio a=guillaume-michel

Before this PR, on STM32, SPI with DMA do not implement embedded-hal blocking traits even if it is allowed by the hardware.

This PR fixes this issue.

I could not do the same thing for `embassy_embedded_hal::shared_bus::asynch::SpiDevice` because  I could not figure out how to deal with "non-blocking" mutex in a non async fn. Maybe someone has the answer...

Hope it is still useful as is.

Co-authored-by: Guillaume MICHEL <guillaume@squaremind.io>
2022-11-30 13:45:26 +00:00
e0ea5dfdb2 embassy-stm32: Allow SPI with DMA to implement blocking embbeded-hal traits 2022-11-30 09:26:16 +01:00
8436c6180f Merge #1085
1085: Bump defmt-rtt to 0.4 r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-11-29 20:19:25 +00:00
1dcb0ea1f5 Bump defmt-rtt to 0.4 2022-11-29 21:15:24 +01:00
902586a019 Merge #1084
1084: Bump embedded-nal-async to 0.3.0 r=Dirbaio a=Dirbaio

bors r+

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-11-29 11:07:07 +00:00
3135ad016d Bump embedded-nal-async to 0.3.0 2022-11-29 12:05:46 +01:00
199504be56 Optimization to be able to work with only 2 buffers 2022-11-29 01:09:47 +01:00
4cc0463123 stm32: Add basic support for DMA priority settings 2022-11-28 21:22:39 +01:00
2a35a09444 stm32: Fix H7 unaligned erase 2022-11-28 21:15:24 +01:00
cea29d7de3 Merge #1079
1079: Async function in trait cleanup r=Dirbaio a=yodaldevoid

Some issues I ran across after the AFIT stuff was merged.

Co-authored-by: Gabriel Smith <ga29smith@gmail.com>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
2022-11-27 23:13:41 +00:00
787e5d4907 Add -time, -sync to CI with all features. 2022-11-28 00:12:13 +01:00
aedcc472c9 time: Fix nighly feature compilation after upgrade to embedded-hal-async
0.2.0-alpha.0
2022-11-27 17:59:01 -05:00
4d84b5469e Drive-by documentation link fixes 2022-11-27 16:32:18 -05:00
3ca14ba4e9 usb-driver: Remove unncessary lifetime 2022-11-27 16:28:24 -05:00
d438d1b685 sync: Fix nightly feature compilation after upgrade to embedded-io 0.4.0 2022-11-27 16:24:20 -05:00
6b8ab32536 Use &mut self for start methods 2022-11-26 15:22:31 +01:00
805b885de6 Merge pull request #1044 from embassy-rs/buffereduart-atomic
rp/uart: use lockfree ringbuffer.
2022-11-25 23:04:28 +01:00
7b838d0336 rp/uart: use lockfree ringbuffer.
This gets rid of another PeripheralMutex usage.
2022-11-25 22:30:47 +01:00
fa37452359 Merge pull request #974 from embassy-rs/afit
Switch to async-fn-in-trait
2022-11-25 22:09:57 +01:00
1e2fb0459d Switch to async-fn-in-trait 2022-11-25 21:02:06 +01:00
83c2f8f416 Merge #1077
1077: fix: bump embassy-boot version r=lulf a=lulf

bors r+

Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-11-25 14:53:35 +00:00
09077f133d fix: bump embassy-boot version 2022-11-25 15:51:31 +01:00
c7be481190 Merge #1075
1075: fix: add required metadata for embassy-boot r=lulf a=lulf



Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
2022-11-25 13:12:24 +00:00
89821846d7 fix: add required metadata for embassy-boot 2022-11-25 11:43:12 +01:00
5aad2129ef added the runner for stm32f091rc 2022-11-23 17:51:43 +05:30
28991d7794 added blinky example for stm32f0 2022-11-23 17:30:58 +05:30
15a93246d6 Buffer management in line with other peripherals. Constructor and config redesign 2022-11-19 19:18:20 +01:00
f5391efe22 Fix fmt 2022-11-19 02:17:58 +01:00
64e8cfef8e Fix build 2022-11-19 01:38:03 +01:00
16838f8a66 Fix format 2022-11-19 00:32:09 +01:00
6b88057aef Add missing parts and Cleanup 2022-11-19 00:29:05 +01:00
1ed260b105 Fix buffer overruns 2022-11-17 00:19:22 +01:00
4fe834db2f Mono channels 2022-11-13 02:48:07 +01:00
17857bc18f Minor changes 2022-11-13 02:12:58 +01:00
d2e8794f29 Investigating discontinuities in the signal 2022-11-13 01:41:32 +01:00
122a31d208 Interrupts, async, sine oscillator 2022-11-12 18:48:57 +01:00
10e3c3f2ec Cargo fmt 2022-11-11 23:49:20 +01:00
4a2e810485 Restrict to pacs supporting i2s 2022-11-10 23:13:01 +01:00
dbe97b4098 Adapted nvmc so it can be used for all nrf targets 2022-11-10 14:37:42 +01:00
f22f36f51b Add input rx 2022-11-10 00:24:49 +01:00
5a64bf651c Buffer trait. Simpler config. 2022-11-10 00:10:42 +01:00
356beabc3b Apply config 2022-11-09 23:08:09 +01:00
3760b60db3 Make bors grin ;) 2022-11-09 22:31:19 +01:00
cecd77938c Draft: Initial support for I2S with a working example.
Co-authored-by: @brainstorm <brainstorm@nopcode.org>
2022-11-09 19:19:01 +01:00
86113e199f Remove unused feature gate 2022-10-11 10:35:43 +02:00
9223b67306 Fix RTC for v2l0 & v2l1 2022-10-11 10:28:28 +02:00
aff265a7f5 Merge branch 'master' of https://github.com/embassy-rs/embassy into embassy-stm32/rtc 2022-10-11 09:19:55 +02:00
79cee74151 Fix stm32wl55jc-cm4 RTC 2022-10-11 09:19:47 +02:00
62c0b18f10 Merge branch 'master' of https://github.com/embassy-rs/embassy into embassy-stm32/rtc 2022-09-30 06:15:12 +02:00
a83560c6b1 Implement RTC peripheral for all stm32 families with rtc 2022-09-29 07:49:32 +02:00
611 changed files with 39061 additions and 25842 deletions

View File

@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
crates:
- stm32
#- stm32 # runs out of disk space...
- rest
# This will ensure at most one doc build job is running at a time
@ -46,7 +46,7 @@ jobs:
- name: Install docserver
run: |
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.3/builder"
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder"
chmod +x /usr/local/bin/builder
- name: build-stm32
@ -54,7 +54,6 @@ jobs:
run: |
mkdir crates
builder ./embassy-stm32 crates/embassy-stm32/git.zup
builder ./stm32-metapac crates/stm32-metapac/git.zup
- name: build-rest
if: ${{ matrix.crates=='rest' }}
@ -62,6 +61,7 @@ jobs:
mkdir crates
builder ./embassy-boot/boot crates/embassy-boot/git.zup
builder ./embassy-boot/nrf crates/embassy-boot-nrf/git.zup
builder ./embassy-boot/rp crates/embassy-boot-rp/git.zup
builder ./embassy-boot/stm32 crates/embassy-boot-stm32/git.zup
builder ./embassy-cortex-m crates/embassy-cortex-m/git.zup
builder ./embassy-embedded-hal crates/embassy-embedded-hal/git.zup
@ -69,12 +69,15 @@ jobs:
builder ./embassy-futures crates/embassy-futures/git.zup
builder ./embassy-lora crates/embassy-lora/git.zup
builder ./embassy-net crates/embassy-net/git.zup
builder ./embassy-net-driver crates/embassy-net-driver/git.zup
builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup
builder ./embassy-nrf crates/embassy-nrf/git.zup
builder ./embassy-rp crates/embassy-rp/git.zup
builder ./embassy-sync crates/embassy-sync/git.zup
builder ./embassy-time crates/embassy-time/git.zup
builder ./embassy-usb crates/embassy-usb/git.zup
builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup
builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup
- name: upload
run: |
@ -82,5 +85,3 @@ jobs:
echo "${{secrets.KUBECONFIG}}" > ~/.kube/config
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
kubectl cp crates $POD:/data

View File

@ -68,5 +68,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Test
run: cd embassy-sync && cargo test
- name: Test boot
working-directory: ./embassy-boot/boot
run: cargo test && cargo test --features nightly && cargo test --features "ed25519-dalek,nightly" && cargo test --features "ed25519-salty,nightly"
- name: Test sync
working-directory: ./embassy-sync
run: cargo test

4
.gitignore vendored
View File

@ -4,8 +4,4 @@ target_ci_stable
Cargo.lock
third_party
/Cargo.toml
stm32-metapac-gen/out/
stm32-metapac-backup
stm32-metapac/src/chips
stm32-metapac/src/peripherals
out/

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "stm32-data"]
path = stm32-data
url = https://github.com/embassy-rs/stm32-data.git

11
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}

31
.vscode/settings.json vendored
View File

@ -1,28 +1,26 @@
{
"editor.formatOnSave": true,
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.noDefaultFeatures": true,
"[toml]": {
"editor.formatOnSave": false
},
"rust-analyzer.check.allTargets": false,
"rust-analyzer.check.noDefaultFeatures": true,
"rust-analyzer.cargo.noDefaultFeatures": true,
"rust-analyzer.procMacro.enable": true,
"rust-analyzer.cargo.target": "thumbv7em-none-eabi",
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
"rust-analyzer.cargo.features": [
// These are needed to prevent embassy-net from failing to build
//"embassy-net/medium-ethernet",
//"embassy-net/tcp",
//"embassy-net/pool-16",
//"time-tick-16mhz",
//"defmt-timestamp-uptime",
"nightly",
//"unstable-traits",
],
"rust-analyzer.linkedProjects": [
// Declare for the target you wish to develop
//"embassy-executor/Cargo.toml",
//"embassy-sync/Cargo.toml",
"examples/nrf/Cargo.toml",
// "embassy-executor/Cargo.toml",
// "embassy-sync/Cargo.toml",
"examples/nrf52840/Cargo.toml",
// "examples/nrf5340/Cargo.toml",
// "examples/nrf-rtos-trace/Cargo.toml",
// "examples/rp/Cargo.toml",
// "examples/std/Cargo.toml",
// "examples/stm32c0/Cargo.toml",
// "examples/stm32f0/Cargo.toml",
// "examples/stm32f1/Cargo.toml",
// "examples/stm32f2/Cargo.toml",
@ -31,6 +29,7 @@
// "examples/stm32f7/Cargo.toml",
// "examples/stm32g0/Cargo.toml",
// "examples/stm32g4/Cargo.toml",
// "examples/stm32h5/Cargo.toml",
// "examples/stm32h7/Cargo.toml",
// "examples/stm32l0/Cargo.toml",
// "examples/stm32l1/Cargo.toml",
@ -38,13 +37,7 @@
// "examples/stm32l5/Cargo.toml",
// "examples/stm32u5/Cargo.toml",
// "examples/stm32wb/Cargo.toml",
// "examples/stm32wb55/Cargo.toml",
// "examples/stm32wl/Cargo.toml",
// "examples/stm32wl55/Cargo.toml",
// "examples/wasm/Cargo.toml",
],
"rust-analyzer.imports.granularity.enforce": true,
"rust-analyzer.imports.granularity.group": "module",
"rust-analyzer.cargo.buildScripts.enable": true,
"rust-analyzer.procMacro.attributes.enable": false,
}

View File

@ -14,12 +14,16 @@ Rust's <a href="https://rust-lang.github.io/async-book/">async/await</a> allows
- **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy.
- <a href="https://docs.embassy.dev/embassy-stm32/">embassy-stm32</a>, for all STM32 microcontroller families.
- <a href="https://docs.embassy.dev/embassy-nrf/">embassy-nrf</a>, for the Nordic Semiconductor nRF52, nRF53, nRF91 series.
- <a href="https://docs.embassy.dev/embassy-rp/">embassy-rp</a>, for the Raspberry Pi RP2040 microcontroller.
- <a href="https://github.com/esp-rs">esp-rs</a>, for the Espressif Systems ESP32 series of chips.
- Embassy HAL support for Espressif chips is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository.
- Async WiFi, Bluetooth and ESP-NOW is being developed in the [esp-rs/esp-wifi](https://github.com/esp-rs/esp-wifi) repository.
- **Time that Just Works** -
No more messing with hardware timers. <a href="https://docs.embassy.dev/embassy-time">embassy_time</a> provides Instant, Duration and Timer types that are globally available and never overflow.
- **Real-time ready** -
Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the <a href="https://github.com/embassy-rs/embassy/blob/master/examples/nrf/src/bin/multiprio.rs">example</a>.
Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the <a href="https://github.com/embassy-rs/embassy/blob/master/examples/nrf52840/src/bin/multiprio.rs">example</a>.
- **Low-power ready** -
Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting.
@ -31,7 +35,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
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.
- **LoRa** -
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
- **USB** -
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
@ -87,7 +91,8 @@ async fn main(spawner: Spawner) {
Examples are found in the `examples/` folder seperated by the chip manufacturer they are designed to run on. For example:
* `examples/nrf` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards.
* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards.
* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095).
* `examples/stm32xx` for the various STM32 families.
* `examples/rp` are for the RP2040 chip.
* `examples/std` are designed to run locally on your PC.
@ -101,16 +106,16 @@ git submodule init
git submodule update
```
- Install `probe-run` with defmt support.
- Install `probe-rs-cli` with defmt support.
```bash
cargo install probe-run
cargo install probe-rs-cli
```
- Change directory to the sample's base directory. For example:
```bash
cd examples/nrf
cd examples/nrf52840
```
- Run the example

64
ci.sh
View File

@ -8,44 +8,31 @@ export DEFMT_LOG=trace
TARGET=$(rustc -vV | sed -n 's|host: ||p')
BUILD_EXTRA=""
if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then
BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std"
else
BUILD_EXTRA=""
fi
find . -name '*.rs' -not -path '*target*' -not -path '*stm32-metapac-gen/out/*' -not -path '*stm32-metapac/src/*' | xargs rustfmt --check --skip-children --unstable-features --edition 2018
# Generate stm32-metapac
if [ ! -d "stm32-metapac-backup" ]
then
cp -r stm32-metapac stm32-metapac-backup
fi
rm -rf stm32-metapac
cp -r stm32-metapac-backup stm32-metapac
# for some reason Cargo stomps the cache if we don't specify --target.
# This happens with vanilla Cargo, not just cargo-batch. Bug?
(cd stm32-metapac-gen; cargo run --release --target $TARGET)
rm -rf stm32-metapac
mv stm32-metapac-gen/out stm32-metapac
find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2018
cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
--- 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-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,nightly \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits,nightly \
--- 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-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,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-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 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1,reset-pin-as-gpio \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits,nfc-pins-as-gpio \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \
@ -61,7 +48,9 @@ cargo batch \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f730i8,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \
@ -74,18 +63,23 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
--- 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,stm32wle5ub,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 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 \
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4,nightly \
--- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf \
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \
--- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \
--- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \
--- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \
@ -93,8 +87,10 @@ cargo batch \
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \
--- 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 \
--- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32g0 \
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h5 \
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \
--- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \
@ -103,7 +99,9 @@ cargo batch \
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/nrf --bin b \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/boot/nrf --bin b \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns --out-dir out/examples/boot/nrf --bin b \
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/rp --bin b \
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \
@ -112,16 +110,22 @@ cargo batch \
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/bluepill-stm32f103c8 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/nucleo-stm32f429zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/nucleo-stm32g491re \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/nucleo-stm32g071rb \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/nucleo-stm32c031c6 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/nucleo-stm32h755zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/nucleo-stm32h563zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
$BUILD_EXTRA

View File

@ -9,12 +9,17 @@ export DEFMT_LOG=trace
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
cargo batch \
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
--- 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,dhcpv4,medium-ethernet,pool-16 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,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-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,7 +41,7 @@ cargo batch \
--- 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 \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55vy,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55uc-cm4,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55cc-cm4,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r9zi,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303vc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any \
@ -65,5 +70,5 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf --bin raw_spawn \
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf52840 --bin raw_spawn \
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --no-default-features --out-dir out/examples/stm32l0 --bin raw_spawn \

View File

@ -6,7 +6,7 @@ version = "0.1.0"
license = "MIT OR Apache-2.0"
[dependencies]
embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers"] }
embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] }
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] }

View File

@ -10,7 +10,6 @@ members = [
[patch.crates-io]
embassy-executor = { path = "../../../../../embassy-executor" }
embassy-stm32 = { path = "../../../../../embassy-stm32" }
stm32-metapac = { path = "../../../../../stm32-metapac" }
[profile.release]
codegen-units = 1

View File

@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
cortex-m = "0.7"
cortex-m-rt = "0.7"
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"], default-features = false }
embassy-executor = { version = "0.1.0", default-features = false, features = ["nightly"] }
embassy-executor = { version = "0.2.0", default-features = false, features = ["nightly", "arch-cortex-m", "executor-thread"] }
defmt = "0.3.0"
defmt-rtt = "0.3.0"

View File

@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
stm32-metapac = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] }
stm32-metapac = { version = "1", features = ["stm32l475vg", "memory-x"] }
defmt = "0.3.0"
defmt-rtt = "0.3.0"

View File

@ -6,7 +6,7 @@ The bootloader can be used either as a library or be flashed directly if you are
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
The bootloader supports both internal and external flash by relying on the `embedded-storage` traits.
The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. The bootloader optionally supports the verification of firmware that has been digitally signed (recommended).
== Hardware support
@ -15,6 +15,7 @@ The bootloader supports
* nRF52 with and without softdevice
* STM32 L4, WB, WL, L1, L0, F3, F7 and H7
* Raspberry Pi: RP2040
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
@ -43,3 +44,51 @@ The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed
The BOOTLOADER_STATE partition must be big enough to store one word per page in the ACTIVE and DFU partitions combined.
The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
=== FirmwareUpdater
The `FirmwareUpdater` is an object for conveniently flashing firmware to the DFU partition and subsequently marking it as being ready for swapping with the active partition on the next reset. Its principle methods are `write_firmware`, which is called once per the size of the flash "write block" (typically 4KiB), and `mark_updated`, which is the final call.
=== Verification
The bootloader supports the verification of firmware that has been flashed to the DFU partition. Verification requires that firmware has been signed digitally using link:https://ed25519.cr.yp.to/[`ed25519`] signatures. With verification enabled, the `FirmwareUpdater::verify_and_mark_updated` method is called in place of `mark_updated`. A public key and signature are required, along with the actual length of the firmware that has been flashed. If verification fails then the firmware will not be marked as updated and therefore be rejected.
Signatures are normally conveyed with the firmware to be updated and not written to flash. How signatures are provided is a firmware responsibility.
To enable verification use either the `ed25519-dalek` or `ed25519-salty` features when depending on the `embassy-boot` crate. We recommend `ed25519-salty` at this time due to its small size.
==== Tips on keys and signing with ed25519
Ed25519 is a public key signature system where you are responsible for keeping the private key secure. We recommend embedding the *public* key in your program so that it can be easily passed to `verify_and_mark_updated`. An example declaration of the public key in your firmware:
[source, rust]
----
static PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("key.pub");
----
Signatures are often conveyed along with firmware by appending them.
Ed25519 keys can be generated by a variety of tools. We recommend link:https://man.openbsd.org/signify[`signify`] as it is in wide use to sign and verify OpenBSD distributions, and is straightforward to use.
The following set of Bash commands can be used to generate public and private keys on Unix platforms, and also generate a local `key.pub` file with the `signify` file headers removed. Declare a `SECRETS_DIR` environment variable in a secure location.
[source, bash]
----
signify -G -n -p $SECRETS_DIR/key.pub -s $SECRETS_DIR/key.sec
tail -n1 $SECRETS_DIR/key.pub | base64 -d -i - | dd ibs=10 skip=1 > key.pub
chmod 700 $SECRETS_DIR/key.sec
export SECRET_SIGNING_KEY=$(tail -n1 $SECRETS_DIR/key.sec)
----
Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware filename of `myfirmware`:
[source, bash]
----
shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt
cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt
signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed
----
Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware.

View File

@ -45,7 +45,7 @@ You can run an example by opening a terminal and entering the following commands
[source, bash]
----
cd examples/nrf
cd examples/nrf52840
cargo run --bin blinky --release
----

View File

@ -1,26 +1,54 @@
[package]
edition = "2021"
name = "embassy-boot"
version = "0.1.0"
description = "Bootloader using Embassy"
version = "0.1.1"
description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
license = "MIT OR Apache-2.0"
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-boot-v$VERSION/embassy-boot/boot/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]
[package.metadata.docs.rs]
features = ["defmt"]
[lib]
[dependencies]
defmt = { version = "0.3", optional = true }
digest = "0.10"
log = { version = "0.4", optional = true }
embassy-sync = { version = "0.1.0", path = "../../embassy-sync" }
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
embedded-storage = "0.3.0"
embedded-storage-async = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true}
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
signature = { version = "1.6.4", default-features = false }
[dev-dependencies]
log = "0.4"
env_logger = "0.9"
rand = "0.8"
rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
futures = { version = "0.3", features = ["executor"] }
sha1 = "0.10.5"
[dev-dependencies.ed25519-dalek]
default_features = false
features = ["rand", "std", "u32_backend"]
[features]
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
ed25519-salty = ["dep:salty", "_verify"]
nightly = ["dep:embedded-storage-async"]
#Internal features
_verify = []

View File

@ -13,11 +13,12 @@ By design, the bootloader does not provide any network capabilities. Networking
The bootloader supports different hardware in separate crates:
* `embassy-boot-nrf` - for the nRF microcontrollers.
* `embassy-boot-rp` - for the RP2040 microcontrollers.
* `embassy-boot-stm32` - for the STM32 microcontrollers.
## Minimum supported Rust version (MSRV)
`embassy-boot` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
## License

View File

@ -0,0 +1,533 @@
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
/// Errors returned by bootloader
#[derive(PartialEq, Eq, Debug)]
pub enum BootError {
/// Error from flash.
Flash(NorFlashErrorKind),
/// Invalid bootloader magic
BadMagic,
}
#[cfg(feature = "defmt")]
impl defmt::Format for BootError {
fn format(&self, fmt: defmt::Formatter) {
match self {
BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
}
}
}
impl<E> From<E> for BootError
where
E: NorFlashError,
{
fn from(error: E) -> Self {
BootError::Flash(error.kind())
}
}
/// Trait defining the flash handles used for active and DFU partition.
pub trait FlashConfig {
/// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
const STATE_ERASE_VALUE: u8 = 0xFF;
/// Flash type used for the state partition.
type STATE: NorFlash;
/// Flash type used for the active partition.
type ACTIVE: NorFlash;
/// Flash type used for the dfu partition.
type DFU: NorFlash;
/// Return flash instance used to write/read to/from active partition.
fn active(&mut self) -> &mut Self::ACTIVE;
/// Return flash instance used to write/read to/from dfu partition.
fn dfu(&mut self) -> &mut Self::DFU;
/// Return flash instance used to write/read to/from bootloader state.
fn state(&mut self) -> &mut Self::STATE;
}
trait FlashConfigEx {
fn page_size() -> u32;
}
impl<T: FlashConfig> FlashConfigEx for T {
/// Get the page size which is the "unit of operation" within the bootloader.
fn page_size() -> u32 {
core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32
}
}
/// BootLoader works with any flash implementing embedded_storage.
pub struct BootLoader {
// Page with current state of bootloader. The state partition has the following format:
// All ranges are in multiples of WRITE_SIZE bytes.
// | Range | Description |
// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
// | 2..2 + N | Progress index used while swapping or reverting |
state: Partition,
// Location of the partition which will be booted from
active: Partition,
// Location of the partition which will be swapped in when requested
dfu: Partition,
}
impl BootLoader {
/// Create a new instance of a bootloader with the given partitions.
///
/// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
/// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self { active, dfu, state }
}
/// Return the offset of the active partition into the active flash.
pub fn boot_address(&self) -> usize {
self.active.from as usize
}
/// Perform necessary boot preparations like swapping images.
///
/// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
/// algorithm to work correctly.
///
/// The provided aligned_buf argument must satisfy any alignment requirements
/// given by the partition flashes. All flash operations will use this buffer.
///
/// SWAPPING
///
/// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
/// The swap index contains the copy progress, as to allow continuation of the copy process on
/// power failure. The index counter is represented within 1 or more pages (depending on total
/// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
/// contains a zero value. This ensures that index updates can be performed atomically and
/// avoid a situation where the wrong index value is set (page write size is "atomic").
///
/// +-----------+------------+--------+--------+--------+--------+
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
/// +-----------+------------+--------+--------+--------+--------+
/// | Active | 0 | 1 | 2 | 3 | - |
/// | DFU | 0 | 3 | 2 | 1 | X |
/// +-----------+------------+--------+--------+--------+--------+
///
/// The algorithm starts by copying 'backwards', and after the first step, the layout is
/// as follows:
///
/// +-----------+------------+--------+--------+--------+--------+
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
/// +-----------+------------+--------+--------+--------+--------+
/// | Active | 1 | 1 | 2 | 1 | - |
/// | DFU | 1 | 3 | 2 | 1 | 3 |
/// +-----------+------------+--------+--------+--------+--------+
///
/// The next iteration performs the same steps
///
/// +-----------+------------+--------+--------+--------+--------+
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
/// +-----------+------------+--------+--------+--------+--------+
/// | Active | 2 | 1 | 2 | 1 | - |
/// | DFU | 2 | 3 | 2 | 2 | 3 |
/// +-----------+------------+--------+--------+--------+--------+
///
/// And again until we're done
///
/// +-----------+------------+--------+--------+--------+--------+
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
/// +-----------+------------+--------+--------+--------+--------+
/// | Active | 3 | 3 | 2 | 1 | - |
/// | DFU | 3 | 3 | 1 | 2 | 3 |
/// +-----------+------------+--------+--------+--------+--------+
///
/// REVERTING
///
/// The reverting algorithm uses the swap index to discover that images were swapped, but that
/// the application failed to mark the boot successful. In this case, the revert algorithm will
/// run.
///
/// The revert index is located separately from the swap index, to ensure that revert can continue
/// on power failure.
///
/// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
///
/// +-----------+--------------+--------+--------+--------+--------+
/// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
//*/
/// +-----------+--------------+--------+--------+--------+--------+
/// | Active | 3 | 1 | 2 | 1 | - |
/// | DFU | 3 | 3 | 1 | 2 | 3 |
/// +-----------+--------------+--------+--------+--------+--------+
///
///
/// +-----------+--------------+--------+--------+--------+--------+
/// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
/// +-----------+--------------+--------+--------+--------+--------+
/// | Active | 3 | 1 | 2 | 1 | - |
/// | DFU | 3 | 3 | 2 | 2 | 3 |
/// +-----------+--------------+--------+--------+--------+--------+
///
/// +-----------+--------------+--------+--------+--------+--------+
/// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
/// +-----------+--------------+--------+--------+--------+--------+
/// | Active | 3 | 1 | 2 | 3 | - |
/// | DFU | 3 | 3 | 2 | 1 | 3 |
/// +-----------+--------------+--------+--------+--------+--------+
///
pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
// Ensure we have enough progress pages to store copy progress
assert_eq!(0, P::page_size() % aligned_buf.len() as u32);
assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32);
assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32);
assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32);
assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32);
assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
// Copy contents from partition N to active
let state = self.read_state(p, aligned_buf)?;
if state == State::Swap {
//
// Check if we already swapped. If we're in the swap state, this means we should revert
// since the app has failed to mark boot as successful
//
if !self.is_swapped(p, aligned_buf)? {
trace!("Swapping");
self.swap(p, aligned_buf)?;
trace!("Swapping done");
} else {
trace!("Reverting");
self.revert(p, aligned_buf)?;
let state_flash = p.state();
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
// Invalidate progress
state_word.fill(!P::STATE_ERASE_VALUE);
self.state
.write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
// Clear magic and progress
self.state.wipe_blocking(state_flash)?;
// Set magic
state_word.fill(BOOT_MAGIC);
self.state.write_blocking(state_flash, 0, state_word)?;
}
}
Ok(state)
}
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
let page_count = (self.active.size() / P::page_size()) as usize;
let progress = self.current_progress(p, aligned_buf)?;
Ok(progress >= page_count * 2)
}
fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
let write_size = P::STATE::WRITE_SIZE as u32;
let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize;
let state_flash = config.state();
let state_word = &mut aligned_buf[..write_size as usize];
self.state.read_blocking(state_flash, write_size, state_word)?;
if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
// Progress is invalid
return Ok(max_index);
}
for index in 0..max_index {
self.state
.read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
return Ok(index);
}
}
Ok(max_index)
}
fn update_progress<P: FlashConfig>(
&mut self,
progress_index: usize,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
state_word.fill(!P::STATE_ERASE_VALUE);
self.state.write_blocking(
p.state(),
(2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32,
state_word,
)?;
Ok(())
}
fn copy_page_once_to_active<P: FlashConfig>(
&mut self,
progress_index: usize,
from_offset: u32,
to_offset: u32,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= progress_index {
let page_size = P::page_size() as u32;
self.active
.erase_blocking(p.active(), to_offset, to_offset + page_size)?;
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
self.dfu
.read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
self.active
.write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
}
self.update_progress(progress_index, p, aligned_buf)?;
}
Ok(())
}
fn copy_page_once_to_dfu<P: FlashConfig>(
&mut self,
progress_index: usize,
from_offset: u32,
to_offset: u32,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= progress_index {
let page_size = P::page_size() as u32;
self.dfu
.erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
self.active
.read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
self.dfu
.write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
}
self.update_progress(progress_index, p, aligned_buf)?;
}
Ok(())
}
fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_size = P::page_size();
let page_count = self.active.size() / page_size;
for page_num in 0..page_count {
let progress_index = (page_num * 2) as usize;
// Copy active page to the 'next' DFU page.
let active_from_offset = (page_count - 1 - page_num) * page_size;
let dfu_to_offset = (page_count - page_num) * page_size;
//trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
// Copy DFU page to the active page
let active_to_offset = (page_count - 1 - page_num) * page_size;
let dfu_from_offset = (page_count - 1 - page_num) * page_size;
//trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
}
Ok(())
}
fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_size = P::page_size();
let page_count = self.active.size() / page_size;
for page_num in 0..page_count {
let progress_index = (page_count * 2 + page_num * 2) as usize;
// Copy the bad active page to the DFU page
let active_from_offset = page_num * page_size;
let dfu_to_offset = page_num * page_size;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
// Copy the DFU page back to the active page
let active_to_offset = page_num * page_size;
let dfu_from_offset = (page_num + 1) * page_size;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
}
Ok(())
}
fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
self.state.read_blocking(config.state(), 0, state_word)?;
if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
}
fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) {
assert_eq!(active.size() % page_size, 0);
assert_eq!(dfu.size() % page_size, 0);
assert!(dfu.size() - active.size() >= page_size);
assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32);
}
/// A flash wrapper implementing the Flash and embedded_storage traits.
pub struct BootFlash<F>
where
F: NorFlash,
{
flash: F,
}
impl<F> BootFlash<F>
where
F: NorFlash,
{
/// Create a new instance of a bootable flash
pub fn new(flash: F) -> Self {
Self { flash }
}
}
impl<F> ErrorType for BootFlash<F>
where
F: NorFlash,
{
type Error = F::Error;
}
impl<F> NorFlash for BootFlash<F>
where
F: NorFlash,
{
const WRITE_SIZE: usize = F::WRITE_SIZE;
const ERASE_SIZE: usize = F::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
F::erase(&mut self.flash, from, to)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
F::write(&mut self.flash, offset, bytes)
}
}
impl<F> ReadNorFlash for BootFlash<F>
where
F: NorFlash,
{
const READ_SIZE: usize = F::READ_SIZE;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
F::read(&mut self.flash, offset, bytes)
}
fn capacity(&self) -> usize {
F::capacity(&self.flash)
}
}
/// Convenience provider that uses a single flash for all partitions.
pub struct SingleFlashConfig<'a, F>
where
F: NorFlash,
{
flash: &'a mut F,
}
impl<'a, F> SingleFlashConfig<'a, F>
where
F: NorFlash,
{
/// Create a provider for a single flash.
pub fn new(flash: &'a mut F) -> Self {
Self { flash }
}
}
impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
where
F: NorFlash,
{
type STATE = F;
type ACTIVE = F;
type DFU = F;
fn active(&mut self) -> &mut Self::STATE {
self.flash
}
fn dfu(&mut self) -> &mut Self::ACTIVE {
self.flash
}
fn state(&mut self) -> &mut Self::DFU {
self.flash
}
}
/// Convenience flash provider that uses separate flash instances for each partition.
pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
active: &'a mut ACTIVE,
state: &'a mut STATE,
dfu: &'a mut DFU,
}
impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
/// Create a new flash provider with separate configuration for all three partitions.
pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
Self { active, state, dfu }
}
}
impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
type STATE = STATE;
type ACTIVE = ACTIVE;
type DFU = DFU;
fn active(&mut self) -> &mut Self::ACTIVE {
self.active
}
fn dfu(&mut self) -> &mut Self::DFU {
self.dfu
}
fn state(&mut self) -> &mut Self::STATE {
self.state
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_range_asserts() {
const ACTIVE: Partition = Partition::new(4096, 4194304);
const DFU: Partition = Partition::new(4194304, 2 * 4194304);
const STATE: Partition = Partition::new(0, 4096);
assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
}
}

View File

@ -0,0 +1,30 @@
use digest::typenum::U64;
use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
use ed25519_dalek::Digest as _;
pub struct Sha512(ed25519_dalek::Sha512);
impl Default for Sha512 {
fn default() -> Self {
Self(ed25519_dalek::Sha512::new())
}
}
impl Update for Sha512 {
fn update(&mut self, data: &[u8]) {
self.0.update(data)
}
}
impl FixedOutput for Sha512 {
fn finalize_into(self, out: &mut digest::Output<Self>) {
let result = self.0.finalize();
out.as_mut_slice().copy_from_slice(result.as_slice())
}
}
impl OutputSizeUser for Sha512 {
type OutputSize = U64;
}
impl HashMarker for Sha512 {}

View File

@ -0,0 +1,5 @@
#[cfg(feature = "ed25519-dalek")]
pub(crate) mod ed25519_dalek;
#[cfg(feature = "ed25519-salty")]
pub(crate) mod salty;

View File

@ -0,0 +1,29 @@
use digest::typenum::U64;
use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
pub struct Sha512(salty::Sha512);
impl Default for Sha512 {
fn default() -> Self {
Self(salty::Sha512::new())
}
}
impl Update for Sha512 {
fn update(&mut self, data: &[u8]) {
self.0.update(data)
}
}
impl FixedOutput for Sha512 {
fn finalize_into(self, out: &mut digest::Output<Self>) {
let result = self.0.finalize();
out.as_mut_slice().copy_from_slice(result.as_slice())
}
}
impl OutputSizeUser for Sha512 {
type OutputSize = U64;
}
impl HashMarker for Sha512 {}

View File

@ -0,0 +1,542 @@
use digest::Digest;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
/// Errors returned by FirmwareUpdater
#[derive(Debug)]
pub enum FirmwareUpdaterError {
/// Error from flash.
Flash(NorFlashErrorKind),
/// Signature errors.
Signature(signature::Error),
}
#[cfg(feature = "defmt")]
impl defmt::Format for FirmwareUpdaterError {
fn format(&self, fmt: defmt::Formatter) {
match self {
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
}
}
}
impl<E> From<E> for FirmwareUpdaterError
where
E: NorFlashError,
{
fn from(error: E) -> Self {
FirmwareUpdaterError::Flash(error.kind())
}
}
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct FirmwareUpdater {
state: Partition,
dfu: Partition,
}
impl Default for FirmwareUpdater {
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
FirmwareUpdater::new(dfu, state)
}
}
impl FirmwareUpdater {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
pub const fn new(dfu: Partition, state: Partition) -> Self {
Self { dfu, state }
}
/// 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`.
#[cfg(feature = "nightly")]
pub async fn get_state<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
self.state.read(state_flash, 0, aligned).await?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(all(feature = "_verify", feature = "nightly"))]
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
&mut self,
_state_and_dfu_flash: &mut F,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(_aligned.len(), F::WRITE_SIZE);
assert!(_update_len <= self.dfu.size());
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
use crate::digest_adapters::ed25519_dalek::Sha512;
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
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 message = [0; 64];
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
.await?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Signature};
use crate::digest_adapters::salty::Sha512;
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
.await?;
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
}
/// Verify the update in DFU with any digest.
#[cfg(feature = "nightly")]
pub async fn hash<F: AsyncNorFlash, D: Digest>(
&mut self,
dfu_flash: &mut F,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read(dfu_flash, offset, chunk_buf).await?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
output.copy_from_slice(digest.finalize().as_slice());
Ok(())
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
pub async fn mark_updated<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC, state_flash).await
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(feature = "nightly")]
pub async fn mark_booted<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
}
#[cfg(feature = "nightly")]
async fn set_magic<F: AsyncNorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
state_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
self.state.read(state_flash, 0, aligned).await?;
if aligned.iter().any(|&b| b != magic) {
// Read progress validity
self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
// FIXME: Do not make this assumption.
const STATE_ERASE_VALUE: u8 = 0xFF;
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_flash, F::WRITE_SIZE as u32, aligned).await?;
}
// Clear magic and progress
self.state.wipe(state_flash).await?;
// Set magic
aligned.fill(magic);
self.state.write(state_flash, 0, aligned).await?;
}
Ok(())
}
/// Write data to a flash page.
///
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
///
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
#[cfg(feature = "nightly")]
pub async fn write_firmware<F: AsyncNorFlash>(
&mut self,
offset: usize,
data: &[u8],
dfu_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
self.dfu
.erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
.await?;
self.dfu.write(dfu_flash, offset as u32, data).await?;
Ok(())
}
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
#[cfg(feature = "nightly")]
pub async fn prepare_update<F: AsyncNorFlash>(
&mut self,
dfu_flash: &mut F,
) -> Result<Partition, FirmwareUpdaterError> {
self.dfu.wipe(dfu_flash).await?;
Ok(self.dfu)
}
//
// Blocking API
//
/// 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_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
self.state.read_blocking(state_flash, 0, aligned)?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// The `_aligned` buffer must have a size of F::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_blocking<F: NorFlash>(
&mut self,
_state_and_dfu_flash: &mut F,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(_aligned.len(), F::WRITE_SIZE);
assert!(_update_len <= self.dfu.size());
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
use crate::digest_adapters::ed25519_dalek::Sha512;
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
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 message = [0; 64];
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Signature};
use crate::digest_adapters::salty::Sha512;
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
}
/// Verify the update in DFU with any digest.
pub fn hash_blocking<F: NorFlash, D: Digest>(
&mut self,
dfu_flash: &mut F,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
output.copy_from_slice(digest.finalize().as_slice());
Ok(())
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
pub fn mark_updated_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub fn mark_booted_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
}
fn set_magic_blocking<F: NorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
state_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
self.state.read_blocking(state_flash, 0, aligned)?;
if aligned.iter().any(|&b| b != magic) {
// Read progress validity
self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
// FIXME: Do not make this assumption.
const STATE_ERASE_VALUE: u8 = 0xFF;
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_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
}
// Clear magic and progress
self.state.wipe_blocking(state_flash)?;
// Set magic
aligned.fill(magic);
self.state.write_blocking(state_flash, 0, aligned)?;
}
Ok(())
}
/// Write data to a flash page.
///
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
///
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
pub fn write_firmware_blocking<F: NorFlash>(
&mut self,
offset: usize,
data: &[u8],
dfu_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
self.dfu
.erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
Ok(())
}
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware_blocking` allows for an optimized
/// API in exchange for added complexity.
pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
self.dfu.wipe_blocking(flash)?;
Ok(self.dfu)
}
}
#[cfg(test)]
mod tests {
use futures::executor::block_on;
use sha1::{Digest, Sha1};
use super::*;
use crate::mem_flash::MemFlash;
#[test]
#[cfg(feature = "nightly")]
fn can_verify_sha1() {
const STATE: Partition = Partition::new(0, 4096);
const DFU: Partition = Partition::new(65536, 131072);
let mut flash = MemFlash::<131072, 4096, 8>::default();
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 = FirmwareUpdater::new(DFU, STATE);
block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
assert_eq!(Sha1::digest(update).as_slice(), hash);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
#![allow(unused)]
use core::ops::{Bound, Range, RangeBounds};
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
pub mem: [u8; SIZE],
pub pending_write_successes: Option<usize>,
}
#[derive(Debug)]
pub struct MemFlashError;
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
pub const fn new(fill: u8) -> Self {
Self {
mem: [fill; SIZE],
pending_write_successes: None,
}
}
#[cfg(test)]
pub fn random() -> Self {
let mut mem = [0; SIZE];
for byte in mem.iter_mut() {
*byte = rand::random::<u8>();
}
Self {
mem,
pending_write_successes: None,
}
}
pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
assert!(offset % WRITE_SIZE == 0);
assert!(offset + bytes.len() <= SIZE);
self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
Ok(())
}
pub fn assert_eq(&self, offset: u32, expectation: &[u8]) {
for i in 0..expectation.len() {
assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i);
}
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
fn default() -> Self {
Self::new(0xFF)
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
type Error = MemFlashError;
}
impl NorFlashError for MemFlashError {
fn kind(&self) -> NorFlashErrorKind {
NorFlashErrorKind::Other
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let len = bytes.len();
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
Ok(())
}
fn capacity(&self) -> usize {
SIZE
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
let from = from as usize;
let to = to as usize;
assert!(from % ERASE_SIZE == 0);
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
for i in from..to {
self.mem[i] = 0xFF;
}
Ok(())
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
assert!(offset % WRITE_SIZE == 0);
assert!(offset + bytes.len() <= SIZE);
if let Some(pending_successes) = self.pending_write_successes {
if pending_successes > 0 {
self.pending_write_successes = Some(pending_successes - 1);
} else {
return Err(MemFlashError);
}
}
for ((offset, mem_byte), new_byte) in self
.mem
.iter_mut()
.enumerate()
.skip(offset)
.take(bytes.len())
.zip(bytes)
{
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
*mem_byte = *new_byte;
}
Ok(())
}
}
#[cfg(feature = "nightly")]
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const READ_SIZE: usize = 1;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
<Self as ReadNorFlash>::read(self, offset, bytes)
}
fn capacity(&self) -> usize {
<Self as ReadNorFlash>::capacity(self)
}
}
#[cfg(feature = "nightly")]
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
<Self as NorFlash>::erase(self, from, to)
}
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
<Self as NorFlash>::write(self, offset, bytes)
}
}

View File

@ -0,0 +1,144 @@
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
/// A region in flash used by the bootloader.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Partition {
/// The offset into the flash where the partition starts.
pub from: u32,
/// The offset into the flash where the partition ends.
pub to: u32,
}
impl Partition {
/// Create a new partition with the provided range
pub const fn new(from: u32, to: u32) -> Self {
Self { from, to }
}
/// Return the size of the partition
pub const fn size(&self) -> u32 {
self.to - self.from
}
/// Read from the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn read<F: AsyncReadNorFlash>(
&self,
flash: &mut F,
offset: u32,
bytes: &mut [u8],
) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.read(offset, bytes).await
}
/// Write to the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.write(offset, bytes).await?;
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
Ok(())
}
/// Erase part of the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
let from = self.from as u32 + from;
let to = self.from as u32 + to;
flash.erase(from, to).await?;
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Erase the entire partition
#[cfg(feature = "nightly")]
pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
let from = self.from as u32;
let to = self.to as u32;
flash.erase(from, to).await?;
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Read from the partition on the provided flash
pub fn read_blocking<F: ReadNorFlash>(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.read(offset, bytes)
}
/// Write to the partition on the provided flash
pub fn write_blocking<F: NorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.write(offset, bytes)?;
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
Ok(())
}
/// Erase part of the partition on the provided flash
pub fn erase_blocking<F: NorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
let from = self.from as u32 + from;
let to = self.from as u32 + to;
flash.erase(from, to)?;
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Erase the entire partition
pub(crate) fn wipe_blocking<F: NorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
let from = self.from as u32;
let to = self.to as u32;
flash.erase(from, to)?;
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::mem_flash::MemFlash;
use crate::Partition;
#[test]
fn can_erase() {
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
let partition = Partition::new(256, 512);
partition.erase_blocking(&mut flash, 64, 192).unwrap();
for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) {
assert_eq!(0x00, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) {
assert_eq!(0xFF, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) {
assert_eq!(0x00, byte, "Index {}", index);
}
}
#[test]
fn can_wipe() {
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
let partition = Partition::new(256, 512);
partition.wipe_blocking(&mut flash).unwrap();
for (index, byte) in flash.mem.iter().copied().enumerate().take(256) {
assert_eq!(0x00, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) {
assert_eq!(0xFF, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) {
assert_eq!(0x00, byte, "Index {}", index);
}
}
}

View File

@ -17,12 +17,12 @@ target = "thumbv7em-none-eabi"
defmt = { version = "0.3", optional = true }
embassy-sync = { path = "../../embassy-sync" }
embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] }
embassy-nrf = { path = "../../embassy-nrf", default-features = false }
embassy-boot = { path = "../boot", default-features = false }
cortex-m = { version = "0.7.6" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
embedded-storage-async = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true }
cfg-if = "1.0.0"
nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true }
@ -36,3 +36,8 @@ defmt = [
softdevice = [
"nrf-softdevice-mbr",
]
nightly = [
"dep:embedded-storage-async",
"embassy-boot/nightly",
"embassy-nrf/nightly"
]

View File

@ -0,0 +1,26 @@
# embassy-boot-nrf
An [Embassy](https://embassy.dev) project.
An adaptation of `embassy-boot` for nRF.
## Features
* Load applications with our without the softdevice.
* Configure bootloader partitions based on linker script.
* Using watchdog timer to detect application failure.
## Minimum supported Rust version (MSRV)
`embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
## 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.

View File

@ -1,7 +1,6 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../../README.md")]
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
@ -11,13 +10,12 @@ use embassy_nrf::wdt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for nRF devices.
pub struct BootLoader {
pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> {
boot: embassy_boot::BootLoader,
magic: AlignedBuffer<4>,
page: AlignedBuffer<PAGE_SIZE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl Default for BootLoader {
impl Default for BootLoader<PAGE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
@ -31,20 +29,20 @@ impl Default for BootLoader {
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as usize,
&__bootloader_active_end as *const u32 as usize,
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
@ -56,20 +54,19 @@ impl Default for BootLoader {
}
}
impl BootLoader {
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
magic: AlignedBuffer([0; 4]),
page: AlignedBuffer([0; PAGE_SIZE]),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, &mut self.magic.0, &mut self.page.0) {
match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) {
Ok(_) => self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
@ -149,11 +146,7 @@ pub struct WatchdogFlash<'d> {
impl<'d> WatchdogFlash<'d> {
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self {
let mut config = wdt::Config::default();
config.timeout_ticks = 32768 * timeout; // timeout seconds
config.run_during_sleep = true;
config.run_during_debug_halt = false;
pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self {
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
Ok(x) => x,
Err(_) => {

View File

@ -0,0 +1,79 @@
[package]
edition = "2021"
name = "embassy-boot-rp"
version = "0.1.0"
description = "Bootloader lib for RP2040 chips"
license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/"
target = "thumbv6m-none-eabi"
[lib]
[dependencies]
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
log = { version = "0.4", optional = true }
embassy-sync = { path = "../../embassy-sync" }
embassy-rp = { path = "../../embassy-rp", default-features = false }
embassy-boot = { path = "../boot", default-features = false }
embassy-time = { path = "../../embassy-time" }
cortex-m = { version = "0.7.6" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true }
cfg-if = "1.0.0"
[features]
defmt = [
"dep:defmt",
"embassy-boot/defmt",
"embassy-rp/defmt",
]
log = [
"dep:log",
"embassy-boot/log",
"embassy-rp/log",
]
debug = ["defmt-rtt"]
nightly = [
"dep:embedded-storage-async",
"embassy-boot/nightly",
"embassy-rp/nightly",
"embassy-time/nightly"
]
[profile.dev]
debug = 2
debug-assertions = true
incremental = false
opt-level = 'z'
overflow-checks = true
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 'z'
overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false

26
embassy-boot/rp/README.md Normal file
View File

@ -0,0 +1,26 @@
# embassy-boot-rp
An [Embassy](https://embassy.dev) project.
An adaptation of `embassy-boot` for RP2040.
NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script.
## Features
* Configure bootloader partitions based on linker script.
* Load applications from active partition.
## Minimum supported Rust version (MSRV)
`embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
## 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.

8
embassy-boot/rp/build.rs Normal file
View File

@ -0,0 +1,8 @@
use std::env;
fn main() {
let target = env::var("TARGET").unwrap();
if target.starts_with("thumbv6m-") {
println!("cargo:rustc-cfg=armv6m");
}
}

225
embassy-boot/rp/src/fmt.rs Normal file
View File

@ -0,0 +1,225 @@
#![macro_use]
#![allow(unused_macros)]
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}

136
embassy-boot/rp/src/lib.rs Normal file
View File

@ -0,0 +1,136 @@
#![no_std]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
use embassy_rp::flash::{Flash, ERASE_SIZE};
use embassy_rp::peripherals::{FLASH, WATCHDOG};
use embassy_rp::watchdog::Watchdog;
use embassy_time::Duration;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for RP2040 devices.
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
boot: embassy_boot::BootLoader,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
}
/// Boots the application.
///
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! {
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
#[cfg(not(armv6m))]
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
cortex_m::asm::bootload(start as *const u32)
}
}
impl Default for BootLoader<ERASE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}
/// A flash implementation that will feed a watchdog when touching flash.
pub struct WatchdogFlash<'d, const SIZE: usize> {
flash: Flash<'d, FLASH, 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 mut watchdog = Watchdog::new(watchdog);
watchdog.start(timeout);
Self { flash, watchdog }
}
}
impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> {
type Error = <Flash<'d, FLASH, 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;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.watchdog.feed();
self.flash.erase(from, to)
}
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
self.watchdog.feed();
self.flash.write(offset, data)
}
}
impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> {
const READ_SIZE: usize = <Flash<'d, FLASH, 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)
}
fn capacity(&self) -> usize {
self.flash.capacity()
}
}

View File

@ -15,16 +15,16 @@ target = "thumbv7em-none-eabi"
[dependencies]
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
log = { version = "0.4", optional = true }
embassy-sync = { path = "../../embassy-sync" }
embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] }
embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
embassy-boot = { path = "../boot", default-features = false }
cortex-m = { version = "0.7.6" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
embedded-storage-async = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true }
cfg-if = "1.0.0"
[features]
@ -39,6 +39,11 @@ log = [
"embassy-stm32/log",
]
debug = ["defmt-rtt"]
nightly = [
"dep:embedded-storage-async",
"embassy-boot/nightly",
"embassy-stm32/nightly"
]
[profile.dev]
debug = 2

View File

@ -1,11 +1,24 @@
# Bootloader for STM32
# embassy-boot-stm32
The bootloader uses `embassy-boot` to interact with the flash.
An [Embassy](https://embassy.dev) project.
# Usage
An adaptation of `embassy-boot` for STM32.
Flash the bootloader
## Features
```
cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx
```
* Configure bootloader partitions based on linker script.
* Load applications from active partition.
## Minimum supported Rust version (MSRV)
`embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
## 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.

View File

@ -1,32 +1,29 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../../README.md")]
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
/// A bootloader for STM32 devices.
pub struct BootLoader<const PAGE_SIZE: usize, const WRITE_SIZE: usize> {
pub struct BootLoader<const BUFFER_SIZE: usize> {
boot: embassy_boot::BootLoader,
magic: AlignedBuffer<WRITE_SIZE>,
page: AlignedBuffer<PAGE_SIZE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRITE_SIZE> {
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
magic: AlignedBuffer([0; WRITE_SIZE]),
page: AlignedBuffer([0; PAGE_SIZE]),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.magic.as_mut(), self.page.as_mut()) {
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
@ -49,7 +46,7 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRIT
}
}
impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> Default for BootLoader<PAGE_SIZE, WRITE_SIZE> {
impl<const BUFFER_SIZE: usize> Default for BootLoader<BUFFER_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
@ -63,20 +60,20 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> Default for BootLoader<PAG
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as usize,
&__bootloader_active_end as *const u32 as usize,
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};

View File

@ -36,9 +36,9 @@ prio-bits-8 = []
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embassy-executor = { version = "0.1.0", path = "../embassy-executor"}
embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-executor = { version = "0.2.0", path = "../embassy-executor"}
embassy-macros = { version = "0.2.0", path = "../embassy-macros"}
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common"}
atomic-polyfill = "1.0.1"
critical-section = "1.1"

View File

@ -1,89 +0,0 @@
//! Executor specific to cortex-m devices.
use core::marker::PhantomData;
pub use embassy_executor::*;
use crate::interrupt::{Interrupt, InterruptExt};
fn pend_by_number(n: u16) {
#[derive(Clone, Copy)]
struct N(u16);
unsafe impl cortex_m::interrupt::InterruptNumber for N {
fn number(self) -> u16 {
self.0
}
}
cortex_m::peripheral::NVIC::pend(N(n))
}
/// Interrupt mode executor.
///
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
/// to poll tasks, and when a task is woken the interrupt is pended from software.
///
/// This allows running async tasks at a priority higher than thread mode. One
/// use case is to leave thread mode free for non-async tasks. Another use case is
/// to run multiple executors: one in thread mode for low priority tasks and another in
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
/// priority ones.
///
/// It is even possible to run multiple interrupt mode executors at different priorities,
/// by assigning different priorities to the interrupts. For an example on how to do this,
/// See the 'multiprio' example for 'embassy-nrf'.
///
/// To use it, you have to pick an interrupt that won't be used by the hardware.
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
/// If this is not the case, you may use an interrupt from any unused peripheral.
///
/// It is somewhat more complex to use, it's recommended to use the thread-mode
/// [`Executor`] instead, if it works for your use case.
pub struct InterruptExecutor<I: Interrupt> {
irq: I,
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl<I: Interrupt> InterruptExecutor<I> {
/// Create a new Executor.
pub fn new(irq: I) -> Self {
let ctx = irq.number() as *mut ();
Self {
irq,
inner: raw::Executor::new(|ctx| pend_by_number(ctx as u16), ctx),
not_send: PhantomData,
}
}
/// Start the executor.
///
/// This initializes the executor, configures and enables the interrupt, and returns.
/// The executor keeps running in the background through the interrupt.
///
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
/// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
/// different "thread" (the interrupt), so spawning tasks on it is effectively
/// sending them.
///
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
/// a task running in it.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
pub fn start(&'static mut self) -> SendSpawner {
self.irq.disable();
self.irq.set_handler(|ctx| unsafe {
let executor = &*(ctx as *const raw::Executor);
executor.poll();
});
self.irq.set_handler_context(&self.inner as *const _ as _);
self.irq.enable();
self.inner.spawner().make_send()
}
}

View File

@ -13,14 +13,44 @@ pub mod _export {
pub use embassy_macros::{cortex_m_interrupt as interrupt, cortex_m_interrupt_declare as declare};
}
/// Interrupt handler trait.
///
/// Drivers that need to handle interrupts implement this trait.
/// The user must ensure `on_interrupt()` is called every time the interrupt fires.
/// Drivers must use use [`Binding`] to assert at compile time that the user has done so.
pub trait Handler<I: Interrupt> {
/// Interrupt handler function.
///
/// Must be called every time the `I` interrupt fires, synchronously from
/// the interrupt handler context.
///
/// # Safety
///
/// This function must ONLY be called from the interrupt handler for `I`.
unsafe fn on_interrupt();
}
/// Compile-time assertion that an interrupt has been bound to a handler.
///
/// For the vast majority of cases, you should use the `bind_interrupts!`
/// macro instead of writing `unsafe impl`s of this trait.
///
/// # Safety
///
/// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()`
/// to be called every time the `I` interrupt fires.
///
/// This allows drivers to check bindings at compile-time.
pub unsafe trait Binding<I: Interrupt, H: Handler<I>> {}
/// Implementation detail, do not use outside embassy crates.
#[doc(hidden)]
pub struct Handler {
pub struct DynHandler {
pub func: AtomicPtr<()>,
pub ctx: AtomicPtr<()>,
}
impl Handler {
impl DynHandler {
pub const fn new() -> Self {
Self {
func: AtomicPtr::new(ptr::null_mut()),
@ -51,7 +81,7 @@ pub unsafe trait Interrupt: Peripheral<P = Self> {
/// Implementation detail, do not use outside embassy crates.
#[doc(hidden)]
unsafe fn __handler(&self) -> &'static Handler;
unsafe fn __handler(&self) -> &'static DynHandler;
}
/// Represents additional behavior for all interrupts.

View File

@ -5,6 +5,6 @@
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub mod executor;
pub use embassy_executor as executor;
pub mod interrupt;
pub mod peripheral;

View File

@ -17,12 +17,12 @@ std = []
nightly = ["embedded-hal-async", "embedded-storage-async"]
[dependencies]
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" }
embedded-hal-async = { version = "=0.1.0-alpha.3", optional = true }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.3.0", optional = true }
embedded-storage-async = { version = "0.4.0", optional = true }
nb = "1.0.0"
defmt = { version = "0.3", optional = true }

View File

@ -1,7 +1,5 @@
//! Adapters between embedded-hal traits.
use core::future::Future;
use embedded_hal_02::{blocking, serial};
/// Wrapper that implements async traits using blocking implementations.
@ -38,32 +36,26 @@ where
E: embedded_hal_1::i2c::Error + 'static,
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
{
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move { self.wrapped.read(address, buffer) }
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, read)
}
fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
async move { self.wrapped.write(address, bytes) }
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(address, write)
}
fn write_read<'a>(&'a mut self, address: u8, bytes: &'a [u8], buffer: &'a mut [u8]) -> Self::WriteReadFuture<'a> {
async move { self.wrapped.write_read(address, bytes, buffer) }
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.write_read(address, write, read)
}
type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
fn transaction<'a, 'b>(
&'a mut self,
async fn transaction(
&mut self,
address: u8,
operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
) -> Self::TransactionFuture<'a, 'b> {
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
async move { todo!() }
todo!()
}
}
@ -84,23 +76,17 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type TransferFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Self::TransferFuture<'a> {
async move {
// Ensure we write the expected bytes
for i in 0..core::cmp::min(read.len(), write.len()) {
read[i] = write[i].clone();
}
self.wrapped.transfer(read)?;
Ok(())
async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [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();
}
self.wrapped.transfer(read)?;
Ok(())
}
type TransferInPlaceFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> {
async move { todo!() }
async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> {
todo!()
}
}
@ -109,10 +95,8 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
async move { Ok(()) }
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
@ -121,13 +105,9 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
self.wrapped.write(data)?;
Ok(())
}
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(data)?;
Ok(())
}
}
@ -136,13 +116,9 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
self.wrapped.transfer(data)?;
Ok(())
}
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.transfer(data)?;
Ok(())
}
}
@ -155,51 +131,9 @@ where
type Error = E;
}
#[cfg(feature = "_todo_embedded_hal_serial")]
impl<T, E> embedded_hal_async::serial::Read for BlockingAsync<T>
where
T: serial::Read<u8, Error = E>,
E: embedded_hal_1::serial::Error + 'static,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let mut pos = 0;
while pos < buf.len() {
match self.wrapped.read() {
Err(nb::Error::WouldBlock) => {}
Err(nb::Error::Other(e)) => return Err(e),
Ok(b) => {
buf[pos] = b;
pos += 1;
}
}
}
Ok(())
}
}
}
#[cfg(feature = "_todo_embedded_hal_serial")]
impl<T, E> embedded_hal_async::serial::Write for BlockingAsync<T>
where
T: blocking::serial::Write<u8, Error = E> + serial::Read<u8, Error = E>,
E: embedded_hal_1::serial::Error + 'static,
{
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
async move { self.wrapped.bwrite_all(buf) }
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
async move { self.wrapped.bflush() }
}
}
/// NOR flash wrapper
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash};
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
impl<T> ErrorType for BlockingAsync<T>
where
@ -215,14 +149,12 @@ where
const WRITE_SIZE: usize = <T as NorFlash>::WRITE_SIZE;
const ERASE_SIZE: usize = <T as NorFlash>::ERASE_SIZE;
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
async move { self.wrapped.write(offset, data) }
async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(offset, data)
}
type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
async move { self.wrapped.erase(from, to) }
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.wrapped.erase(from, to)
}
}
@ -231,9 +163,8 @@ where
T: ReadNorFlash,
{
const READ_SIZE: usize = <T as ReadNorFlash>::READ_SIZE;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move { self.wrapped.read(address, data) }
async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, data)
}
fn capacity(&self) -> usize {

View File

@ -1,5 +1,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
#![cfg_attr(
feature = "nightly",
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections, try_blocks)
)]
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
#![warn(missing_docs)]
//! Utilities to use `embedded-hal` traits with Embassy.

View File

@ -22,7 +22,6 @@
//! let i2c_dev2 = I2cDevice::new(i2c_bus);
//! let mpu = Mpu6050::new(i2c_dev2);
//! ```
use core::future::Future;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
@ -55,53 +54,39 @@ where
M: RawMutex + 'static,
BUS: i2c::I2c + 'static,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.read(address, read).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.write(address, write).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write_read<'a>(
&'a mut self,
async fn write_read(
&mut self,
address: u8,
wr_buffer: &'a [u8],
rd_buffer: &'a mut [u8],
) -> Self::WriteReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
write: &[u8],
read: &mut [u8],
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.write_read(address, write, read)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
fn transaction<'a, 'b>(
&'a mut self,
async fn transaction(
&mut self,
address: u8,
operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
) -> Self::TransactionFuture<'a, 'b> {
operations: &mut [embedded_hal_async::i2c::Operation<'_>],
) -> Result<(), I2cDeviceError<BUS::Error>> {
let _ = address;
let _ = operations;
async move { todo!() }
todo!()
}
}
@ -136,55 +121,37 @@ where
M: RawMutex + 'static,
BUS: i2c::I2c + SetConfig + 'static,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write_read<'a>(
&'a mut self,
async fn write_read(
&mut self,
address: u8,
wr_buffer: &'a [u8],
rd_buffer: &'a mut [u8],
) -> Self::WriteReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
wr_buffer: &[u8],
rd_buffer: &mut [u8],
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
fn transaction<'a, 'b>(
&'a mut self,
address: u8,
operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
) -> Self::TransactionFuture<'a, 'b> {
async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
async move { todo!() }
todo!()
}
}

View File

@ -25,12 +25,11 @@
//! let spi_dev2 = SpiDevice::new(spi_bus, cs_pin2);
//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128);
//! ```
use core::future::Future;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
use embedded_hal_1::digital::OutputPin;
use embedded_hal_1::spi::ErrorType;
use embedded_hal_1::spi::Operation;
use embedded_hal_async::spi;
use crate::shared_bus::SpiDeviceError;
@ -57,41 +56,92 @@ where
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
unsafe impl<M, BUS, CS> spi::SpiDevice for SpiDevice<'_, M, BUS, CS>
impl<M, BUS, CS> spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS>
where
M: RawMutex + 'static,
BUS: spi::SpiBusFlush + 'static,
M: RawMutex,
BUS: spi::SpiBusRead,
CS: OutputPin,
{
type Bus = BUS;
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)?;
type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
where
Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
let op_res: Result<(), BUS::Error> = try {
for buf in operations {
bus.read(buf).await?;
}
};
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
where
R: 'a,
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
{
async move {
let mut bus = self.bus.lock().await;
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
// 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 f_res = f(&mut *bus).await;
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
let cs_res = self.cs.set_high();
Ok(op_res)
}
}
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
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)?;
Ok(f_res)
}
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,
BUS: spi::SpiBus,
CS: OutputPin,
{
async fn transaction(&mut self, operations: &mut [spi::Operation<'_, 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 op in operations {
match op {
Operation::Read(buf) => bus.read(buf).await?,
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?,
}
}
};
// 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)
}
}
@ -122,41 +172,94 @@ where
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
unsafe impl<M, BUS, CS> spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS>
impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS>
where
M: RawMutex + 'static,
BUS: spi::SpiBusFlush + SetConfig + 'static,
M: RawMutex,
BUS: spi::SpiBusWrite + SetConfig,
CS: OutputPin,
{
type Bus = BUS;
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)?;
type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
where
Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
let op_res: Result<(), BUS::Error> = try {
for buf in operations {
bus.write(buf).await?;
}
};
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
where
R: 'a,
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
{
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
// 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 f_res = f(&mut *bus).await;
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
// 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 f_res = f_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
}
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,
BUS: spi::SpiBus + SetConfig,
CS: OutputPin,
{
async fn transaction(&mut self, operations: &mut [spi::Operation<'_, 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 op in operations {
match op {
Operation::Read(buf) => bus.read(buf).await?,
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?,
}
}
};
// 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)
}
}

View File

@ -72,34 +72,6 @@ where
let _ = operations;
todo!()
}
fn write_iter<B: IntoIterator<Item = u8>>(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error> {
let _ = addr;
let _ = bytes;
todo!()
}
fn write_iter_read<B: IntoIterator<Item = u8>>(
&mut self,
addr: u8,
bytes: B,
buffer: &mut [u8],
) -> Result<(), Self::Error> {
let _ = addr;
let _ = bytes;
let _ = buffer;
todo!()
}
fn transaction_iter<'a, O: IntoIterator<Item = Operation<'a>>>(
&mut self,
address: u8,
operations: O,
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
todo!()
}
}
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Write for I2cDevice<'_, M, BUS>
@ -204,32 +176,4 @@ where
let _ = operations;
todo!()
}
fn write_iter<B: IntoIterator<Item = u8>>(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error> {
let _ = addr;
let _ = bytes;
todo!()
}
fn write_iter_read<B: IntoIterator<Item = u8>>(
&mut self,
addr: u8,
bytes: B,
buffer: &mut [u8],
) -> Result<(), Self::Error> {
let _ = addr;
let _ = bytes;
let _ = buffer;
todo!()
}
fn transaction_iter<'a, O: IntoIterator<Item = Operation<'a>>>(
&mut self,
address: u8,
operations: O,
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
todo!()
}
}

View File

@ -23,8 +23,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;
use embedded_hal_1::spi::SpiBusFlush;
use embedded_hal_1::spi::{self, Operation, SpiBus, SpiBusRead, SpiBusWrite};
use crate::shared_bus::SpiDeviceError;
use crate::SetConfig;
@ -50,30 +49,85 @@ where
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS>
impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS>
where
M: RawMutex,
BUS: SpiBusFlush,
BUS: SpiBusRead,
CS: OutputPin,
{
type Bus = BUS;
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> Result<R, BUS::Error>) -> Result<R, Self::Error> {
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 f_res = f(&mut bus);
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 f_res = f_res.map_err(SpiDeviceError::Spi)?;
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
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,
BUS: SpiBus,
CS: OutputPin,
{
fn transaction(&mut self, operations: &mut [Operation<'_, 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(|op| match op {
Operation::Read(buf) => bus.read(buf),
Operation::Write(buf) => bus.write(buf),
Operation::Transfer(read, write) => bus.transfer(read, write),
Operation::TransferInPlace(buf) => bus.transfer_in_place(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)
})
}
}
@ -89,11 +143,11 @@ where
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let f_res = bus.transfer(words);
let op_res = bus.transfer(words);
let cs_res = self.cs.set_high();
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
Ok(op_res)
})
}
}
@ -110,11 +164,11 @@ where
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let f_res = bus.write(words);
let op_res = bus.write(words);
let cs_res = self.cs.set_high();
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
Ok(op_res)
})
}
}
@ -146,30 +200,85 @@ where
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS>
impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS>
where
M: RawMutex,
BUS: SpiBusFlush + SetConfig,
BUS: SpiBusRead + SetConfig,
CS: OutputPin,
{
type Bus = BUS;
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> Result<R, BUS::Error>) -> Result<R, Self::Error> {
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 f_res = f(&mut bus);
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 f_res = f_res.map_err(SpiDeviceError::Spi)?;
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
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,
BUS: SpiBus + SetConfig,
CS: OutputPin,
{
fn transaction(&mut self, operations: &mut [Operation<'_, 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(|op| match op {
Operation::Read(buf) => bus.read(buf),
Operation::Write(buf) => bus.write(buf),
Operation::Transfer(read, write) => bus.transfer(read, write),
Operation::TransferInPlace(buf) => bus.transfer_in_place(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)
})
}
}

View File

@ -0,0 +1,23 @@
# Changelog
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.0 - 2023-04-27
- Replace unnecessary atomics in runqueue
- add Pender, rework Cargo features.
- add support for turbo-wakers.
- Allow TaskStorage to auto-implement `Sync`
- Use AtomicPtr for signal_ctx, removes 1 unsafe.
- Replace unsound critical sections with atomics
## 0.1.1 - 2022-11-23
- Fix features for documentation
## 0.1.0 - 2022-11-23
- First release

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-executor"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
@ -14,30 +14,42 @@ categories = [
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/"
features = ["nightly", "defmt"]
features = ["nightly", "defmt", "pender-callback"]
flavors = [
{ name = "std", target = "x86_64-unknown-linux-gnu", features = ["std"] },
{ name = "wasm", target = "wasm32-unknown-unknown", features = ["wasm"] },
{ name = "thumbv6m-none-eabi", target = "thumbv6m-none-eabi", features = [] },
{ name = "thumbv7m-none-eabi", target = "thumbv7m-none-eabi", features = [] },
{ name = "thumbv7em-none-eabi", target = "thumbv7em-none-eabi", features = [] },
{ name = "thumbv7em-none-eabihf", target = "thumbv7em-none-eabihf", features = [] },
{ name = "thumbv8m.base-none-eabi", target = "thumbv8m.base-none-eabi", features = [] },
{ name = "thumbv8m.main-none-eabi", target = "thumbv8m.main-none-eabi", features = [] },
{ name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] },
{ name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] },
{ name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] },
{ name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] },
{ name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] },
]
[package.metadata.docs.rs]
features = ["std", "nightly", "defmt"]
default-target = "thumbv7em-none-eabi"
targets = ["thumbv7em-none-eabi"]
features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-thread", "executor-interrupt"]
[features]
default = []
std = ["critical-section/std"]
wasm = ["dep:wasm-bindgen", "dep:js-sys"]
# Architecture
_arch = [] # some arch was picked
arch-std = ["_arch", "critical-section/std"]
arch-cortex-m = ["_arch", "dep:cortex-m"]
arch-xtensa = ["_arch"]
arch-riscv32 = ["_arch"]
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
# Enable creating a `Pender` from an arbitrary function pointer callback.
pender-callback = []
# Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs)
executor-thread = []
# Enable the interrupt-mode executor (available in Cortex-M only)
executor-interrupt = []
# Enable nightly-only features
nightly = []
turbowakers = []
integrated-timers = ["dep:embassy-time"]
# Trace interrupt invocations with rtos-trace.
@ -49,13 +61,15 @@ 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.1.0", path = "../embassy-macros" }
embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true}
atomic-polyfill = "1.0.1"
critical-section = "1.1"
cfg-if = "1.0.0"
static_cell = "1.0"
# WASM dependencies
# arch-cortex-m dependencies
cortex-m = { version = "0.7.6", optional = true }
# arch-wasm dependencies
wasm-bindgen = { version = "0.2.82", optional = true }
js-sys = { version = "0.3", optional = true }

View File

@ -1,59 +1,209 @@
use core::arch::asm;
use core::marker::PhantomData;
use core::ptr;
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::arch::asm;
use core::marker::PhantomData;
use super::{raw, Spawner};
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main;
/// Thread mode executor, using WFE/SEV.
///
/// This is the simplest and most common kind of executor. It runs on
/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
/// is executed, to make the `WFE` exit from sleep and poll the task.
///
/// This executor allows for ultra low power consumption for chips where `WFE`
/// triggers low-power sleep without extra steps. If your chip requires extra steps,
/// you may use [`raw::Executor`] directly to program custom behavior.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(|_| unsafe { asm!("sev") }, ptr::null_mut()),
not_send: PhantomData,
#[derive(Copy, Clone)]
pub(crate) struct ThreadPender;
impl ThreadPender {
pub(crate) fn pend(self) {
unsafe { core::arch::asm!("sev") }
}
}
/// Run the executor.
/// Thread mode executor, using WFE/SEV.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
/// This is the simplest and most common kind of executor. It runs on
/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
/// is executed, to make the `WFE` exit from sleep and poll the task.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
/// This executor allows for ultra low power consumption for chips where `WFE`
/// triggers low-power sleep without extra steps. If your chip requires extra steps,
/// you may use [`raw::Executor`] directly to program custom behavior.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe {
self.inner.poll();
asm!("wfe");
};
}
}
}
}
#[cfg(feature = "executor-interrupt")]
pub use interrupt::*;
#[cfg(feature = "executor-interrupt")]
mod interrupt {
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use atomic_polyfill::{AtomicBool, Ordering};
use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;
use crate::raw::{self, Pender, PenderInner};
#[derive(Clone, Copy)]
pub(crate) struct InterruptPender(u16);
impl InterruptPender {
pub(crate) fn pend(self) {
// STIR is faster, but is only available in v7 and higher.
#[cfg(not(armv6m))]
{
let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) };
nvic.request(self);
}
#[cfg(armv6m)]
cortex_m::peripheral::NVIC::pend(self);
}
}
unsafe impl cortex_m::interrupt::InterruptNumber for InterruptPender {
fn number(self) -> u16 {
self.0
}
}
/// Interrupt mode executor.
///
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
/// to poll tasks, and when a task is woken the interrupt is pended from software.
///
/// This allows running async tasks at a priority higher than thread mode. One
/// use case is to leave thread mode free for non-async tasks. Another use case is
/// to run multiple executors: one in thread mode for low priority tasks and another in
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
/// priority ones.
///
/// It is even possible to run multiple interrupt mode executors at different priorities,
/// by assigning different priorities to the interrupts. For an example on how to do this,
/// See the 'multiprio' example for 'embassy-nrf'.
///
/// To use it, you have to pick an interrupt that won't be used by the hardware.
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
/// If this is not the case, you may use an interrupt from any unused peripheral.
///
/// It is somewhat more complex to use, it's recommended to use the thread-mode
/// [`Executor`] instead, if it works for your use case.
pub struct InterruptExecutor {
started: AtomicBool,
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
}
unsafe impl Send for InterruptExecutor {}
unsafe impl Sync for InterruptExecutor {}
impl InterruptExecutor {
/// Create a new, not started `InterruptExecutor`.
#[inline]
pub const fn new() -> Self {
Self {
started: AtomicBool::new(false),
executor: UnsafeCell::new(MaybeUninit::uninit()),
}
}
/// Executor interrupt callback.
///
/// # Safety
///
/// You MUST call this from the interrupt handler, and from nowhere else.
pub unsafe fn on_interrupt(&'static self) {
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
executor.poll();
}
/// Start the executor.
///
/// This initializes the executor, enables the interrupt, and returns.
/// The executor keeps running in the background through the interrupt.
///
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
/// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
/// different "thread" (the interrupt), so spawning tasks on it is effectively
/// sending them.
///
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
/// a task running in it.
///
/// # Interrupt requirements
///
/// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
///
/// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
///
/// You must set the interrupt priority before calling this method. You MUST NOT
/// do it after.
///
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
if self
.started
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
panic!("InterruptExecutor::start() called multiple times on the same executor.");
}
loop {
unsafe {
self.inner.poll();
asm!("wfe");
};
(&mut *self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender(
irq.number(),
)))))
}
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
unsafe { NVIC::unmask(irq) }
executor.spawner().make_send()
}
}
}

View File

@ -1,73 +1,86 @@
use core::marker::PhantomData;
use core::ptr;
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-riscv32`.");
use atomic_polyfill::{AtomicBool, Ordering};
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
use super::{raw, Spawner};
#[cfg(feature = "nightly")]
pub use embassy_macros::main_riscv as main;
/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV
///
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
/// RISCV32 Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
#[derive(Copy, Clone)]
pub(crate) struct ThreadPender;
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
// use Signal_Work_Thread_Mode as substitute for local interrupt register
inner: raw::Executor::new(
|_| {
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
},
ptr::null_mut(),
),
not_send: PhantomData,
impl ThreadPender {
#[allow(unused)]
pub(crate) fn pend(self) {
SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst);
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
loop {
unsafe {
self.inner.poll();
// we do not care about race conditions between the load and store operations, interrupts
//will only set this value to true.
critical_section::with(|_| {
// if there is work to do, loop back to polling
// TODO can we relax this?
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
}
// if not, wait for interrupt
else {
core::arch::asm!("wfi");
}
});
// if an interrupt occurred while waiting, it will be serviced here
/// RISCV32 Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe {
self.inner.poll();
// we do not care about race conditions between the load and store operations, interrupts
//will only set this value to true.
critical_section::with(|_| {
// if there is work to do, loop back to polling
// TODO can we relax this?
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
}
// if not, wait for interrupt
else {
core::arch::asm!("wfi");
}
});
// if an interrupt occurred while waiting, it will be serviced here
}
}
}
}

View File

@ -1,84 +1,100 @@
use std::marker::PhantomData;
use std::sync::{Condvar, Mutex};
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-std`.");
use super::{raw, Spawner};
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use std::marker::PhantomData;
use std::sync::{Condvar, Mutex};
/// Single-threaded std-based executor.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
signaler: &'static Signaler,
}
#[cfg(feature = "nightly")]
pub use embassy_macros::main_std as main;
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
let signaler = &*Box::leak(Box::new(Signaler::new()));
Self {
inner: raw::Executor::new(
|p| unsafe {
let s = &*(p as *const () as *const Signaler);
s.signal()
},
signaler as *const _ as _,
),
not_send: PhantomData,
signaler,
use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
#[derive(Copy, Clone)]
pub(crate) struct ThreadPender(&'static Signaler);
impl ThreadPender {
#[allow(unused)]
pub(crate) fn pend(self) {
self.0.signal()
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe { self.inner.poll() };
self.signaler.wait()
}
/// Single-threaded std-based executor.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
signaler: &'static Signaler,
}
}
struct Signaler {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
let signaler = &*Box::leak(Box::new(Signaler::new()));
Self {
inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))),
not_send: PhantomData,
signaler,
}
}
impl Signaler {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe { self.inner.poll() };
self.signaler.wait()
}
}
}
fn wait(&self) {
let mut signaled = self.mutex.lock().unwrap();
while !*signaled {
signaled = self.condvar.wait(signaled).unwrap();
}
*signaled = false;
struct Signaler {
mutex: Mutex<bool>,
condvar: Condvar,
}
fn signal(&self) {
let mut signaled = self.mutex.lock().unwrap();
*signaled = true;
self.condvar.notify_one();
impl Signaler {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait(&self) {
let mut signaled = self.mutex.lock().unwrap();
while !*signaled {
signaled = self.condvar.wait(signaled).unwrap();
}
*signaled = false;
}
fn signal(&self) {
let mut signaled = self.mutex.lock().unwrap();
*signaled = true;
self.condvar.notify_one();
}
}
}

View File

@ -1,74 +1,88 @@
use core::marker::PhantomData;
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-wasm`.");
use js_sys::Promise;
use wasm_bindgen::prelude::*;
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use super::raw::util::UninitCell;
use super::raw::{self};
use super::Spawner;
use core::marker::PhantomData;
/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
pub struct Executor {
inner: raw::Executor,
ctx: &'static WasmContext,
not_send: PhantomData<*mut ()>,
}
#[cfg(feature = "nightly")]
pub use embassy_macros::main_wasm as main;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
pub(crate) struct WasmContext {
promise: Promise,
closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
}
use crate::raw::util::UninitCell;
use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
impl WasmContext {
pub fn new() -> Self {
Self {
promise: Promise::resolve(&JsValue::undefined()),
closure: UninitCell::uninit(),
}
/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
pub struct Executor {
inner: raw::Executor,
ctx: &'static WasmContext,
not_send: PhantomData<*mut ()>,
}
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
let ctx = &*Box::leak(Box::new(WasmContext::new()));
let inner = raw::Executor::new(
|p| unsafe {
let ctx = &*(p as *const () as *const WasmContext);
let _ = ctx.promise.then(ctx.closure.as_mut());
},
ctx as *const _ as _,
);
Self {
inner,
not_send: PhantomData,
ctx,
pub(crate) struct WasmContext {
promise: Promise,
closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
}
#[derive(Copy, Clone)]
pub(crate) struct ThreadPender(&'static WasmContext);
impl ThreadPender {
#[allow(unused)]
pub(crate) fn pend(self) {
let _ = self.0.promise.then(unsafe { self.0.closure.as_mut() });
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
unsafe {
let executor = &self.inner;
self.ctx.closure.write(Closure::new(move |_| {
executor.poll();
}));
init(self.inner.spawner());
impl WasmContext {
pub fn new() -> Self {
Self {
promise: Promise::resolve(&JsValue::undefined()),
closure: UninitCell::uninit(),
}
}
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
let ctx = &*Box::leak(Box::new(WasmContext::new()));
Self {
inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))),
not_send: PhantomData,
ctx,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
unsafe {
let executor = &self.inner;
self.ctx.closure.write(Closure::new(move |_| {
executor.poll();
}));
init(self.inner.spawner());
}
}
}
}

View File

@ -1,74 +1,84 @@
use core::marker::PhantomData;
use core::ptr;
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-xtensa`.");
use atomic_polyfill::{AtomicBool, Ordering};
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
use super::{raw, Spawner};
use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa
///
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
#[derive(Copy, Clone)]
pub(crate) struct ThreadPender;
/// Xtensa Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
// use Signal_Work_Thread_Mode as substitute for local interrupt register
inner: raw::Executor::new(
|_| {
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
},
ptr::null_mut(),
),
not_send: PhantomData,
impl ThreadPender {
#[allow(unused)]
pub(crate) fn pend(self) {
SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst);
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
loop {
unsafe {
self.inner.poll();
// we do not care about race conditions between the load and store operations, interrupts
// will only set this value to true.
// if there is work to do, loop back to polling
// TODO can we relax this?
critical_section::with(|_| {
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
} else {
// waiti sets the PS.INTLEVEL when slipping into sleep
// because critical sections in Xtensa are implemented via increasing
// PS.INTLEVEL the critical section ends here
// take care not add code after `waiti` if it needs to be inside the CS
core::arch::asm!("waiti 0"); // critical section ends here
}
});
/// Xtensa Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe {
self.inner.poll();
// we do not care about race conditions between the load and store operations, interrupts
// will only set this value to true.
// if there is work to do, loop back to polling
// TODO can we relax this?
critical_section::with(|_| {
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
} else {
// waiti sets the PS.INTLEVEL when slipping into sleep
// because critical sections in Xtensa are implemented via increasing
// PS.INTLEVEL the critical section ends here
// take care not add code after `waiti` if it needs to be inside the CS
core::arch::asm!("waiti 0"); // critical section ends here
}
});
}
}
}
}

View File

@ -1,5 +1,5 @@
#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)]
#![cfg_attr(all(feature = "nightly", target_arch = "xtensa"), feature(asm_experimental_arch))]
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
#![cfg_attr(all(feature = "nightly", feature = "arch-xtensa"), feature(asm_experimental_arch))]
#![allow(clippy::new_without_default)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
@ -10,47 +10,43 @@ pub(crate) mod fmt;
#[cfg(feature = "nightly")]
pub use embassy_macros::task;
cfg_if::cfg_if! {
if #[cfg(cortex_m)] {
#[path="arch/cortex_m.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main;
}
else if #[cfg(target_arch="riscv32")] {
#[path="arch/riscv32.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_riscv as main;
}
else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] {
#[path="arch/xtensa.rs"]
mod arch;
pub use arch::*;
}
else if #[cfg(feature="wasm")] {
#[path="arch/wasm.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_wasm as main;
}
else if #[cfg(feature="std")] {
#[path="arch/std.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_std as main;
}
macro_rules! check_at_most_one {
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
#[cfg(any($($res)*))]
compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*));
};
(@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => {
check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]);
};
($($f:literal),*$(,)?) => {
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
};
}
check_at_most_one!("arch-cortex-m", "arch-riscv32", "arch-xtensa", "arch-std", "arch-wasm",);
#[cfg(feature = "_arch")]
#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
#[cfg_attr(feature = "arch-xtensa", path = "arch/xtensa.rs")]
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
mod arch;
#[cfg(feature = "_arch")]
pub use arch::*;
pub mod raw;
mod spawner;
pub use spawner::*;
/// Implementation details for embassy macros.
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
#[doc(hidden)]
/// Implementation details for embassy macros. DO NOT USE.
pub mod export {
pub mod _export {
#[cfg(feature = "rtos-trace")]
pub use rtos_trace::trace;
pub use static_cell::StaticCell;
/// Expands the given block of code when `embassy-executor` is compiled with
/// the `rtos-trace-interrupt` feature.
@ -70,14 +66,3 @@ pub mod export {
($($tt:tt)*) => {};
}
}
pub mod raw;
mod spawner;
pub use spawner::*;
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
#[doc(hidden)]
pub mod _export {
pub use static_cell::StaticCell;
}

View File

@ -11,17 +11,17 @@ mod run_queue;
#[cfg(feature = "integrated-timers")]
mod timer_queue;
pub(crate) mod util;
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
mod waker;
use core::cell::Cell;
use core::future::Future;
use core::marker::PhantomData;
use core::mem;
use core::pin::Pin;
use core::ptr::NonNull;
use core::task::{Context, Poll};
use core::{mem, ptr};
use atomic_polyfill::{AtomicU32, Ordering};
use critical_section::CriticalSection;
#[cfg(feature = "integrated-timers")]
use embassy_time::driver::{self, AlarmHandle};
#[cfg(feature = "integrated-timers")]
@ -30,7 +30,7 @@ use embassy_time::Instant;
use rtos_trace::trace;
use self::run_queue::{RunQueue, RunQueueItem};
use self::util::UninitCell;
use self::util::{SyncUnsafeCell, UninitCell};
pub use self::waker::task_from_waker;
use super::SpawnToken;
@ -43,35 +43,49 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
/// Raw task header for use in task pointers.
///
/// This is an opaque struct, used for raw pointers to tasks, for use
/// with funtions like [`wake_task`] and [`task_from_waker`].
pub struct TaskHeader {
pub(crate) struct TaskHeader {
pub(crate) state: AtomicU32,
pub(crate) run_queue_item: RunQueueItem,
pub(crate) executor: Cell<*const Executor>, // Valid if state != 0
pub(crate) poll_fn: UninitCell<unsafe fn(NonNull<TaskHeader>)>, // Valid if STATE_SPAWNED
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
#[cfg(feature = "integrated-timers")]
pub(crate) expires_at: Cell<Instant>,
pub(crate) expires_at: SyncUnsafeCell<Instant>,
#[cfg(feature = "integrated-timers")]
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
}
impl TaskHeader {
pub(crate) const fn new() -> Self {
Self {
state: AtomicU32::new(0),
run_queue_item: RunQueueItem::new(),
executor: Cell::new(ptr::null()),
poll_fn: UninitCell::uninit(),
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
#[derive(Clone, Copy)]
pub struct TaskRef {
ptr: NonNull<TaskHeader>,
}
#[cfg(feature = "integrated-timers")]
expires_at: Cell::new(Instant::from_ticks(0)),
#[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(),
unsafe impl Send for TaskRef where &'static TaskHeader: Send {}
unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {}
impl TaskRef {
fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
Self {
ptr: NonNull::from(task).cast(),
}
}
/// Safety: The pointer must have been obtained with `Task::as_ptr`
pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self {
Self {
ptr: NonNull::new_unchecked(ptr as *mut TaskHeader),
}
}
pub(crate) fn header(self) -> &'static TaskHeader {
unsafe { self.ptr.as_ref() }
}
/// The returned pointer is valid for the entire TaskStorage.
pub(crate) fn as_ptr(self) -> *const TaskHeader {
self.ptr.as_ptr()
}
}
/// Raw storage in which a task can be spawned.
@ -101,7 +115,18 @@ impl<F: Future + 'static> TaskStorage<F> {
/// Create a new TaskStorage, in not-spawned state.
pub const fn new() -> Self {
Self {
raw: TaskHeader::new(),
raw: TaskHeader {
state: AtomicU32::new(0),
run_queue_item: RunQueueItem::new(),
executor: SyncUnsafeCell::new(None),
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
poll_fn: SyncUnsafeCell::new(None),
#[cfg(feature = "integrated-timers")]
expires_at: SyncUnsafeCell::new(Instant::from_ticks(0)),
#[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(),
},
future: UninitCell::uninit(),
}
}
@ -120,29 +145,17 @@ impl<F: Future + 'static> TaskStorage<F> {
/// Once the task has finished running, you may spawn it again. It is allowed to spawn it
/// on a different executor.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
if self.spawn_mark_used() {
return unsafe { SpawnToken::<F>::new(self.spawn_initialize(future)) };
let task = AvailableTask::claim(self);
match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<F>::new(task) }
}
None => SpawnToken::new_failed(),
}
SpawnToken::<F>::new_failed()
}
fn spawn_mark_used(&'static self) -> bool {
let state = STATE_SPAWNED | STATE_RUN_QUEUED;
self.raw
.state
.compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull<TaskHeader> {
// Initialize the task
self.raw.poll_fn.write(Self::poll);
self.future.write(future());
NonNull::new_unchecked(self as *const TaskStorage<F> as *const TaskHeader as *mut TaskHeader)
}
unsafe fn poll(p: NonNull<TaskHeader>) {
unsafe fn poll(p: TaskRef) {
let this = &*(p.as_ptr() as *const TaskStorage<F>);
let future = Pin::new_unchecked(this.future.as_mut());
@ -160,9 +173,37 @@ impl<F: Future + 'static> TaskStorage<F> {
// it's a noop for our waker.
mem::forget(waker);
}
#[doc(hidden)]
#[allow(dead_code)]
fn _assert_sync(self) {
fn assert_sync<T: Sync>(_: T) {}
assert_sync(self)
}
}
unsafe impl<F: Future + 'static> Sync for TaskStorage<F> {}
struct AvailableTask<F: Future + 'static> {
task: &'static TaskStorage<F>,
}
impl<F: Future + 'static> AvailableTask<F> {
fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
task.raw
.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
.ok()
.map(|_| Self { task })
}
fn initialize(self, future: impl FnOnce() -> F) -> TaskRef {
unsafe {
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
self.task.future.write(future());
}
TaskRef::new(self.task)
}
}
/// Raw storage that can hold up to N tasks of the same type.
///
@ -187,13 +228,14 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
/// is currently free. If none is free, a "poisoned" SpawnToken is returned,
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
for task in &self.pool {
if task.spawn_mark_used() {
return unsafe { SpawnToken::<F>::new(task.spawn_initialize(future)) };
let task = self.pool.iter().find_map(AvailableTask::claim);
match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<F>::new(task) }
}
None => SpawnToken::new_failed(),
}
SpawnToken::<F>::new_failed()
}
/// Like spawn(), but allows the task to be send-spawned if the args are Send even if
@ -235,39 +277,71 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`.
for task in &self.pool {
if task.spawn_mark_used() {
return SpawnToken::<FutFn>::new(task.spawn_initialize(future));
let task = self.pool.iter().find_map(AvailableTask::claim);
match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<FutFn>::new(task) }
}
None => SpawnToken::new_failed(),
}
SpawnToken::<FutFn>::new_failed()
}
}
/// Raw executor.
#[derive(Clone, Copy)]
pub(crate) enum PenderInner {
#[cfg(feature = "executor-thread")]
Thread(crate::arch::ThreadPender),
#[cfg(feature = "executor-interrupt")]
Interrupt(crate::arch::InterruptPender),
#[cfg(feature = "pender-callback")]
Callback { func: fn(*mut ()), context: *mut () },
}
unsafe impl Send for PenderInner {}
unsafe impl Sync for PenderInner {}
/// Platform/architecture-specific action executed when an executor has pending work.
///
/// This is the core of the Embassy executor. It is low-level, requiring manual
/// handling of wakeups and task polling. If you can, prefer using one of the
/// [higher level executors](crate::Executor).
/// When a task within an executor is woken, the `Pender` is called. This does a
/// platform/architecture-specific action to signal there is pending work in the executor.
/// When this happens, you must arrange for [`Executor::poll`] to be called.
///
/// The raw executor leaves it up to you to handle wakeups and scheduling:
///
/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks
/// that "want to run").
/// - You must supply a `signal_fn`. The executor will call it to notify you it has work
/// to do. You must arrange for `poll()` to be called as soon as possible.
///
/// `signal_fn` can be called from *any* context: any thread, any interrupt priority
/// level, etc. It may be called synchronously from any `Executor` method call as well.
/// You must deal with this correctly.
///
/// In particular, you must NOT call `poll` directly from `signal_fn`, as this violates
/// the requirement for `poll` to not be called reentrantly.
pub struct Executor {
/// You can think of it as a waker, but for the whole executor.
pub struct Pender(pub(crate) PenderInner);
impl Pender {
/// Create a `Pender` that will call an arbitrary function pointer.
///
/// # Arguments
///
/// - `func`: The function pointer to call.
/// - `context`: Opaque context pointer, that will be passed to the function pointer.
#[cfg(feature = "pender-callback")]
pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self {
Self(PenderInner::Callback {
func,
context: context.into(),
})
}
}
impl Pender {
pub(crate) fn pend(&self) {
match self.0 {
#[cfg(feature = "executor-thread")]
PenderInner::Thread(x) => x.pend(),
#[cfg(feature = "executor-interrupt")]
PenderInner::Interrupt(x) => x.pend(),
#[cfg(feature = "pender-callback")]
PenderInner::Callback { func, context } => func(context),
}
}
}
pub(crate) struct SyncExecutor {
run_queue: RunQueue,
signal_fn: fn(*mut ()),
signal_ctx: *mut (),
pender: Pender,
#[cfg(feature = "integrated-timers")]
pub(crate) timer_queue: timer_queue::TimerQueue,
@ -275,23 +349,14 @@ pub struct Executor {
alarm: AlarmHandle,
}
impl Executor {
/// Create a new executor.
///
/// When the executor has work to do, it will call `signal_fn` with
/// `signal_ctx` as argument.
///
/// See [`Executor`] docs for details on `signal_fn`.
pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self {
impl SyncExecutor {
pub(crate) fn new(pender: Pender) -> Self {
#[cfg(feature = "integrated-timers")]
let alarm = unsafe { unwrap!(driver::allocate_alarm()) };
#[cfg(feature = "integrated-timers")]
driver::set_alarm_callback(alarm, signal_fn, signal_ctx);
Self {
run_queue: RunQueue::new(),
signal_fn,
signal_ctx,
pender,
#[cfg(feature = "integrated-timers")]
timer_queue: timer_queue::TimerQueue::new(),
@ -307,59 +372,44 @@ impl Executor {
/// - `task` must be set up to run in this executor.
/// - `task` must NOT be already enqueued (in this executor or another one).
#[inline(always)]
unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull<TaskHeader>) {
unsafe fn enqueue(&self, task: TaskRef) {
#[cfg(feature = "rtos-trace")]
trace::task_ready_begin(task.as_ptr() as u32);
if self.run_queue.enqueue(cs, task) {
(self.signal_fn)(self.signal_ctx)
if self.run_queue.enqueue(task) {
self.pender.pend();
}
}
/// Spawn a task in this executor.
///
/// # Safety
///
/// `task` must be a valid pointer to an initialized but not-already-spawned task.
///
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
/// In this case, the task's Future must be Send. This is because this is effectively
/// sending the task to the executor thread.
pub(super) unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) {
task.as_ref().executor.set(self);
#[cfg(feature = "integrated-timers")]
fn alarm_callback(ctx: *mut ()) {
let this: &Self = unsafe { &*(ctx as *const Self) };
this.pender.pend();
}
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
task.header().executor.set(Some(self));
#[cfg(feature = "rtos-trace")]
trace::task_new(task.as_ptr() as u32);
critical_section::with(|cs| {
self.enqueue(cs, task);
})
self.enqueue(task);
}
/// Poll all queued tasks in this executor.
///
/// This loops over all tasks that are queued to be polled (i.e. they're
/// freshly spawned or they've been woken). Other tasks are not polled.
///
/// You must call `poll` after receiving a call to `signal_fn`. It is OK
/// to call `poll` even when not requested by `signal_fn`, but it wastes
/// energy.
///
/// # Safety
///
/// You must NOT call `poll` reentrantly on the same executor.
///
/// In particular, note that `poll` may call `signal_fn` synchronously. Therefore, you
/// must NOT directly call `poll()` from your `signal_fn`. Instead, `signal_fn` has to
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
/// no `poll()` already running.
pub unsafe fn poll(&'static self) {
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
pub(crate) unsafe fn poll(&'static self) {
#[cfg(feature = "integrated-timers")]
driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ());
#[allow(clippy::never_loop)]
loop {
#[cfg(feature = "integrated-timers")]
self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task));
self.run_queue.dequeue_all(|p| {
let task = p.as_ref();
let task = p.header();
#[cfg(feature = "integrated-timers")]
task.expires_at.set(Instant::MAX);
@ -378,7 +428,7 @@ impl Executor {
trace::task_exec_begin(p.as_ptr() as u32);
// Run the task
task.poll_fn.read()(p as _);
task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "rtos-trace")]
trace::task_exec_end();
@ -407,6 +457,84 @@ impl Executor {
#[cfg(feature = "rtos-trace")]
trace::system_idle();
}
}
/// Raw executor.
///
/// This is the core of the Embassy executor. It is low-level, requiring manual
/// handling of wakeups and task polling. If you can, prefer using one of the
/// [higher level executors](crate::Executor).
///
/// The raw executor leaves it up to you to handle wakeups and scheduling:
///
/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks
/// that "want to run").
/// - You must supply a [`Pender`]. The executor will call it to notify you it has work
/// to do. You must arrange for `poll()` to be called as soon as possible.
///
/// The [`Pender`] can be called from *any* context: any thread, any interrupt priority
/// level, etc. It may be called synchronously from any `Executor` method call as well.
/// You must deal with this correctly.
///
/// In particular, you must NOT call `poll` directly from the pender callback, as this violates
/// the requirement for `poll` to not be called reentrantly.
#[repr(transparent)]
pub struct Executor {
pub(crate) inner: SyncExecutor,
_not_sync: PhantomData<*mut ()>,
}
impl Executor {
pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self {
mem::transmute(inner)
}
/// Create a new executor.
///
/// When the executor has work to do, it will call the [`Pender`].
///
/// See [`Executor`] docs for details on `Pender`.
pub fn new(pender: Pender) -> Self {
Self {
inner: SyncExecutor::new(pender),
_not_sync: PhantomData,
}
}
/// Spawn a task in this executor.
///
/// # Safety
///
/// `task` must be a valid pointer to an initialized but not-already-spawned task.
///
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
/// In this case, the task's Future must be Send. This is because this is effectively
/// sending the task to the executor thread.
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
self.inner.spawn(task)
}
/// Poll all queued tasks in this executor.
///
/// This loops over all tasks that are queued to be polled (i.e. they're
/// freshly spawned or they've been woken). Other tasks are not polled.
///
/// You must call `poll` after receiving a call to the [`Pender`]. It is OK
/// to call `poll` even when not requested by the `Pender`, but it wastes
/// energy.
///
/// # Safety
///
/// You must NOT call `poll` reentrantly on the same executor.
///
/// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you
/// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
/// no `poll()` already running.
pub unsafe fn poll(&'static self) {
self.inner.poll()
}
/// Get a spawner that spawns tasks in this executor.
///
@ -417,30 +545,29 @@ impl Executor {
}
}
/// Wake a task by raw pointer.
/// Wake a task by `TaskRef`.
///
/// You can obtain task pointers from `Waker`s using [`task_from_waker`].
///
/// # Safety
///
/// `task` must be a valid task pointer obtained from [`task_from_waker`].
pub unsafe fn wake_task(task: NonNull<TaskHeader>) {
critical_section::with(|cs| {
let header = task.as_ref();
let state = header.state.load(Ordering::Relaxed);
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task(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) {
return;
None
} else {
// Mark it as scheduled
Some(state | STATE_RUN_QUEUED)
}
});
// Mark it as scheduled
header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed);
if res.is_ok() {
// We have just marked the task as scheduled, so enqueue it.
let executor = &*header.executor.get();
executor.enqueue(cs, task);
})
unsafe {
let executor = header.executor.get().unwrap_unchecked();
executor.enqueue(task);
}
}
}
#[cfg(feature = "integrated-timers")]
@ -450,9 +577,11 @@ struct TimerQueue;
impl embassy_time::queue::TimerQueue for TimerQueue {
fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) {
let task = waker::task_from_waker(waker);
let task = unsafe { task.as_ref() };
let expires_at = task.expires_at.get();
task.expires_at.set(expires_at.min(at));
let task = task.header();
unsafe {
let expires_at = task.expires_at.get();
task.expires_at.set(expires_at.min(at));
}
}
}

View File

@ -2,18 +2,18 @@ use core::ptr;
use core::ptr::NonNull;
use atomic_polyfill::{AtomicPtr, Ordering};
use critical_section::CriticalSection;
use super::TaskHeader;
use super::{TaskHeader, TaskRef};
use crate::raw::util::SyncUnsafeCell;
pub(crate) struct RunQueueItem {
next: AtomicPtr<TaskHeader>,
next: SyncUnsafeCell<Option<TaskRef>>,
}
impl RunQueueItem {
pub const fn new() -> Self {
Self {
next: AtomicPtr::new(ptr::null_mut()),
next: SyncUnsafeCell::new(None),
}
}
}
@ -46,29 +46,43 @@ impl RunQueue {
///
/// `item` must NOT be already enqueued in any queue.
#[inline(always)]
pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull<TaskHeader>) -> bool {
let prev = self.head.load(Ordering::Relaxed);
task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed);
self.head.store(task.as_ptr(), Ordering::Relaxed);
prev.is_null()
pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
let mut was_empty = false;
self.head
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
was_empty = prev.is_null();
unsafe {
// safety: the pointer is either null or valid
let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
// safety: there are no concurrent accesses to `next`
task.header().run_queue_item.next.set(prev);
}
Some(task.as_ptr() as *mut _)
})
.ok();
was_empty
}
/// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull<TaskHeader>)) {
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
// Atomically empty the queue.
let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
// safety: the pointer is either null or valid
let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
// Iterate the linked list of tasks that were previously in the queue.
while let Some(task) = NonNull::new(ptr) {
while let Some(task) = next {
// If the task re-enqueues itself, the `next` pointer will get overwritten.
// Therefore, first read the next pointer, and only then process the task.
let next = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed);
// safety: there are no concurrent accesses to `next`
next = unsafe { task.header().run_queue_item.next.get() };
on_task(task);
ptr = next
}
}
}

View File

@ -1,45 +1,43 @@
use core::cell::Cell;
use core::cmp::min;
use core::ptr;
use core::ptr::NonNull;
use atomic_polyfill::Ordering;
use embassy_time::Instant;
use super::{TaskHeader, STATE_TIMER_QUEUED};
use super::{TaskRef, STATE_TIMER_QUEUED};
use crate::raw::util::SyncUnsafeCell;
pub(crate) struct TimerQueueItem {
next: Cell<*mut TaskHeader>,
next: SyncUnsafeCell<Option<TaskRef>>,
}
impl TimerQueueItem {
pub const fn new() -> Self {
Self {
next: Cell::new(ptr::null_mut()),
next: SyncUnsafeCell::new(None),
}
}
}
pub(crate) struct TimerQueue {
head: Cell<*mut TaskHeader>,
head: SyncUnsafeCell<Option<TaskRef>>,
}
impl TimerQueue {
pub const fn new() -> Self {
Self {
head: Cell::new(ptr::null_mut()),
head: SyncUnsafeCell::new(None),
}
}
pub(crate) unsafe fn update(&self, p: NonNull<TaskHeader>) {
let task = p.as_ref();
pub(crate) unsafe fn update(&self, p: TaskRef) {
let task = p.header();
if task.expires_at.get() != Instant::MAX {
let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
let is_new = old_state & STATE_TIMER_QUEUED == 0;
if is_new {
task.timer_queue_item.next.set(self.head.get());
self.head.set(p.as_ptr());
self.head.set(Some(p));
}
}
}
@ -47,7 +45,7 @@ impl TimerQueue {
pub(crate) unsafe fn next_expiration(&self) -> Instant {
let mut res = Instant::MAX;
self.retain(|p| {
let task = p.as_ref();
let task = p.header();
let expires = task.expires_at.get();
res = min(res, expires);
expires != Instant::MAX
@ -55,9 +53,9 @@ impl TimerQueue {
res
}
pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull<TaskHeader>)) {
pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(TaskRef)) {
self.retain(|p| {
let task = p.as_ref();
let task = p.header();
if task.expires_at.get() <= now {
on_task(p);
false
@ -67,11 +65,10 @@ impl TimerQueue {
});
}
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull<TaskHeader>) -> bool) {
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
let mut prev = &self.head;
while !prev.get().is_null() {
let p = NonNull::new_unchecked(prev.get());
let task = &*p.as_ptr();
while let Some(p) = prev.get() {
let task = p.header();
if f(p) {
// Skip to next
prev = &task.timer_queue_item.next;

View File

@ -26,8 +26,31 @@ impl<T> UninitCell<T> {
}
}
impl<T: Copy> UninitCell<T> {
pub unsafe fn read(&self) -> T {
ptr::read(self.as_mut_ptr())
unsafe impl<T> Sync for UninitCell<T> {}
#[repr(transparent)]
pub struct SyncUnsafeCell<T> {
value: UnsafeCell<T>,
}
unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {}
impl<T> SyncUnsafeCell<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self {
value: UnsafeCell::new(value),
}
}
pub unsafe fn set(&self, value: T) {
*self.value.get() = value;
}
pub unsafe fn get(&self) -> T
where
T: Copy,
{
*self.value.get()
}
}

View File

@ -1,8 +1,7 @@
use core::mem;
use core::ptr::NonNull;
use core::task::{RawWaker, RawWakerVTable, Waker};
use super::{wake_task, TaskHeader};
use super::{wake_task, TaskHeader, TaskRef};
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
@ -11,14 +10,14 @@ unsafe fn clone(p: *const ()) -> RawWaker {
}
unsafe fn wake(p: *const ()) {
wake_task(NonNull::new_unchecked(p as *mut TaskHeader))
wake_task(TaskRef::from_ptr(p as *const TaskHeader))
}
unsafe fn drop(_: *const ()) {
// nop
}
pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker {
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE))
}
@ -33,7 +32,7 @@ pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker {
/// # Panics
///
/// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> {
pub fn task_from_waker(waker: &Waker) -> TaskRef {
// safety: OK because WakerHack has the same layout as Waker.
// This is not really guaranteed because the structs are `repr(Rust)`, it is
// indeed the case in the current implementation.
@ -43,8 +42,8 @@ pub fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> {
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
}
// safety: we never create a waker with a null data pointer.
unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) }
// safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) }
}
struct WakerHack {

View File

@ -0,0 +1,34 @@
use core::ptr::NonNull;
use core::task::Waker;
use super::{wake_task, TaskHeader, TaskRef};
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _))
}
/// Get a task pointer from a waker.
///
/// This can be used as an optimization in wait queues to store task pointers
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
/// avoid dynamic dispatch.
///
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
///
/// # Panics
///
/// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> TaskRef {
let ptr = waker.as_turbo_ptr().as_ptr();
// safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) }
}
#[inline(never)]
#[no_mangle]
fn _turbo_wake(ptr: NonNull<()>) {
// safety: our wakers are always created with `TaskRef::as_ptr`
let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) };
wake_task(task)
}

View File

@ -1,7 +1,6 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::mem;
use core::ptr::NonNull;
use core::task::Poll;
use super::raw;
@ -22,12 +21,12 @@ use super::raw;
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
pub struct SpawnToken<S> {
raw_task: Option<NonNull<raw::TaskHeader>>,
raw_task: Option<raw::TaskRef>,
phantom: PhantomData<*mut S>,
}
impl<S> SpawnToken<S> {
pub(crate) unsafe fn new(raw_task: NonNull<raw::TaskHeader>) -> Self {
pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
Self {
raw_task: Some(raw_task),
phantom: PhantomData,
@ -90,10 +89,11 @@ impl Spawner {
///
/// Panics if the current executor is not an Embassy executor.
pub async fn for_current_executor() -> Self {
poll_fn(|cx| unsafe {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = (*task.as_ptr()).executor.get();
Poll::Ready(Self::new(&*executor))
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
let executor = unsafe { raw::Executor::wrap(executor) };
Poll::Ready(Self::new(executor))
})
.await
}
@ -131,9 +131,7 @@ impl Spawner {
/// spawner to other threads, but the spawner loses the ability to spawn
/// non-Send tasks.
pub fn make_send(&self) -> SendSpawner {
SendSpawner {
executor: self.executor,
}
SendSpawner::new(&self.executor.inner)
}
}
@ -146,14 +144,11 @@ impl Spawner {
/// If you want to spawn non-Send tasks, use [Spawner].
#[derive(Copy, Clone)]
pub struct SendSpawner {
executor: &'static raw::Executor,
executor: &'static raw::SyncExecutor,
}
unsafe impl Send for SendSpawner {}
unsafe impl Sync for SendSpawner {}
impl SendSpawner {
pub(crate) fn new(executor: &'static raw::Executor) -> Self {
pub(crate) fn new(executor: &'static raw::SyncExecutor) -> Self {
Self { executor }
}
@ -166,10 +161,10 @@ impl SendSpawner {
///
/// Panics if the current executor is not an Embassy executor.
pub async fn for_current_executor() -> Self {
poll_fn(|cx| unsafe {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = (*task.as_ptr()).executor.get();
Poll::Ready(Self::new(&*executor))
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
Poll::Ready(Self::new(executor))
})
.await
}

View File

@ -24,6 +24,7 @@ pub fn yield_now() -> impl Future<Output = ()> {
YieldNowFuture { yielded: false }
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct YieldNowFuture {
yielded: bool,
}

View File

@ -0,0 +1,558 @@
use core::slice;
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
/// Atomic reusable ringbuffer
///
/// This ringbuffer implementation is designed to be stored in a `static`,
/// therefore all methods take `&self` and not `&mut self`.
///
/// It is "reusable": when created it has no backing buffer, you can give it
/// one with `init` and take it back with `deinit`, and init it again in the
/// future if needed. This is very non-idiomatic, but helps a lot when storing
/// it in a `static`.
///
/// One concurrent writer and one concurrent reader are supported, even at
/// different execution priorities (like main and irq).
pub struct RingBuffer {
pub buf: AtomicPtr<u8>,
pub len: AtomicUsize,
// start and end wrap at len*2, not at len.
// This allows distinguishing "full" and "empty".
// full is when start+len == end (modulo len*2)
// empty is when start == end
//
// This avoids having to consider the ringbuffer "full" at len-1 instead of len.
// The usual solution is adding a "full" flag, but that can't be made atomic
pub start: AtomicUsize,
pub end: AtomicUsize,
}
pub struct Reader<'a>(&'a RingBuffer);
pub struct Writer<'a>(&'a RingBuffer);
impl RingBuffer {
/// Create a new empty ringbuffer.
pub const fn new() -> Self {
Self {
buf: AtomicPtr::new(core::ptr::null_mut()),
len: AtomicUsize::new(0),
start: AtomicUsize::new(0),
end: AtomicUsize::new(0),
}
}
/// Initialize the ring buffer with a buffer.
///
/// # Safety
/// - The buffer (`buf .. buf+len`) must be valid memory until `deinit` is called.
/// - Must not be called concurrently with any other methods.
pub unsafe fn init(&self, buf: *mut u8, len: usize) {
// Ordering: it's OK to use `Relaxed` because this is not called
// concurrently with other methods.
self.buf.store(buf, Ordering::Relaxed);
self.len.store(len, Ordering::Relaxed);
self.start.store(0, Ordering::Relaxed);
self.end.store(0, Ordering::Relaxed);
}
/// Deinitialize the ringbuffer.
///
/// After calling this, the ringbuffer becomes empty, as if it was
/// just created with `new()`.
///
/// # Safety
/// - Must not be called concurrently with any other methods.
pub unsafe fn deinit(&self) {
// Ordering: it's OK to use `Relaxed` because this is not called
// concurrently with other methods.
self.len.store(0, Ordering::Relaxed);
self.start.store(0, Ordering::Relaxed);
self.end.store(0, Ordering::Relaxed);
}
/// Create a reader.
///
/// # Safety
///
/// Only one reader can exist at a time.
pub unsafe fn reader(&self) -> Reader<'_> {
Reader(self)
}
/// Create a writer.
///
/// # Safety
///
/// Only one writer can exist at a time.
pub unsafe fn writer(&self) -> Writer<'_> {
Writer(self)
}
pub fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn is_full(&self) -> bool {
let len = self.len.load(Ordering::Relaxed);
let start = self.start.load(Ordering::Relaxed);
let end = self.end.load(Ordering::Relaxed);
self.wrap(start + len) == end
}
pub fn is_empty(&self) -> bool {
let start = self.start.load(Ordering::Relaxed);
let end = self.end.load(Ordering::Relaxed);
start == end
}
fn wrap(&self, mut n: usize) -> usize {
let len = self.len.load(Ordering::Relaxed);
if n >= len * 2 {
n -= len * 2
}
n
}
}
impl<'a> Writer<'a> {
/// Push data into the buffer in-place.
///
/// The closure `f` is called with a free part of the buffer, it must write
/// some data to it and return the amount of bytes written.
pub fn push(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize {
let (p, n) = self.push_buf();
let buf = unsafe { slice::from_raw_parts_mut(p, n) };
let n = f(buf);
self.push_done(n);
n
}
/// Push one data byte.
///
/// Returns true if pushed succesfully.
pub fn push_one(&mut self, val: u8) -> bool {
let n = self.push(|f| match f {
[] => 0,
[x, ..] => {
*x = val;
1
}
});
n != 0
}
/// Get a buffer where data can be pushed to.
///
/// Equivalent to [`Self::push_buf`] but returns a slice.
pub fn push_slice(&mut self) -> &mut [u8] {
let (data, len) = self.push_buf();
unsafe { slice::from_raw_parts_mut(data, len) }
}
/// Get up to two buffers where data can be pushed to.
///
/// Equivalent to [`Self::push_bufs`] but returns slices.
pub fn push_slices(&mut self) -> [&mut [u8]; 2] {
let [(d0, l0), (d1, l1)] = self.push_bufs();
unsafe { [slice::from_raw_parts_mut(d0, l0), slice::from_raw_parts_mut(d1, l1)] }
}
/// Get a buffer where data can be pushed to.
///
/// Write data to the start of the buffer, then call `push_done` with
/// however many bytes you've pushed.
///
/// The buffer is suitable to DMA to.
///
/// If the ringbuf is full, size=0 will be returned.
///
/// The buffer stays valid as long as no other `Writer` method is called
/// and `init`/`deinit` aren't called on the ringbuf.
pub fn push_buf(&mut self) -> (*mut u8, usize) {
// Ordering: popping writes `start` last, so we read `start` first.
// Read it with Acquire ordering, so that the next accesses can't be reordered up past it.
let mut start = self.0.start.load(Ordering::Acquire);
let buf = self.0.buf.load(Ordering::Relaxed);
let len = self.0.len.load(Ordering::Relaxed);
let mut end = self.0.end.load(Ordering::Relaxed);
let empty = start == end;
if start >= len {
start -= len
}
if end >= len {
end -= len
}
if start == end && !empty {
// full
return (buf, 0);
}
let n = if start > end { start - end } else { len - end };
trace!(" ringbuf: push_buf {:?}..{:?}", end, end + n);
(unsafe { buf.add(end) }, n)
}
/// Get up to two buffers where data can be pushed to.
///
/// Write data starting at the beginning of the first buffer, then call
/// `push_done` with however many bytes you've pushed.
///
/// The buffers are suitable to DMA to.
///
/// If the ringbuf is full, both buffers will be zero length.
/// If there is only area available, the second buffer will be zero length.
///
/// The buffer stays valid as long as no other `Writer` method is called
/// and `init`/`deinit` aren't called on the ringbuf.
pub fn push_bufs(&mut self) -> [(*mut u8, usize); 2] {
// Ordering: as per push_buf()
let mut start = self.0.start.load(Ordering::Acquire);
let buf = self.0.buf.load(Ordering::Relaxed);
let len = self.0.len.load(Ordering::Relaxed);
let mut end = self.0.end.load(Ordering::Relaxed);
let empty = start == end;
if start >= len {
start -= len
}
if end >= len {
end -= len
}
if start == end && !empty {
// full
return [(buf, 0), (buf, 0)];
}
let n0 = if start > end { start - end } else { len - end };
let n1 = if start <= end { start } else { 0 };
trace!(" ringbuf: push_bufs [{:?}..{:?}, {:?}..{:?}]", end, end + n0, 0, n1);
[(unsafe { buf.add(end) }, n0), (buf, n1)]
}
pub fn push_done(&mut self, n: usize) {
trace!(" ringbuf: push {:?}", n);
let end = self.0.end.load(Ordering::Relaxed);
// Ordering: write `end` last, with Release ordering.
// The ordering ensures no preceding memory accesses (such as writing
// the actual data in the buffer) can be reordered down past it, which
// will guarantee the reader sees them after reading from `end`.
self.0.end.store(self.0.wrap(end + n), Ordering::Release);
}
}
impl<'a> Reader<'a> {
/// Pop data from the buffer in-place.
///
/// The closure `f` is called with the next data, it must process
/// some data from it and return the amount of bytes processed.
pub fn pop(&mut self, f: impl FnOnce(&[u8]) -> usize) -> usize {
let (p, n) = self.pop_buf();
let buf = unsafe { slice::from_raw_parts(p, n) };
let n = f(buf);
self.pop_done(n);
n
}
/// Pop one data byte.
///
/// Returns true if popped succesfully.
pub fn pop_one(&mut self) -> Option<u8> {
let mut res = None;
self.pop(|f| match f {
&[] => 0,
&[x, ..] => {
res = Some(x);
1
}
});
res
}
/// Get a buffer where data can be popped from.
///
/// Equivalent to [`Self::pop_buf`] but returns a slice.
pub fn pop_slice(&mut self) -> &mut [u8] {
let (data, len) = self.pop_buf();
unsafe { slice::from_raw_parts_mut(data, len) }
}
/// Get a buffer where data can be popped from.
///
/// Read data from the start of the buffer, then call `pop_done` with
/// however many bytes you've processed.
///
/// The buffer is suitable to DMA from.
///
/// If the ringbuf is empty, size=0 will be returned.
///
/// The buffer stays valid as long as no other `Reader` method is called
/// and `init`/`deinit` aren't called on the ringbuf.
pub fn pop_buf(&mut self) -> (*mut u8, usize) {
// Ordering: pushing writes `end` last, so we read `end` first.
// Read it with Acquire ordering, so that the next accesses can't be reordered up past it.
// This is needed to guarantee we "see" the data written by the writer.
let mut end = self.0.end.load(Ordering::Acquire);
let buf = self.0.buf.load(Ordering::Relaxed);
let len = self.0.len.load(Ordering::Relaxed);
let mut start = self.0.start.load(Ordering::Relaxed);
if start == end {
return (buf, 0);
}
if start >= len {
start -= len
}
if end >= len {
end -= len
}
let n = if end > start { end - start } else { len - start };
trace!(" ringbuf: pop_buf {:?}..{:?}", start, start + n);
(unsafe { buf.add(start) }, n)
}
pub fn pop_done(&mut self, n: usize) {
trace!(" ringbuf: pop {:?}", n);
let start = self.0.start.load(Ordering::Relaxed);
// Ordering: write `start` last, with Release ordering.
// The ordering ensures no preceding memory accesses (such as reading
// the actual data) can be reordered down past it. This is necessary
// because writing to `start` is effectively freeing the read part of the
// buffer, which "gives permission" to the writer to write to it again.
// Therefore, all buffer accesses must be completed before this.
self.0.start.store(self.0.wrap(start + n), Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_pop() {
let mut b = [0; 4];
let rb = RingBuffer::new();
unsafe {
rb.init(b.as_mut_ptr(), 4);
assert_eq!(rb.is_empty(), true);
assert_eq!(rb.is_full(), false);
rb.writer().push(|buf| {
assert_eq!(4, buf.len());
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
buf[3] = 4;
4
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), true);
rb.writer().push(|buf| {
// If it's full, we can push 0 bytes.
assert_eq!(0, buf.len());
0
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), true);
rb.reader().pop(|buf| {
assert_eq!(4, buf.len());
assert_eq!(1, buf[0]);
1
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), false);
rb.reader().pop(|buf| {
assert_eq!(3, buf.len());
0
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), false);
rb.reader().pop(|buf| {
assert_eq!(3, buf.len());
assert_eq!(2, buf[0]);
assert_eq!(3, buf[1]);
2
});
rb.reader().pop(|buf| {
assert_eq!(1, buf.len());
assert_eq!(4, buf[0]);
1
});
assert_eq!(rb.is_empty(), true);
assert_eq!(rb.is_full(), false);
rb.reader().pop(|buf| {
assert_eq!(0, buf.len());
0
});
rb.writer().push(|buf| {
assert_eq!(4, buf.len());
buf[0] = 10;
1
});
rb.writer().push(|buf| {
assert_eq!(3, buf.len());
buf[0] = 11;
buf[1] = 12;
2
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), false);
rb.writer().push(|buf| {
assert_eq!(1, buf.len());
buf[0] = 13;
1
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), true);
}
}
#[test]
fn zero_len() {
let rb = RingBuffer::new();
unsafe {
assert_eq!(rb.is_empty(), true);
assert_eq!(rb.is_full(), true);
rb.writer().push(|buf| {
assert_eq!(0, buf.len());
0
});
rb.reader().pop(|buf| {
assert_eq!(0, buf.len());
0
});
}
}
#[test]
fn push_slices() {
init();
let mut b = [0; 4];
let rb = RingBuffer::new();
unsafe {
rb.init(b.as_mut_ptr(), 4);
/* push 3 -> [1 2 3 x] */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(4, ps[0].len());
assert_eq!(0, ps[1].len());
ps[0][0] = 1;
ps[0][1] = 2;
ps[0][2] = 3;
w.push_done(3);
drop(w);
/* pop 2 -> [x x 3 x] */
rb.reader().pop(|buf| {
assert_eq!(3, buf.len());
assert_eq!(1, buf[0]);
assert_eq!(2, buf[1]);
assert_eq!(3, buf[2]);
2
});
/* push 3 -> [5 6 3 4] */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(1, ps[0].len());
assert_eq!(2, ps[1].len());
ps[0][0] = 4;
ps[1][0] = 5;
ps[1][1] = 6;
w.push_done(3);
drop(w);
/* buf is now full */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(0, ps[0].len());
assert_eq!(0, ps[1].len());
/* pop 2 -> [5 6 x x] */
rb.reader().pop(|buf| {
assert_eq!(2, buf.len());
assert_eq!(3, buf[0]);
assert_eq!(4, buf[1]);
2
});
/* should now have one push slice again */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(2, ps[0].len());
assert_eq!(0, ps[1].len());
drop(w);
/* pop 2 -> [x x x x] */
rb.reader().pop(|buf| {
assert_eq!(2, buf.len());
assert_eq!(5, buf[0]);
assert_eq!(6, buf[1]);
2
});
/* should now have two push slices */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(2, ps[0].len());
assert_eq!(2, ps[1].len());
drop(w);
/* make sure we exercise all wrap around cases properly */
for _ in 0..10 {
/* should be empty, push 1 */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(4, ps[0].len() + ps[1].len());
w.push_done(1);
drop(w);
/* should have 1 element */
let mut w = rb.writer();
let ps = w.push_slices();
assert_eq!(3, ps[0].len() + ps[1].len());
drop(w);
/* pop 1 */
rb.reader().pop(|buf| {
assert_eq!(1, buf.len());
1
});
}
}
}
}

View File

@ -1,6 +1,7 @@
use core::mem;
use core::mem::MaybeUninit;
#[must_use = "to delay the drop handler invokation to the end of the scope"]
pub struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
@ -27,6 +28,7 @@ impl<F: FnOnce()> Drop for OnDrop<F> {
///
/// To correctly dispose of this device, call the [defuse](struct.DropBomb.html#method.defuse)
/// method before this object is dropped.
#[must_use = "to delay the drop bomb invokation to the end of the scope"]
pub struct DropBomb {
_private: (),
}

View File

@ -4,6 +4,7 @@
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub mod atomic_ring_buffer;
pub mod drop;
mod macros;
mod peripheral;

View File

@ -1,10 +1,12 @@
#[macro_export]
macro_rules! peripherals {
macro_rules! peripherals_definition {
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
/// Types for the peripheral singletons.
pub mod peripherals {
$(
$(#[$cfg])?
#[allow(non_camel_case_types)]
#[doc = concat!(stringify!($name), " peripheral")]
pub struct $name { _private: () }
$(#[$cfg])?
@ -24,10 +26,19 @@ macro_rules! peripherals {
$crate::impl_peripheral!($name);
)*
}
};
}
#[macro_export]
macro_rules! peripherals_struct {
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
/// Struct containing all the peripheral singletons.
///
/// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`].
#[allow(non_snake_case)]
pub struct Peripherals {
$(
#[doc = concat!(stringify!($name), " peripheral")]
$(#[$cfg])?
pub $name: peripherals::$name,
)*
@ -70,6 +81,24 @@ macro_rules! peripherals {
};
}
#[macro_export]
macro_rules! peripherals {
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
$crate::peripherals_definition!(
$(
$(#[$cfg])?
$name,
)*
);
$crate::peripherals_struct!(
$(
$(#[$cfg])?
$name,
)*
);
};
}
#[macro_export]
macro_rules! into_ref {
($($name:ident),*) => {
@ -86,7 +115,7 @@ macro_rules! impl_peripheral {
type P = $type;
#[inline]
unsafe fn clone_unchecked(&mut self) -> Self::P {
unsafe fn clone_unchecked(&self) -> Self::P {
$type { ..*self }
}
}

View File

@ -3,16 +3,17 @@ use core::ops::{Deref, DerefMut};
/// An exclusive reference to a peripheral.
///
/// This is functionally the same as a `&'a mut T`. The reason for having a
/// dedicated struct is memory efficiency:
/// This is functionally the same as a `&'a mut T`. There's a few advantages in having
/// a dedicated struct instead:
///
/// Peripheral singletons are typically either zero-sized (for concrete peripherals
/// like `PA9` or `Spi4`) or very small (for example `AnyPin` which is 1 byte).
/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized.
/// PeripheralRef stores a copy of `T` instead, so it's the same size.
///
/// but it is the size of `T` not the size
/// of a pointer. This is useful if T is a zero sized type.
/// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete
/// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte).
/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized.
/// PeripheralRef stores a copy of `T` instead, so it's the same size.
/// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`,
/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic
/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes
/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization.
pub struct PeripheralRef<'a, T> {
inner: T,
_lifetime: PhantomData<&'a mut T>,
@ -38,7 +39,7 @@ impl<'a, T> PeripheralRef<'a, T> {
/// You should strongly prefer using `reborrow()` instead. It returns a
/// `PeripheralRef` that borrows `self`, which allows the borrow checker
/// to enforce this at compile time.
pub unsafe fn clone_unchecked(&mut self) -> PeripheralRef<'a, T>
pub unsafe fn clone_unchecked(&self) -> PeripheralRef<'a, T>
where
T: Peripheral<P = T>,
{
@ -145,14 +146,14 @@ pub trait Peripheral: Sized {
///
/// You should strongly prefer using `into_ref()` instead. It returns a
/// `PeripheralRef`, which allows the borrow checker to enforce this at compile time.
unsafe fn clone_unchecked(&mut self) -> Self::P;
unsafe fn clone_unchecked(&self) -> Self::P;
/// Convert a value into a `PeripheralRef`.
///
/// When called on an owned `T`, yields a `PeripheralRef<'static, T>`.
/// When called on an `&'a mut T`, yields a `PeripheralRef<'a, T>`.
#[inline]
fn into_ref<'a>(mut self) -> PeripheralRef<'a, Self::P>
fn into_ref<'a>(self) -> PeripheralRef<'a, Self::P>
where
Self: 'a,
{
@ -160,14 +161,14 @@ pub trait Peripheral: Sized {
}
}
impl<'b, T: DerefMut> Peripheral for T
impl<'b, T: Deref> Peripheral for T
where
T::Target: Peripheral,
{
type P = <T::Target as Peripheral>::P;
#[inline]
unsafe fn clone_unchecked(&mut self) -> Self::P {
self.deref_mut().clone_unchecked()
unsafe fn clone_unchecked(&self) -> Self::P {
self.deref().clone_unchecked()
}
}

View File

@ -7,21 +7,13 @@ license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
features = ["time", "defmt"]
flavors = [
{ name = "sx126x", target = "thumbv7em-none-eabihf", features = ["sx126x"] },
{ name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] },
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] },
]
[lib]
features = ["stm32wl", "time", "defmt"]
target = "thumbv7em-none-eabi"
[features]
sx126x = []
sx127x = []
stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
stm32wl = ["dep:embassy-stm32"]
time = []
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
defmt = ["dep:defmt", "lorawan-device/defmt"]
[dependencies]
@ -29,14 +21,14 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.1.0", path = "../embassy-time" }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
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.9" }
embedded-hal-async = { version = "=0.1.0-alpha.3" }
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 = { version = "0.2", features = ["unproven"] }
bit_field = { version = "0.10" }
lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
lorawan = { version = "0.7.1", default-features = false }
lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }

325
embassy-lora/src/iv.rs Normal file
View File

@ -0,0 +1,325 @@
#[cfg(feature = "stm32wl")]
use embassy_stm32::interrupt::*;
#[cfg(feature = "stm32wl")]
use embassy_stm32::{pac, PeripheralRef};
#[cfg(feature = "stm32wl")]
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
#[cfg(feature = "stm32wl")]
use embassy_sync::signal::Signal;
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::delay::DelayUs;
use embedded_hal_async::digital::Wait;
use lora_phy::mod_params::RadioError::*;
use lora_phy::mod_params::{BoardType, RadioError};
use lora_phy::mod_traits::InterfaceVariant;
#[cfg(feature = "stm32wl")]
static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
#[cfg(feature = "stm32wl")]
/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination
pub struct Stm32wlInterfaceVariant<'a, CTRL> {
board_type: BoardType,
irq: PeripheralRef<'a, SUBGHZ_RADIO>,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
}
#[cfg(feature = "stm32wl")]
impl<'a, CTRL> Stm32wlInterfaceVariant<'a, CTRL>
where
CTRL: OutputPin,
{
/// Create an InterfaceVariant instance for an stm32wl/sx1262 combination
pub fn new(
irq: PeripheralRef<'a, SUBGHZ_RADIO>,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
) -> Result<Self, RadioError> {
irq.disable();
irq.set_handler(Self::on_interrupt);
Ok(Self {
board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board
irq,
rf_switch_rx,
rf_switch_tx,
})
}
fn on_interrupt(_: *mut ()) {
unsafe { SUBGHZ_RADIO::steal() }.disable();
IRQ_SIGNAL.signal(());
}
}
#[cfg(feature = "stm32wl")]
impl<CTRL> InterfaceVariant for Stm32wlInterfaceVariant<'_, CTRL>
where
CTRL: OutputPin,
{
fn set_board_type(&mut self, board_type: BoardType) {
self.board_type = board_type;
}
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
let pwr = pac::PWR;
unsafe {
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW));
}
Ok(())
}
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
let pwr = pac::PWR;
unsafe {
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH));
}
Ok(())
}
async fn reset(&mut self, _delay: &mut impl DelayUs) -> Result<(), RadioError> {
let rcc = pac::RCC;
unsafe {
rcc.csr().modify(|w| w.set_rfrst(true));
rcc.csr().modify(|w| w.set_rfrst(false));
}
Ok(())
}
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
let pwr = pac::PWR;
while unsafe { pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY } {}
Ok(())
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
self.irq.enable();
IRQ_SIGNAL.wait().await;
Ok(())
}
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
None => (),
};
match &mut self.rf_switch_rx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
None => Ok(()),
}
}
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
}
/// Base for the InterfaceVariant implementation for an stm32l0/sx1276 combination
pub struct Stm32l0InterfaceVariant<CTRL, WAIT> {
board_type: BoardType,
nss: CTRL,
reset: CTRL,
irq: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
}
impl<CTRL, WAIT> Stm32l0InterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
/// Create an InterfaceVariant instance for an stm32l0/sx1276 combination
pub fn new(
nss: CTRL,
reset: CTRL,
irq: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
) -> Result<Self, RadioError> {
Ok(Self {
board_type: BoardType::Stm32l0Sx1276, // updated when associated with a specific LoRa board
nss,
reset,
irq,
rf_switch_rx,
rf_switch_tx,
})
}
}
impl<CTRL, WAIT> InterfaceVariant for Stm32l0InterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
fn set_board_type(&mut self, board_type: BoardType) {
self.board_type = board_type;
}
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
self.nss.set_low().map_err(|_| NSS)
}
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
self.nss.set_high().map_err(|_| NSS)
}
async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
delay.delay_ms(10).await;
self.reset.set_low().map_err(|_| Reset)?;
delay.delay_ms(10).await;
self.reset.set_high().map_err(|_| Reset)?;
delay.delay_ms(10).await;
Ok(())
}
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
Ok(())
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
self.irq.wait_for_high().await.map_err(|_| Irq)
}
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
None => (),
};
match &mut self.rf_switch_rx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
None => Ok(()),
}
}
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
}
/// Base for the InterfaceVariant implementation for a generic Sx126x LoRa board
pub struct GenericSx126xInterfaceVariant<CTRL, WAIT> {
board_type: BoardType,
nss: CTRL,
reset: CTRL,
dio1: WAIT,
busy: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
}
impl<CTRL, WAIT> GenericSx126xInterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
/// Create an InterfaceVariant instance for an nrf52840/sx1262 combination
pub fn new(
nss: CTRL,
reset: CTRL,
dio1: WAIT,
busy: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
) -> Result<Self, RadioError> {
Ok(Self {
board_type: BoardType::Rak4631Sx1262, // updated when associated with a specific LoRa board
nss,
reset,
dio1,
busy,
rf_switch_rx,
rf_switch_tx,
})
}
}
impl<CTRL, WAIT> InterfaceVariant for GenericSx126xInterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
fn set_board_type(&mut self, board_type: BoardType) {
self.board_type = board_type;
}
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
self.nss.set_low().map_err(|_| NSS)
}
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
self.nss.set_high().map_err(|_| NSS)
}
async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
delay.delay_ms(10).await;
self.reset.set_low().map_err(|_| Reset)?;
delay.delay_ms(20).await;
self.reset.set_high().map_err(|_| Reset)?;
delay.delay_ms(10).await;
Ok(())
}
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
self.busy.wait_for_low().await.map_err(|_| Busy)
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
if self.board_type != BoardType::RpPicoWaveshareSx1262 {
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
} else {
self.dio1.wait_for_rising_edge().await.map_err(|_| DIO1)?;
}
Ok(())
}
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
None => (),
};
match &mut self.rf_switch_rx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
None => Ok(()),
}
}
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
}

View File

@ -1,16 +1,12 @@
#![no_std]
#![feature(type_alias_impl_trait)]
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
//! crate's async LoRaWAN MAC implementation.
#![feature(async_fn_in_trait, impl_trait_projections)]
#![allow(incomplete_features)]
//! embassy-lora holds LoRa-specific functionality.
pub(crate) mod fmt;
#[cfg(feature = "stm32wl")]
pub mod stm32wl;
#[cfg(feature = "sx126x")]
pub mod sx126x;
#[cfg(feature = "sx127x")]
pub mod sx127x;
/// interface variants required by the external lora physical layer crate (lora-phy)
pub mod iv;
#[cfg(feature = "time")]
use embassy_time::{Duration, Instant, Timer};
@ -34,13 +30,11 @@ impl lorawan_device::async_device::radio::Timer for LoraTimer {
self.start = Instant::now();
}
type AtFuture<'m> = impl core::future::Future<Output = ()> + 'm;
fn at<'m>(&'m mut self, millis: u64) -> Self::AtFuture<'m> {
Timer::at(self.start + Duration::from_millis(millis))
async fn at(&mut self, millis: u64) {
Timer::at(self.start + Duration::from_millis(millis)).await
}
type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
Timer::after(Duration::from_millis(millis))
async fn delay_ms(&mut self, millis: u64) {
Timer::after(Duration::from_millis(millis)).await
}
}

View File

@ -1,292 +0,0 @@
//! A radio driver integration for the radio found on STM32WL family devices.
use core::future::{poll_fn, Future};
use core::task::Poll;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_stm32::dma::NoDma;
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
use embassy_stm32::subghz::{
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
};
use embassy_sync::waitqueue::AtomicWaker;
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
use lorawan_device::async_device::Timings;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
Idle,
Txing,
Rxing,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RadioError;
static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
/// The radio peripheral keeping the radio state and owning the radio IRQ.
pub struct SubGhzRadio<'d, RS> {
radio: SubGhz<'d, NoDma, NoDma>,
switch: RS,
irq: PeripheralRef<'d, SUBGHZ_RADIO>,
}
#[derive(Default)]
#[non_exhaustive]
pub struct SubGhzRadioConfig {
pub reg_mode: RegMode,
pub calibrate_image: CalibrateImage,
pub pa_config: PaConfig,
pub tx_params: TxParams,
}
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
/// Create a new instance of a SubGhz radio for LoRaWAN.
pub fn new(
mut radio: SubGhz<'d, NoDma, NoDma>,
switch: RS,
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
config: SubGhzRadioConfig,
) -> Result<Self, RadioError> {
into_ref!(irq);
radio.reset();
irq.disable();
irq.set_handler(|_| {
IRQ_WAKER.wake();
unsafe { SUBGHZ_RADIO::steal().disable() };
});
configure_radio(&mut radio, config)?;
Ok(Self { radio, switch, irq })
}
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
/// the upcoming RX window start.
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
trace!("TX request: {:?}", config);
self.switch.set_tx();
self.radio
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
self.set_lora_mod_params(config.rf)?;
let packet_params = LoRaPacketParams::new()
.set_preamble_len(8)
.set_header_type(HeaderType::Variable)
.set_payload_len(buf.len() as u8)
.set_crc_en(true)
.set_invert_iq(false);
self.radio.set_lora_packet_params(&packet_params)?;
let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
self.radio.set_irq_cfg(&irq_cfg)?;
self.radio.set_buffer_base_address(0, 0)?;
self.radio.write_buffer(0, buf)?;
// The maximum airtime for any LoRaWAN package is 2793.5ms.
// The value of 4000ms is copied from C driver and gives us a good safety margin.
self.radio.set_tx(Timeout::from_millis_sat(4000))?;
trace!("TX started");
loop {
let (_status, irq_status) = self.irq_wait().await;
if irq_status & Irq::TxDone.mask() != 0 {
trace!("TX done");
return Ok(0);
}
if irq_status & Irq::Timeout.mask() != 0 {
return Err(RadioError);
}
}
}
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
let mod_params = LoRaModParams::new()
.set_sf(convert_spreading_factor(&config.spreading_factor))
.set_bw(convert_bandwidth(&config.bandwidth))
.set_cr(CodingRate::Cr45)
.set_ldro_en(matches!(
(config.spreading_factor, config.bandwidth),
(SpreadingFactor::_12, Bandwidth::_125KHz)
| (SpreadingFactor::_12, Bandwidth::_250KHz)
| (SpreadingFactor::_11, Bandwidth::_125KHz)
));
self.radio.set_lora_mod_params(&mod_params)
}
/// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
/// be able to hold a single LoRaWAN packet.
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
assert!(buf.len() >= 255);
trace!("RX request: {:?}", config);
self.switch.set_rx();
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
self.set_lora_mod_params(config)?;
let packet_params = LoRaPacketParams::new()
.set_preamble_len(8)
.set_header_type(HeaderType::Variable)
.set_payload_len(0xFF)
.set_crc_en(false)
.set_invert_iq(true);
self.radio.set_lora_packet_params(&packet_params)?;
let irq_cfg = CfgIrq::new()
.irq_enable_all(Irq::RxDone)
.irq_enable_all(Irq::PreambleDetected)
.irq_enable_all(Irq::HeaderValid)
.irq_enable_all(Irq::HeaderErr)
.irq_enable_all(Irq::Err)
.irq_enable_all(Irq::Timeout);
self.radio.set_irq_cfg(&irq_cfg)?;
self.radio.set_buffer_base_address(0, 0)?;
// NOTE: Upper layer handles timeout by cancelling the future
self.radio.set_rx(Timeout::DISABLED)?;
trace!("RX started");
loop {
let (_status, irq_status) = self.irq_wait().await;
if irq_status & Irq::RxDone.mask() != 0 {
let (_status, len, ptr) = self.radio.rx_buffer_status()?;
let packet_status = self.radio.lora_packet_status()?;
let rssi = packet_status.rssi_pkt().to_integer();
let snr = packet_status.snr_pkt().to_integer();
self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
self.radio.set_standby(StandbyClk::Rc)?;
#[cfg(feature = "defmt")]
trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
#[cfg(feature = "log")]
trace!("RX done: {:02x?}", &mut buf[..len as usize]);
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
}
if irq_status & Irq::Timeout.mask() != 0 {
return Err(RadioError);
}
}
}
async fn irq_wait(&mut self) -> (Status, u16) {
poll_fn(|cx| {
self.irq.unpend();
self.irq.enable();
IRQ_WAKER.register(cx.waker());
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
self.radio
.clear_irq_status(irq_status)
.expect("error clearing irq status");
trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
if irq_status == 0 {
Poll::Pending
} else {
Poll::Ready((status, irq_status))
}
})
.await
}
}
fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
trace!("Configuring STM32WL SUBGHZ radio");
radio.set_regulator_mode(config.reg_mode)?;
radio.set_standby(StandbyClk::Rc)?;
let tcxo_mode = TcxoMode::new()
.set_txco_trim(TcxoTrim::Volts1pt7)
.set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(100)));
radio.set_tcxo_mode(&tcxo_mode)?;
// Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
// The STM32CUBE C driver also does this.
radio.set_hse_in_trim(HseTrim::MIN)?;
// Re-calibrate everything after setting the TXCO config.
radio.calibrate(0x7F)?;
radio.calibrate_image(config.calibrate_image)?;
radio.set_pa_config(&config.pa_config)?;
radio.set_tx_params(&config.tx_params)?;
radio.set_pa_ocp(Ocp::Max140m)?;
radio.set_packet_type(PacketType::LoRa)?;
radio.set_lora_sync_word(LoRaSyncWord::Public)?;
trace!("Done initializing STM32WL SUBGHZ radio");
Ok(())
}
impl<'d, RS: RadioSwitch> PhyRxTx for SubGhzRadio<'d, RS> {
type PhyError = RadioError;
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm where Self: 'm;
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
async move { self.do_tx(config, buf).await }
}
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm where Self: 'm;
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
async move { self.do_rx(config, buf).await }
}
}
impl From<embassy_stm32::spi::Error> for RadioError {
fn from(_: embassy_stm32::spi::Error) -> Self {
RadioError
}
}
impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
fn get_rx_window_offset_ms(&self) -> i32 {
-500
}
fn get_rx_window_duration_ms(&self) -> u32 {
3000
}
}
pub trait RadioSwitch {
fn set_rx(&mut self);
fn set_tx(&mut self);
}
fn convert_spreading_factor(sf: &SpreadingFactor) -> SF {
match sf {
SpreadingFactor::_7 => SF::Sf7,
SpreadingFactor::_8 => SF::Sf8,
SpreadingFactor::_9 => SF::Sf9,
SpreadingFactor::_10 => SF::Sf10,
SpreadingFactor::_11 => SF::Sf11,
SpreadingFactor::_12 => SF::Sf12,
}
}
fn convert_bandwidth(bw: &Bandwidth) -> LoRaBandwidth {
match bw {
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
Bandwidth::_500KHz => LoRaBandwidth::Bw500,
}
}

View File

@ -1,153 +0,0 @@
use core::future::Future;
use defmt::Format;
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::*;
use lorawan_device::async_device::radio::{PhyRxTx, RfConfig, RxQuality, TxConfig};
use lorawan_device::async_device::Timings;
mod sx126x_lora;
use sx126x_lora::LoRa;
use self::sx126x_lora::mod_params::RadioError;
/// Semtech Sx126x LoRa peripheral
pub struct Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
pub lora: LoRa<SPI, CTRL, WAIT>,
}
impl<SPI, CTRL, WAIT, BUS> Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
pub async fn new(
spi: SPI,
cs: CTRL,
reset: CTRL,
antenna_rx: CTRL,
antenna_tx: CTRL,
dio1: WAIT,
busy: WAIT,
enable_public_network: bool,
) -> Result<Self, RadioError<BUS>> {
let mut lora = LoRa::new(spi, cs, reset, antenna_rx, antenna_tx, dio1, busy);
lora.init().await?;
lora.set_lora_modem(enable_public_network).await?;
Ok(Self { lora })
}
}
impl<SPI, CTRL, WAIT, BUS> Timings for Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
fn get_rx_window_offset_ms(&self) -> i32 {
-500
}
fn get_rx_window_duration_ms(&self) -> u32 {
800
}
}
impl<SPI, CTRL, WAIT, BUS> PhyRxTx for Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
type PhyError = RadioError<BUS>;
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
where
SPI: 'm,
CTRL: 'm,
WAIT: 'm,
BUS: 'm;
fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> {
trace!("TX START");
async move {
self.lora
.set_tx_config(
config.pw,
config.rf.spreading_factor.into(),
config.rf.bandwidth.into(),
config.rf.coding_rate.into(),
8,
false,
true,
false,
0,
false,
)
.await?;
self.lora.set_max_payload_length(buffer.len() as u8).await?;
self.lora.set_channel(config.rf.frequency).await?;
self.lora.send(buffer, 0xffffff).await?;
self.lora.process_irq(None, None, None).await?;
trace!("TX DONE");
return Ok(0);
}
}
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
where
SPI: 'm,
CTRL: 'm,
WAIT: 'm,
BUS: 'm;
fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> {
trace!("RX START");
async move {
self.lora
.set_rx_config(
config.spreading_factor.into(),
config.bandwidth.into(),
config.coding_rate.into(),
8,
4,
false,
0u8,
true,
false,
0,
true,
true,
)
.await?;
self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?;
self.lora.set_channel(config.frequency).await?;
self.lora.rx(90 * 1000).await?;
let mut received_len = 0u8;
self.lora
.process_irq(Some(receiving_buffer), Some(&mut received_len), None)
.await?;
trace!("RX DONE");
let packet_status = self.lora.get_latest_packet_status();
let mut rssi = 0i16;
let mut snr = 0i8;
if packet_status.is_some() {
rssi = packet_status.unwrap().rssi as i16;
snr = packet_status.unwrap().snr;
}
Ok((received_len as usize, RxQuality::new(rssi, snr)))
}
}
}

View File

@ -1,256 +0,0 @@
use embassy_time::{Duration, Timer};
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiBus;
use super::mod_params::RadioError::*;
use super::mod_params::*;
use super::LoRa;
// Defines the time required for the TCXO to wakeup [ms].
const BRD_TCXO_WAKEUP_TIME: u32 = 10;
// Provides board-specific functionality for Semtech SX126x-based boards.
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
where
SPI: SpiBus<u8, Error = BUS>,
CTRL: OutputPin,
WAIT: Wait,
{
// De-initialize the radio I/Os pins interface. Useful when going into MCU low power modes.
pub(super) async fn brd_io_deinit(&mut self) -> Result<(), RadioError<BUS>> {
Ok(()) // no operation currently
}
// Initialize the TCXO power pin
pub(super) async fn brd_io_tcxo_init(&mut self) -> Result<(), RadioError<BUS>> {
let timeout = self.brd_get_board_tcxo_wakeup_time() << 6;
self.sub_set_dio3_as_tcxo_ctrl(TcxoCtrlVoltage::Ctrl1V7, timeout)
.await?;
Ok(())
}
// Initialize RF switch control pins
pub(super) async fn brd_io_rf_switch_init(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_dio2_as_rf_switch_ctrl(true).await?;
Ok(())
}
// Initialize the radio debug pins
pub(super) async fn brd_io_dbg_init(&mut self) -> Result<(), RadioError<BUS>> {
Ok(()) // no operation currently
}
// Hardware reset of the radio
pub(super) async fn brd_reset(&mut self) -> Result<(), RadioError<BUS>> {
Timer::after(Duration::from_millis(10)).await;
self.reset.set_low().map_err(|_| Reset)?;
Timer::after(Duration::from_millis(20)).await;
self.reset.set_high().map_err(|_| Reset)?;
Timer::after(Duration::from_millis(10)).await;
Ok(())
}
// Wait while the busy pin is high
pub(super) async fn brd_wait_on_busy(&mut self) -> Result<(), RadioError<BUS>> {
self.busy.wait_for_low().await.map_err(|_| Busy)?;
Ok(())
}
// Wake up the radio
pub(super) async fn brd_wakeup(&mut self) -> Result<(), RadioError<BUS>> {
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::GetStatus.value()]).await.map_err(SPI)?;
self.spi.write(&[0x00]).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
self.brd_set_operating_mode(RadioMode::StandbyRC);
Ok(())
}
// Send a command that writes data to the radio
pub(super) async fn brd_write_command(&mut self, op_code: OpCode, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
self.spi.write(buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
if op_code != OpCode::SetSleep {
self.brd_wait_on_busy().await?;
}
Ok(())
}
// Send a command that reads data from the radio, filling the provided buffer and returning a status
pub(super) async fn brd_read_command(&mut self, op_code: OpCode, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
let mut status = [0u8];
let mut input = [0u8];
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
self.spi.transfer(&mut status, &[0x00]).await.map_err(SPI)?;
for i in 0..buffer.len() {
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
buffer[i] = input[0];
}
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(status[0])
}
// Write one or more bytes of data to the radio memory
pub(super) async fn brd_write_registers(
&mut self,
start_register: Register,
buffer: &[u8],
) -> Result<(), RadioError<BUS>> {
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::WriteRegister.value()]).await.map_err(SPI)?;
self.spi
.write(&[
((start_register.addr() & 0xFF00) >> 8) as u8,
(start_register.addr() & 0x00FF) as u8,
])
.await
.map_err(SPI)?;
self.spi.write(buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Read one or more bytes of data from the radio memory
pub(super) async fn brd_read_registers(
&mut self,
start_register: Register,
buffer: &mut [u8],
) -> Result<(), RadioError<BUS>> {
let mut input = [0u8];
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::ReadRegister.value()]).await.map_err(SPI)?;
self.spi
.write(&[
((start_register.addr() & 0xFF00) >> 8) as u8,
(start_register.addr() & 0x00FF) as u8,
0x00u8,
])
.await
.map_err(SPI)?;
for i in 0..buffer.len() {
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
buffer[i] = input[0];
}
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Write data to the buffer holding the payload in the radio
pub(super) async fn brd_write_buffer(&mut self, offset: u8, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::WriteBuffer.value()]).await.map_err(SPI)?;
self.spi.write(&[offset]).await.map_err(SPI)?;
self.spi.write(buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Read data from the buffer holding the payload in the radio
pub(super) async fn brd_read_buffer(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), RadioError<BUS>> {
let mut input = [0u8];
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::ReadBuffer.value()]).await.map_err(SPI)?;
self.spi.write(&[offset]).await.map_err(SPI)?;
self.spi.write(&[0x00]).await.map_err(SPI)?;
for i in 0..buffer.len() {
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
buffer[i] = input[0];
}
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Set the radio output power
pub(super) async fn brd_set_rf_tx_power(&mut self, power: i8) -> Result<(), RadioError<BUS>> {
self.sub_set_tx_params(power, RampTime::Ramp40Us).await?;
Ok(())
}
// Get the radio type
pub(super) fn brd_get_radio_type(&mut self) -> RadioType {
RadioType::SX1262
}
// Quiesce the antenna(s).
pub(super) fn brd_ant_sleep(&mut self) -> Result<(), RadioError<BUS>> {
self.antenna_tx.set_low().map_err(|_| AntTx)?;
self.antenna_rx.set_low().map_err(|_| AntRx)?;
Ok(())
}
// Prepare the antenna(s) for a receive operation
pub(super) fn brd_ant_set_rx(&mut self) -> Result<(), RadioError<BUS>> {
self.antenna_tx.set_low().map_err(|_| AntTx)?;
self.antenna_rx.set_high().map_err(|_| AntRx)?;
Ok(())
}
// Prepare the antenna(s) for a send operation
pub(super) fn brd_ant_set_tx(&mut self) -> Result<(), RadioError<BUS>> {
self.antenna_rx.set_low().map_err(|_| AntRx)?;
self.antenna_tx.set_high().map_err(|_| AntTx)?;
Ok(())
}
// Check if the given RF frequency is supported by the hardware
pub(super) async fn brd_check_rf_frequency(&mut self, _frequency: u32) -> Result<bool, RadioError<BUS>> {
Ok(true)
}
// Get the duration required for the TCXO to wakeup [ms].
pub(super) fn brd_get_board_tcxo_wakeup_time(&mut self) -> u32 {
BRD_TCXO_WAKEUP_TIME
}
/* Get current state of the DIO1 pin - not currently needed if waiting on DIO1 instead of using an IRQ process
pub(super) async fn brd_get_dio1_pin_state(
&mut self,
) -> Result<u32, RadioError<BUS>> {
Ok(0)
}
*/
// Get the current radio operatiing mode
pub(super) fn brd_get_operating_mode(&mut self) -> RadioMode {
self.operating_mode
}
// Set/Update the current radio operating mode This function is only required to reflect the current radio operating mode when processing interrupts.
pub(super) fn brd_set_operating_mode(&mut self, mode: RadioMode) {
self.operating_mode = mode;
}
}

View File

@ -1,732 +0,0 @@
#![allow(dead_code)]
use embassy_time::{Duration, Timer};
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiBus;
mod board_specific;
pub mod mod_params;
mod subroutine;
use mod_params::RadioError::*;
use mod_params::*;
// Syncwords for public and private networks
const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444;
const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424;
// Maximum number of registers that can be added to the retention list
const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4;
// Possible LoRa bandwidths
const LORA_BANDWIDTHS: [Bandwidth; 3] = [Bandwidth::_125KHz, Bandwidth::_250KHz, Bandwidth::_500KHz];
// Radio complete wakeup time with margin for temperature compensation [ms]
const RADIO_WAKEUP_TIME: u32 = 3;
/// Provides high-level access to Semtech SX126x-based boards
pub struct LoRa<SPI, CTRL, WAIT> {
spi: SPI,
cs: CTRL,
reset: CTRL,
antenna_rx: CTRL,
antenna_tx: CTRL,
dio1: WAIT,
busy: WAIT,
operating_mode: RadioMode,
rx_continuous: bool,
max_payload_length: u8,
modulation_params: Option<ModulationParams>,
packet_type: PacketType,
packet_params: Option<PacketParams>,
packet_status: Option<PacketStatus>,
image_calibrated: bool,
frequency_error: u32,
}
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
where
SPI: SpiBus<u8, Error = BUS>,
CTRL: OutputPin,
WAIT: Wait,
{
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time ()
pub fn new(spi: SPI, cs: CTRL, reset: CTRL, antenna_rx: CTRL, antenna_tx: CTRL, dio1: WAIT, busy: WAIT) -> Self {
Self {
spi,
cs,
reset,
antenna_rx,
antenna_tx,
dio1,
busy,
operating_mode: RadioMode::Sleep,
rx_continuous: false,
max_payload_length: 0xFFu8,
modulation_params: None,
packet_type: PacketType::LoRa,
packet_params: None,
packet_status: None,
image_calibrated: false,
frequency_error: 0u32, // where is volatile FrequencyError modified ???
}
}
/// Initialize the radio
pub async fn init(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_init().await?;
self.sub_set_standby(StandbyMode::RC).await?;
self.sub_set_regulator_mode(RegulatorMode::UseDCDC).await?;
self.sub_set_buffer_base_address(0x00u8, 0x00u8).await?;
self.sub_set_tx_params(0i8, RampTime::Ramp200Us).await?;
self.sub_set_dio_irq_params(
IrqMask::All.value(),
IrqMask::All.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
self.add_register_to_retention_list(Register::RxGain.addr()).await?;
self.add_register_to_retention_list(Register::TxModulation.addr())
.await?;
Ok(())
}
/// Return current radio state
pub fn get_status(&mut self) -> RadioState {
match self.brd_get_operating_mode() {
RadioMode::Transmit => RadioState::TxRunning,
RadioMode::Receive => RadioState::RxRunning,
RadioMode::ChannelActivityDetection => RadioState::ChannelActivityDetecting,
_ => RadioState::Idle,
}
}
/// Configure the radio for LoRa (FSK support should be provided in a separate driver, if desired)
pub async fn set_lora_modem(&mut self, enable_public_network: bool) -> Result<(), RadioError<BUS>> {
self.sub_set_packet_type(PacketType::LoRa).await?;
if enable_public_network {
self.brd_write_registers(
Register::LoRaSyncword,
&[
((LORA_MAC_PUBLIC_SYNCWORD >> 8) & 0xFF) as u8,
(LORA_MAC_PUBLIC_SYNCWORD & 0xFF) as u8,
],
)
.await?;
} else {
self.brd_write_registers(
Register::LoRaSyncword,
&[
((LORA_MAC_PRIVATE_SYNCWORD >> 8) & 0xFF) as u8,
(LORA_MAC_PRIVATE_SYNCWORD & 0xFF) as u8,
],
)
.await?;
}
Ok(())
}
/// Sets the channel frequency
pub async fn set_channel(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_rf_frequency(frequency).await?;
Ok(())
}
/* Checks if the channel is free for the given time. This is currently not implemented until a substitute
for switching to the FSK modem is found.
pub async fn is_channel_free(&mut self, frequency: u32, rxBandwidth: u32, rssiThresh: i16, maxCarrierSenseTime: u32) -> bool;
*/
/// Generate a 32 bit random value based on the RSSI readings, after disabling all interrupts. Ensure set_lora_modem() is called befrorehand.
/// After calling this function either set_rx_config() or set_tx_config() must be called.
pub async fn get_random_value(&mut self) -> Result<u32, RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::None.value(),
IrqMask::None.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
let result = self.sub_get_random().await?;
Ok(result)
}
/// Set the reception parameters for the LoRa modem (only). Ensure set_lora_modem() is called befrorehand.
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
/// preamble_length length in symbols (the hardware adds 4 more symbols)
/// symb_timeout RxSingle timeout value in symbols
/// fixed_len fixed length packets [0: variable, 1: fixed]
/// payload_len payload length when fixed length is used
/// crc_on [0: OFF, 1: ON]
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
/// hop_period number of symbols between each hop
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
/// rx_continuous reception mode [false: single mode, true: continuous mode]
pub async fn set_rx_config(
&mut self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
symb_timeout: u16,
fixed_len: bool,
payload_len: u8,
crc_on: bool,
_freq_hop_on: bool,
_hop_period: u8,
iq_inverted: bool,
rx_continuous: bool,
) -> Result<(), RadioError<BUS>> {
let mut symb_timeout_final = symb_timeout;
self.rx_continuous = rx_continuous;
if self.rx_continuous {
symb_timeout_final = 0;
}
if fixed_len {
self.max_payload_length = payload_len;
} else {
self.max_payload_length = 0xFFu8;
}
self.sub_set_stop_rx_timer_on_preamble_detect(false).await?;
let mut low_data_rate_optimize = 0x00u8;
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
&& (bandwidth == Bandwidth::_125KHz))
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
{
low_data_rate_optimize = 0x01u8;
}
let modulation_params = ModulationParams {
spreading_factor: spreading_factor,
bandwidth: bandwidth,
coding_rate: coding_rate,
low_data_rate_optimize: low_data_rate_optimize,
};
let mut preamble_length_final = preamble_length;
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length_final = 12;
}
let packet_params = PacketParams {
preamble_length: preamble_length_final,
implicit_header: fixed_len,
payload_length: self.max_payload_length,
crc_on: crc_on,
iq_inverted: iq_inverted,
};
self.modulation_params = Some(modulation_params);
self.packet_params = Some(packet_params);
self.standby().await?;
self.sub_set_modulation_params().await?;
self.sub_set_packet_params().await?;
self.sub_set_lora_symb_num_timeout(symb_timeout_final).await?;
// Optimize the Inverted IQ Operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
let mut iq_polarity = [0x00u8];
self.brd_read_registers(Register::IQPolarity, &mut iq_polarity).await?;
if iq_inverted {
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] & (!(1 << 2))])
.await?;
} else {
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] | (1 << 2)])
.await?;
}
Ok(())
}
/// Set the transmission parameters for the LoRa modem (only).
/// power output power [dBm]
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
/// preamble_length length in symbols (the hardware adds 4 more symbols)
/// fixed_len fixed length packets [0: variable, 1: fixed]
/// crc_on [0: OFF, 1: ON]
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
/// hop_period number of symbols between each hop
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
pub async fn set_tx_config(
&mut self,
power: i8,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
fixed_len: bool,
crc_on: bool,
_freq_hop_on: bool,
_hop_period: u8,
iq_inverted: bool,
) -> Result<(), RadioError<BUS>> {
let mut low_data_rate_optimize = 0x00u8;
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
&& (bandwidth == Bandwidth::_125KHz))
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
{
low_data_rate_optimize = 0x01u8;
}
let modulation_params = ModulationParams {
spreading_factor: spreading_factor,
bandwidth: bandwidth,
coding_rate: coding_rate,
low_data_rate_optimize: low_data_rate_optimize,
};
let mut preamble_length_final = preamble_length;
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length_final = 12;
}
let packet_params = PacketParams {
preamble_length: preamble_length_final,
implicit_header: fixed_len,
payload_length: self.max_payload_length,
crc_on: crc_on,
iq_inverted: iq_inverted,
};
self.modulation_params = Some(modulation_params);
self.packet_params = Some(packet_params);
self.standby().await?;
self.sub_set_modulation_params().await?;
self.sub_set_packet_params().await?;
// Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
let mut tx_modulation = [0x00u8];
self.brd_read_registers(Register::TxModulation, &mut tx_modulation)
.await?;
if bandwidth == Bandwidth::_500KHz {
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] & (!(1 << 2))])
.await?;
} else {
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] | (1 << 2)])
.await?;
}
self.brd_set_rf_tx_power(power).await?;
Ok(())
}
/// Check if the given RF frequency is supported by the hardware [true: supported, false: unsupported]
pub async fn check_rf_frequency(&mut self, frequency: u32) -> Result<bool, RadioError<BUS>> {
Ok(self.brd_check_rf_frequency(frequency).await?)
}
/// Computes the packet time on air in ms for the given payload for a LoRa modem (can only be called once set_rx_config or set_tx_config have been called)
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
/// preamble_length length in symbols (the hardware adds 4 more symbols)
/// fixed_len fixed length packets [0: variable, 1: fixed]
/// payload_len sets payload length when fixed length is used
/// crc_on [0: OFF, 1: ON]
pub fn get_time_on_air(
&mut self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
fixed_len: bool,
payload_len: u8,
crc_on: bool,
) -> Result<u32, RadioError<BUS>> {
let numerator = 1000
* Self::get_lora_time_on_air_numerator(
spreading_factor,
bandwidth,
coding_rate,
preamble_length,
fixed_len,
payload_len,
crc_on,
);
let denominator = bandwidth.value_in_hz();
if denominator == 0 {
Err(RadioError::InvalidBandwidth)
} else {
Ok((numerator + denominator - 1) / denominator)
}
}
/// Send the buffer of the given size. Prepares the packet to be sent and sets the radio in transmission [timeout in ms]
pub async fn send(&mut self, buffer: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
if self.packet_params.is_some() {
self.sub_set_dio_irq_params(
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
let mut packet_params = self.packet_params.as_mut().unwrap();
packet_params.payload_length = buffer.len() as u8;
self.sub_set_packet_params().await?;
self.sub_send_payload(buffer, timeout).await?;
Ok(())
} else {
Err(RadioError::PacketParamsMissing)
}
}
/// Set the radio in sleep mode
pub async fn sleep(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_sleep(SleepParams {
wakeup_rtc: false,
reset: false,
warm_start: true,
})
.await?;
Timer::after(Duration::from_millis(2)).await;
Ok(())
}
/// Set the radio in standby mode
pub async fn standby(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_standby(StandbyMode::RC).await?;
Ok(())
}
/// Set the radio in reception mode for the given duration [0: continuous, others: timeout (ms)]
pub async fn rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::All.value(),
IrqMask::All.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
if self.rx_continuous {
self.sub_set_rx(0xFFFFFF).await?;
} else {
self.sub_set_rx(timeout << 6).await?;
}
Ok(())
}
/// Start a Channel Activity Detection
pub async fn start_cad(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
self.sub_set_cad().await?;
Ok(())
}
/// Sets the radio in continuous wave transmission mode
/// frequency channel RF frequency
/// power output power [dBm]
/// timeout transmission mode timeout [s]
pub async fn set_tx_continuous_wave(
&mut self,
frequency: u32,
power: i8,
_timeout: u16,
) -> Result<(), RadioError<BUS>> {
self.sub_set_rf_frequency(frequency).await?;
self.brd_set_rf_tx_power(power).await?;
self.sub_set_tx_continuous_wave().await?;
Ok(())
}
/// Read the current RSSI value for the LoRa modem (only) [dBm]
pub async fn get_rssi(&mut self) -> Result<i16, RadioError<BUS>> {
let value = self.sub_get_rssi_inst().await?;
Ok(value as i16)
}
/// Write one or more radio registers with a buffer of a given size, starting at the first register address
pub async fn write_registers_from_buffer(
&mut self,
start_register: Register,
buffer: &[u8],
) -> Result<(), RadioError<BUS>> {
self.brd_write_registers(start_register, buffer).await?;
Ok(())
}
/// Read one or more radio registers into a buffer of a given size, starting at the first register address
pub async fn read_registers_into_buffer(
&mut self,
start_register: Register,
buffer: &mut [u8],
) -> Result<(), RadioError<BUS>> {
self.brd_read_registers(start_register, buffer).await?;
Ok(())
}
/// Set the maximum payload length (in bytes) for a LoRa modem (only).
pub async fn set_max_payload_length(&mut self, max: u8) -> Result<(), RadioError<BUS>> {
if self.packet_params.is_some() {
let packet_params = self.packet_params.as_mut().unwrap();
self.max_payload_length = max;
packet_params.payload_length = max;
self.sub_set_packet_params().await?;
Ok(())
} else {
Err(RadioError::PacketParamsMissing)
}
}
/// Get the time required for the board plus radio to get out of sleep [ms]
pub fn get_wakeup_time(&mut self) -> u32 {
self.brd_get_board_tcxo_wakeup_time() + RADIO_WAKEUP_TIME
}
/// Process the radio irq
pub async fn process_irq(
&mut self,
receiving_buffer: Option<&mut [u8]>,
received_len: Option<&mut u8>,
cad_activity_detected: Option<&mut bool>,
) -> Result<(), RadioError<BUS>> {
loop {
trace!("process_irq loop entered");
let de = self.sub_get_device_errors().await?;
trace!("device_errors: rc_64khz_calibration = {}, rc_13mhz_calibration = {}, pll_calibration = {}, adc_calibration = {}, image_calibration = {}, xosc_start = {}, pll_lock = {}, pa_ramp = {}",
de.rc_64khz_calibration, de.rc_13mhz_calibration, de.pll_calibration, de.adc_calibration, de.image_calibration, de.xosc_start, de.pll_lock, de.pa_ramp);
let st = self.sub_get_status().await?;
trace!(
"radio status: cmd_status: {:x}, chip_mode: {:x}",
st.cmd_status,
st.chip_mode
);
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
let operating_mode = self.brd_get_operating_mode();
let irq_flags = self.sub_get_irq_status().await?;
self.sub_clear_irq_status(irq_flags).await?;
trace!("process_irq DIO1 satisfied: irq_flags = {:x}", irq_flags);
// check for errors and unexpected interrupt masks (based on operation mode)
if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() {
if !self.rx_continuous {
self.brd_set_operating_mode(RadioMode::StandbyRC);
}
return Err(RadioError::HeaderError);
} else if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() {
if operating_mode == RadioMode::Receive {
if !self.rx_continuous {
self.brd_set_operating_mode(RadioMode::StandbyRC);
}
return Err(RadioError::CRCErrorOnReceive);
} else {
return Err(RadioError::CRCErrorUnexpected);
}
} else if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
if operating_mode == RadioMode::Transmit {
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Err(RadioError::TransmitTimeout);
} else if operating_mode == RadioMode::Receive {
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Err(RadioError::ReceiveTimeout);
} else {
return Err(RadioError::TimeoutUnexpected);
}
} else if ((irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value())
&& (operating_mode != RadioMode::Transmit)
{
return Err(RadioError::TransmitDoneUnexpected);
} else if ((irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value())
&& (operating_mode != RadioMode::Receive)
{
return Err(RadioError::ReceiveDoneUnexpected);
} else if (((irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value())
|| ((irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value()))
&& (operating_mode != RadioMode::ChannelActivityDetection)
{
return Err(RadioError::CADUnexpected);
}
if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() {
trace!("HeaderValid");
} else if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() {
trace!("PreambleDetected");
} else if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() {
trace!("SyncwordValid");
}
// handle completions
if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Ok(());
} else if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
if !self.rx_continuous {
self.brd_set_operating_mode(RadioMode::StandbyRC);
// implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3)
self.brd_write_registers(Register::RTCCtrl, &[0x00]).await?;
let mut evt_clr = [0x00u8];
self.brd_read_registers(Register::EvtClr, &mut evt_clr).await?;
evt_clr[0] |= 1 << 1;
self.brd_write_registers(Register::EvtClr, &evt_clr).await?;
}
if receiving_buffer.is_some() && received_len.is_some() {
*(received_len.unwrap()) = self.sub_get_payload(receiving_buffer.unwrap()).await?;
}
self.packet_status = self.sub_get_packet_status().await?.into();
return Ok(());
} else if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
if cad_activity_detected.is_some() {
*(cad_activity_detected.unwrap()) =
(irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
}
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Ok(());
}
// if DIO1 was driven high for reasons other than an error or operation completion (currently, PreambleDetected, SyncwordValid, and HeaderValid
// are in that category), loop to wait again
}
}
// SX126x-specific functions
/// Set the radio in reception mode with Max LNA gain for the given time (SX126x radios only) [0: continuous, others timeout in ms]
pub async fn set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::All.value(),
IrqMask::All.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
if self.rx_continuous {
self.sub_set_rx_boosted(0xFFFFFF).await?; // Rx continuous
} else {
self.sub_set_rx_boosted(timeout << 6).await?;
}
Ok(())
}
/// Set the Rx duty cycle management parameters (SX126x radios only)
/// rx_time structure describing reception timeout value
/// sleep_time structure describing sleep timeout value
pub async fn set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_rx_duty_cycle(rx_time, sleep_time).await?;
Ok(())
}
pub fn get_latest_packet_status(&mut self) -> Option<PacketStatus> {
self.packet_status
}
// Utilities
async fn add_register_to_retention_list(&mut self, register_address: u16) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize];
// Read the address and registers already added to the list
self.brd_read_registers(Register::RetentionList, &mut buffer).await?;
let number_of_registers = buffer[0];
for i in 0..number_of_registers {
if register_address
== ((buffer[(1 + (2 * i)) as usize] as u16) << 8) | (buffer[(2 + (2 * i)) as usize] as u16)
{
return Ok(()); // register already in list
}
}
if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION {
buffer[0] += 1; // increment number of registers
buffer[(1 + (2 * number_of_registers)) as usize] = ((register_address >> 8) & 0xFF) as u8;
buffer[(2 + (2 * number_of_registers)) as usize] = (register_address & 0xFF) as u8;
self.brd_write_registers(Register::RetentionList, &buffer).await?;
Ok(())
} else {
Err(RadioError::RetentionListExceeded)
}
}
fn get_lora_time_on_air_numerator(
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
fixed_len: bool,
payload_len: u8,
crc_on: bool,
) -> u32 {
let cell_denominator;
let cr_denominator = (coding_rate.value() as i32) + 4;
// Ensure that the preamble length is at least 12 symbols when using SF5 or SF6
let mut preamble_length_final = preamble_length;
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length_final = 12;
}
let mut low_data_rate_optimize = false;
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
&& (bandwidth == Bandwidth::_125KHz))
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
{
low_data_rate_optimize = true;
}
let mut cell_numerator = ((payload_len as i32) << 3) + (if crc_on { 16 } else { 0 })
- (4 * spreading_factor.value() as i32)
+ (if fixed_len { 0 } else { 20 });
if spreading_factor.value() <= 6 {
cell_denominator = 4 * (spreading_factor.value() as i32);
} else {
cell_numerator += 8;
if low_data_rate_optimize {
cell_denominator = 4 * ((spreading_factor.value() as i32) - 2);
} else {
cell_denominator = 4 * (spreading_factor.value() as i32);
}
}
if cell_numerator < 0 {
cell_numerator = 0;
}
let mut intermediate: i32 = (((cell_numerator + cell_denominator - 1) / cell_denominator) * cr_denominator)
+ (preamble_length_final as i32)
+ 12;
if spreading_factor.value() <= 6 {
intermediate = intermediate + 2;
}
(((4 * intermediate) + 1) * (1 << (spreading_factor.value() - 2))) as u32
}
}

View File

@ -1,469 +0,0 @@
use core::fmt::Debug;
use lorawan_device::async_device::radio as device;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RadioError<BUS> {
SPI(BUS),
CS,
Reset,
AntRx,
AntTx,
Busy,
DIO1,
PayloadSizeMismatch(usize, usize),
RetentionListExceeded,
InvalidBandwidth,
ModulationParamsMissing,
PacketParamsMissing,
HeaderError,
CRCErrorUnexpected,
CRCErrorOnReceive,
TransmitTimeout,
ReceiveTimeout,
TimeoutUnexpected,
TransmitDoneUnexpected,
ReceiveDoneUnexpected,
CADUnexpected,
}
pub struct RadioSystemError {
pub rc_64khz_calibration: bool,
pub rc_13mhz_calibration: bool,
pub pll_calibration: bool,
pub adc_calibration: bool,
pub image_calibration: bool,
pub xosc_start: bool,
pub pll_lock: bool,
pub pa_ramp: bool,
}
#[derive(Clone, Copy, PartialEq)]
pub enum PacketType {
GFSK = 0x00,
LoRa = 0x01,
None = 0x0F,
}
impl PacketType {
pub const fn value(self) -> u8 {
self as u8
}
pub fn to_enum(value: u8) -> Self {
if value == 0x00 {
PacketType::GFSK
} else if value == 0x01 {
PacketType::LoRa
} else {
PacketType::None
}
}
}
#[derive(Clone, Copy)]
pub struct PacketStatus {
pub rssi: i8,
pub snr: i8,
pub signal_rssi: i8,
pub freq_error: u32,
}
#[derive(Clone, Copy, PartialEq)]
pub enum RadioType {
SX1261,
SX1262,
}
#[derive(Clone, Copy, PartialEq)]
pub enum RadioMode {
Sleep = 0x00, // sleep mode
StandbyRC = 0x01, // standby mode with RC oscillator
StandbyXOSC = 0x02, // standby mode with XOSC oscillator
FrequencySynthesis = 0x03, // frequency synthesis mode
Transmit = 0x04, // transmit mode
Receive = 0x05, // receive mode
ReceiveDutyCycle = 0x06, // receive duty cycle mode
ChannelActivityDetection = 0x07, // channel activity detection mode
}
impl RadioMode {
/// Returns the value of the mode.
pub const fn value(self) -> u8 {
self as u8
}
pub fn to_enum(value: u8) -> Self {
if value == 0x00 {
RadioMode::Sleep
} else if value == 0x01 {
RadioMode::StandbyRC
} else if value == 0x02 {
RadioMode::StandbyXOSC
} else if value == 0x03 {
RadioMode::FrequencySynthesis
} else if value == 0x04 {
RadioMode::Transmit
} else if value == 0x05 {
RadioMode::Receive
} else if value == 0x06 {
RadioMode::ReceiveDutyCycle
} else if value == 0x07 {
RadioMode::ChannelActivityDetection
} else {
RadioMode::Sleep
}
}
}
pub enum RadioState {
Idle = 0x00,
RxRunning = 0x01,
TxRunning = 0x02,
ChannelActivityDetecting = 0x03,
}
impl RadioState {
/// Returns the value of the state.
pub fn value(self) -> u8 {
self as u8
}
}
pub struct RadioStatus {
pub cmd_status: u8,
pub chip_mode: u8,
}
impl RadioStatus {
pub fn value(self) -> u8 {
(self.chip_mode << 4) | (self.cmd_status << 1)
}
}
#[derive(Clone, Copy)]
pub enum IrqMask {
None = 0x0000,
TxDone = 0x0001,
RxDone = 0x0002,
PreambleDetected = 0x0004,
SyncwordValid = 0x0008,
HeaderValid = 0x0010,
HeaderError = 0x0020,
CRCError = 0x0040,
CADDone = 0x0080,
CADActivityDetected = 0x0100,
RxTxTimeout = 0x0200,
All = 0xFFFF,
}
impl IrqMask {
pub fn value(self) -> u16 {
self as u16
}
}
#[derive(Clone, Copy)]
pub enum Register {
PacketParams = 0x0704, // packet configuration
PayloadLength = 0x0702, // payload size
SynchTimeout = 0x0706, // recalculated number of symbols
Syncword = 0x06C0, // Syncword values
LoRaSyncword = 0x0740, // LoRa Syncword value
GeneratedRandomNumber = 0x0819, //32-bit generated random number
AnaLNA = 0x08E2, // disable the LNA
AnaMixer = 0x08E5, // disable the mixer
RxGain = 0x08AC, // RX gain (0x94: power saving, 0x96: rx boosted)
XTATrim = 0x0911, // device internal trimming capacitor
OCP = 0x08E7, // over current protection max value
RetentionList = 0x029F, // retention list
IQPolarity = 0x0736, // optimize the inverted IQ operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
TxModulation = 0x0889, // modulation quality with 500 kHz LoRa Bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
TxClampCfg = 0x08D8, // better resistance to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
RTCCtrl = 0x0902, // RTC control
EvtClr = 0x0944, // event clear
}
impl Register {
pub fn addr(self) -> u16 {
self as u16
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum OpCode {
GetStatus = 0xC0,
WriteRegister = 0x0D,
ReadRegister = 0x1D,
WriteBuffer = 0x0E,
ReadBuffer = 0x1E,
SetSleep = 0x84,
SetStandby = 0x80,
SetFS = 0xC1,
SetTx = 0x83,
SetRx = 0x82,
SetRxDutyCycle = 0x94,
SetCAD = 0xC5,
SetTxContinuousWave = 0xD1,
SetTxContinuousPremable = 0xD2,
SetPacketType = 0x8A,
GetPacketType = 0x11,
SetRFFrequency = 0x86,
SetTxParams = 0x8E,
SetPAConfig = 0x95,
SetCADParams = 0x88,
SetBufferBaseAddress = 0x8F,
SetModulationParams = 0x8B,
SetPacketParams = 0x8C,
GetRxBufferStatus = 0x13,
GetPacketStatus = 0x14,
GetRSSIInst = 0x15,
GetStats = 0x10,
ResetStats = 0x00,
CfgDIOIrq = 0x08,
GetIrqStatus = 0x12,
ClrIrqStatus = 0x02,
Calibrate = 0x89,
CalibrateImage = 0x98,
SetRegulatorMode = 0x96,
GetErrors = 0x17,
ClrErrors = 0x07,
SetTCXOMode = 0x97,
SetTxFallbackMode = 0x93,
SetRFSwitchMode = 0x9D,
SetStopRxTimerOnPreamble = 0x9F,
SetLoRaSymbTimeout = 0xA0,
}
impl OpCode {
pub fn value(self) -> u8 {
self as u8
}
}
pub struct SleepParams {
pub wakeup_rtc: bool, // get out of sleep mode if wakeup signal received from RTC
pub reset: bool,
pub warm_start: bool,
}
impl SleepParams {
pub fn value(self) -> u8 {
((self.warm_start as u8) << 2) | ((self.reset as u8) << 1) | (self.wakeup_rtc as u8)
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum StandbyMode {
RC = 0x00,
XOSC = 0x01,
}
impl StandbyMode {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum RegulatorMode {
UseLDO = 0x00,
UseDCDC = 0x01,
}
impl RegulatorMode {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub struct CalibrationParams {
pub rc64k_enable: bool, // calibrate RC64K clock
pub rc13m_enable: bool, // calibrate RC13M clock
pub pll_enable: bool, // calibrate PLL
pub adc_pulse_enable: bool, // calibrate ADC Pulse
pub adc_bulkn_enable: bool, // calibrate ADC bulkN
pub adc_bulkp_enable: bool, // calibrate ADC bulkP
pub img_enable: bool,
}
impl CalibrationParams {
pub fn value(self) -> u8 {
((self.img_enable as u8) << 6)
| ((self.adc_bulkp_enable as u8) << 5)
| ((self.adc_bulkn_enable as u8) << 4)
| ((self.adc_pulse_enable as u8) << 3)
| ((self.pll_enable as u8) << 2)
| ((self.rc13m_enable as u8) << 1)
| ((self.rc64k_enable as u8) << 0)
}
}
#[derive(Clone, Copy)]
pub enum TcxoCtrlVoltage {
Ctrl1V6 = 0x00,
Ctrl1V7 = 0x01,
Ctrl1V8 = 0x02,
Ctrl2V2 = 0x03,
Ctrl2V4 = 0x04,
Ctrl2V7 = 0x05,
Ctrl3V0 = 0x06,
Ctrl3V3 = 0x07,
}
impl TcxoCtrlVoltage {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum RampTime {
Ramp10Us = 0x00,
Ramp20Us = 0x01,
Ramp40Us = 0x02,
Ramp80Us = 0x03,
Ramp200Us = 0x04,
Ramp800Us = 0x05,
Ramp1700Us = 0x06,
Ramp3400Us = 0x07,
}
impl RampTime {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum SpreadingFactor {
_5 = 0x05,
_6 = 0x06,
_7 = 0x07,
_8 = 0x08,
_9 = 0x09,
_10 = 0x0A,
_11 = 0x0B,
_12 = 0x0C,
}
impl SpreadingFactor {
pub fn value(self) -> u8 {
self as u8
}
}
impl From<device::SpreadingFactor> for SpreadingFactor {
fn from(sf: device::SpreadingFactor) -> Self {
match sf {
device::SpreadingFactor::_7 => SpreadingFactor::_7,
device::SpreadingFactor::_8 => SpreadingFactor::_8,
device::SpreadingFactor::_9 => SpreadingFactor::_9,
device::SpreadingFactor::_10 => SpreadingFactor::_10,
device::SpreadingFactor::_11 => SpreadingFactor::_11,
device::SpreadingFactor::_12 => SpreadingFactor::_12,
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Bandwidth {
_500KHz = 0x06,
_250KHz = 0x05,
_125KHz = 0x04,
}
impl Bandwidth {
pub fn value(self) -> u8 {
self as u8
}
pub fn value_in_hz(self) -> u32 {
match self {
Bandwidth::_125KHz => 125000u32,
Bandwidth::_250KHz => 250000u32,
Bandwidth::_500KHz => 500000u32,
}
}
}
impl From<device::Bandwidth> for Bandwidth {
fn from(bw: device::Bandwidth) -> Self {
match bw {
device::Bandwidth::_500KHz => Bandwidth::_500KHz,
device::Bandwidth::_250KHz => Bandwidth::_250KHz,
device::Bandwidth::_125KHz => Bandwidth::_125KHz,
}
}
}
#[derive(Clone, Copy)]
pub enum CodingRate {
_4_5 = 0x01,
_4_6 = 0x02,
_4_7 = 0x03,
_4_8 = 0x04,
}
impl CodingRate {
pub fn value(self) -> u8 {
self as u8
}
}
impl From<device::CodingRate> for CodingRate {
fn from(cr: device::CodingRate) -> Self {
match cr {
device::CodingRate::_4_5 => CodingRate::_4_5,
device::CodingRate::_4_6 => CodingRate::_4_6,
device::CodingRate::_4_7 => CodingRate::_4_7,
device::CodingRate::_4_8 => CodingRate::_4_8,
}
}
}
#[derive(Clone, Copy)]
pub struct ModulationParams {
pub spreading_factor: SpreadingFactor,
pub bandwidth: Bandwidth,
pub coding_rate: CodingRate,
pub low_data_rate_optimize: u8,
}
#[derive(Clone, Copy)]
pub struct PacketParams {
pub preamble_length: u16, // number of LoRa symbols in the preamble
pub implicit_header: bool, // if the header is explicit, it will be transmitted in the LoRa packet, but is not transmitted if the header is implicit (known fixed length)
pub payload_length: u8,
pub crc_on: bool,
pub iq_inverted: bool,
}
#[derive(Clone, Copy)]
pub enum CADSymbols {
_1 = 0x00,
_2 = 0x01,
_4 = 0x02,
_8 = 0x03,
_16 = 0x04,
}
impl CADSymbols {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum CADExitMode {
CADOnly = 0x00,
CADRx = 0x01,
CADLBT = 0x10,
}
impl CADExitMode {
pub fn value(self) -> u8 {
self as u8
}
}

View File

@ -1,674 +0,0 @@
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiBus;
use super::mod_params::*;
use super::LoRa;
// Internal frequency of the radio
const SX126X_XTAL_FREQ: u32 = 32000000;
// Scaling factor used to perform fixed-point operations
const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14;
// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT
const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT);
// Maximum value for parameter symbNum
const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248;
// Provides board-specific functionality for Semtech SX126x-based boards
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
where
SPI: SpiBus<u8, Error = BUS>,
CTRL: OutputPin,
WAIT: Wait,
{
// Initialize the radio driver
pub(super) async fn sub_init(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_reset().await?;
self.brd_wakeup().await?;
self.sub_set_standby(StandbyMode::RC).await?;
self.brd_io_tcxo_init().await?;
self.brd_io_rf_switch_init().await?;
self.image_calibrated = false;
Ok(())
}
// Wakeup the radio if it is in Sleep mode and check that Busy is low
pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError<BUS>> {
let operating_mode = self.brd_get_operating_mode();
if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle {
self.brd_wakeup().await?;
}
self.brd_wait_on_busy().await?;
Ok(())
}
// Save the payload to be sent in the radio buffer
pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError<BUS>> {
self.brd_write_buffer(0x00, payload).await?;
Ok(())
}
// Read the payload received.
pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
let (size, offset) = self.sub_get_rx_buffer_status().await?;
if (size as usize) > buffer.len() {
Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len()))
} else {
self.brd_read_buffer(offset, buffer).await?;
Ok(size)
}
}
// Send a payload
pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_payload(payload).await?;
self.sub_set_tx(timeout).await?;
Ok(())
}
// Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command.
//
// The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of
// the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts
// generated during this process will not cause undesired side-effects in the software.
//
// The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing.
pub(super) async fn sub_get_random(&mut self) -> Result<u32, RadioError<BUS>> {
let mut reg_ana_lna_buffer_original = [0x00u8];
let mut reg_ana_mixer_buffer_original = [0x00u8];
let mut reg_ana_lna_buffer = [0x00u8];
let mut reg_ana_mixer_buffer = [0x00u8];
let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8];
self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original)
.await?;
reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0));
self.brd_write_registers(Register::AnaLNA, &reg_ana_lna_buffer).await?;
self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original)
.await?;
reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7));
self.brd_write_registers(Register::AnaMixer, &reg_ana_mixer_buffer)
.await?;
// Set radio in continuous reception
self.sub_set_rx(0xFFFFFFu32).await?;
self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer)
.await?;
self.sub_set_standby(StandbyMode::RC).await?;
self.brd_write_registers(Register::AnaLNA, &reg_ana_lna_buffer_original)
.await?;
self.brd_write_registers(Register::AnaMixer, &reg_ana_mixer_buffer_original)
.await?;
Ok(Self::convert_u8_buffer_to_u32(&number_buffer))
}
// Set the radio in sleep mode
pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError<BUS>> {
self.brd_ant_sleep()?;
if !sleep_config.warm_start {
self.image_calibrated = false;
}
self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()])
.await?;
self.brd_set_operating_mode(RadioMode::Sleep);
Ok(())
}
// Set the radio in configuration mode
pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?;
if mode == StandbyMode::RC {
self.brd_set_operating_mode(RadioMode::StandbyRC);
} else {
self.brd_set_operating_mode(RadioMode::StandbyXOSC);
}
self.brd_ant_sleep()?;
Ok(())
}
// Set the radio in FS mode
pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError<BUS>> {
// antenna settings ???
self.brd_write_command(OpCode::SetFS, &[]).await?;
self.brd_set_operating_mode(RadioMode::FrequencySynthesis);
Ok(())
}
// Set the radio in transmission mode with timeout specified
pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_ant_set_tx()?;
self.brd_set_operating_mode(RadioMode::Transmit);
self.brd_write_command(OpCode::SetTx, &buffer).await?;
Ok(())
}
// Set the radio in reception mode with timeout specified
pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_ant_set_rx()?;
self.brd_set_operating_mode(RadioMode::Receive);
self.brd_write_registers(Register::RxGain, &[0x94u8]).await?;
self.brd_write_command(OpCode::SetRx, &buffer).await?;
Ok(())
}
// Set the radio in reception mode with Boosted LNA gain and timeout specified
pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_ant_set_rx()?;
self.brd_set_operating_mode(RadioMode::Receive);
// set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
self.brd_write_registers(Register::RxGain, &[0x96u8]).await?;
self.brd_write_command(OpCode::SetRx, &buffer).await?;
Ok(())
}
// Set the Rx duty cycle management parameters
pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
((rx_time >> 16) & 0xFF) as u8,
((rx_time >> 8) & 0xFF) as u8,
(rx_time & 0xFF) as u8,
((sleep_time >> 16) & 0xFF) as u8,
((sleep_time >> 8) & 0xFF) as u8,
(sleep_time & 0xFF) as u8,
];
// antenna settings ???
self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?;
self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle);
Ok(())
}
// Set the radio in CAD mode
pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_ant_set_rx()?;
self.brd_write_command(OpCode::SetCAD, &[]).await?;
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
Ok(())
}
// Set the radio in continuous wave transmission mode
pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_ant_set_tx()?;
self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?;
self.brd_set_operating_mode(RadioMode::Transmit);
Ok(())
}
// Set the radio in continuous preamble transmission mode
pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_ant_set_tx()?;
self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?;
self.brd_set_operating_mode(RadioMode::Transmit);
Ok(())
}
// Decide which interrupt will stop the internal radio rx timer.
// false timer stop after header/syncword detection
// true timer stop after preamble detection
pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect(
&mut self,
enable: bool,
) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8])
.await?;
Ok(())
}
// Set the number of symbols the radio will wait to validate a reception
pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError<BUS>> {
let mut exp = 0u8;
let mut reg;
let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1;
while mant > 31 {
mant = (mant + 3) >> 2;
exp += 1;
}
reg = mant << ((2 * exp) + 1);
self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?;
if symb_num != 0 {
reg = exp + (mant << 3);
self.brd_write_registers(Register::SynchTimeout, &[reg]).await?;
}
Ok(())
}
// Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled
pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()])
.await?;
Ok(())
}
// Calibrate the given radio block
pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()])
.await?;
Ok(())
}
// Calibrate the image rejection based on the given frequency
pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError<BUS>> {
let mut cal_freq = [0x00u8, 0x00u8];
if freq > 900000000 {
cal_freq[0] = 0xE1;
cal_freq[1] = 0xE9;
} else if freq > 850000000 {
cal_freq[0] = 0xD7;
cal_freq[1] = 0xDB;
} else if freq > 770000000 {
cal_freq[0] = 0xC1;
cal_freq[1] = 0xC5;
} else if freq > 460000000 {
cal_freq[0] = 0x75;
cal_freq[1] = 0x81;
} else if freq > 425000000 {
cal_freq[0] = 0x6B;
cal_freq[1] = 0x6F;
}
self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?;
Ok(())
}
// Activate the extention of the timeout when a long preamble is used
pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError<BUS>> {
Ok(()) // no operation currently
}
// Set the transmission parameters
// hp_max 0 for sx1261, 7 for sx1262
// device_sel 1 for sx1261, 0 for sx1262
// pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT
pub(super) async fn sub_set_pa_config(
&mut self,
pa_duty_cycle: u8,
hp_max: u8,
device_sel: u8,
pa_lut: u8,
) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut])
.await?;
Ok(())
}
// Define into which mode the chip goes after a TX / RX done
pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode])
.await?;
Ok(())
}
// Set the IRQ mask and DIO masks
pub(super) async fn sub_set_dio_irq_params(
&mut self,
irq_mask: u16,
dio1_mask: u16,
dio2_mask: u16,
dio3_mask: u16,
) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; 8];
buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8;
buffer[1] = (irq_mask & 0x00FF) as u8;
buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8;
buffer[3] = (dio1_mask & 0x00FF) as u8;
buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8;
buffer[5] = (dio2_mask & 0x00FF) as u8;
buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8;
buffer[7] = (dio3_mask & 0x00FF) as u8;
self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?;
Ok(())
}
// Return the current IRQ status
pub(super) async fn sub_get_irq_status(&mut self) -> Result<u16, RadioError<BUS>> {
let mut irq_status = [0x00u8, 0x00u8];
self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?;
Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16))
}
// Indicate if DIO2 is used to control an RF Switch
pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?;
Ok(())
}
// Indicate if the radio main clock is supplied from a TCXO
// tcxo_voltage voltage used to control the TCXO on/off from DIO3
// timeout duration given to the TCXO to go to 32MHz
pub(super) async fn sub_set_dio3_as_tcxo_ctrl(
&mut self,
tcxo_voltage: TcxoCtrlVoltage,
timeout: u32,
) -> Result<(), RadioError<BUS>> {
let buffer = [
tcxo_voltage.value() & 0x07,
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?;
Ok(())
}
// Set the RF frequency (Hz)
pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; 4];
if !self.image_calibrated {
self.sub_calibrate_image(frequency).await?;
self.image_calibrated = true;
}
let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency);
buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8;
buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8;
buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8;
buffer[3] = (freq_in_pll_steps & 0xFF) as u8;
self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?;
Ok(())
}
// Set the radio for the given protocol (LoRa or GFSK). This method has to be called before setting RF frequency, modulation paramaters, and packet paramaters.
pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError<BUS>> {
self.packet_type = packet_type;
self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()])
.await?;
Ok(())
}
// Get the current radio protocol (LoRa or GFSK)
pub(super) fn sub_get_packet_type(&mut self) -> PacketType {
self.packet_type
}
// Set the transmission parameters
// power RF output power [-18..13] dBm
// ramp_time transmission ramp up time
pub(super) async fn sub_set_tx_params(
&mut self,
mut power: i8,
ramp_time: RampTime,
) -> Result<(), RadioError<BUS>> {
if self.brd_get_radio_type() == RadioType::SX1261 {
if power == 15 {
self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?;
} else {
self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?;
}
if power >= 14 {
power = 14;
} else if power < -17 {
power = -17;
}
} else {
// Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
let mut tx_clamp_cfg = [0x00u8];
self.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?;
tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1);
self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?;
self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?;
if power > 22 {
power = 22;
} else if power < -9 {
power = -9;
}
}
// power conversion of negative number from i8 to u8 ???
self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()])
.await?;
Ok(())
}
// Set the modulation parameters
pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError<BUS>> {
if self.modulation_params.is_some() {
let mut buffer = [0x00u8; 4];
// Since this driver only supports LoRa, ensure the packet type is set accordingly
self.sub_set_packet_type(PacketType::LoRa).await?;
let modulation_params = self.modulation_params.unwrap();
buffer[0] = modulation_params.spreading_factor.value();
buffer[1] = modulation_params.bandwidth.value();
buffer[2] = modulation_params.coding_rate.value();
buffer[3] = modulation_params.low_data_rate_optimize;
self.brd_write_command(OpCode::SetModulationParams, &buffer).await?;
Ok(())
} else {
Err(RadioError::ModulationParamsMissing)
}
}
// Set the packet parameters
pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError<BUS>> {
if self.packet_params.is_some() {
let mut buffer = [0x00u8; 6];
// Since this driver only supports LoRa, ensure the packet type is set accordingly
self.sub_set_packet_type(PacketType::LoRa).await?;
let packet_params = self.packet_params.unwrap();
buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8;
buffer[1] = (packet_params.preamble_length & 0xFF) as u8;
buffer[2] = packet_params.implicit_header as u8;
buffer[3] = packet_params.payload_length;
buffer[4] = packet_params.crc_on as u8;
buffer[5] = packet_params.iq_inverted as u8;
self.brd_write_command(OpCode::SetPacketParams, &buffer).await?;
Ok(())
} else {
Err(RadioError::PacketParamsMissing)
}
}
// Set the channel activity detection (CAD) parameters
// symbols number of symbols to use for CAD operations
// det_peak limit for detection of SNR peak used in the CAD
// det_min minimum symbol recognition for CAD
// exit_mode operation to be done at the end of CAD action
// timeout timeout value to abort the CAD activity
pub(super) async fn sub_set_cad_params(
&mut self,
symbols: CADSymbols,
det_peak: u8,
det_min: u8,
exit_mode: CADExitMode,
timeout: u32,
) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; 7];
buffer[0] = symbols.value();
buffer[1] = det_peak;
buffer[2] = det_min;
buffer[3] = exit_mode.value();
buffer[4] = Self::timeout_1(timeout);
buffer[5] = Self::timeout_2(timeout);
buffer[6] = Self::timeout_3(timeout);
self.brd_write_command(OpCode::SetCADParams, &buffer).await?;
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
Ok(())
}
// Set the data buffer base address for transmission and reception
pub(super) async fn sub_set_buffer_base_address(
&mut self,
tx_base_address: u8,
rx_base_address: u8,
) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address])
.await?;
Ok(())
}
// Get the current radio status
pub(super) async fn sub_get_status(&mut self) -> Result<RadioStatus, RadioError<BUS>> {
let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?;
Ok(RadioStatus {
cmd_status: (status & (0x07 << 1)) >> 1,
chip_mode: (status & (0x07 << 4)) >> 4,
})
}
// Get the instantaneous RSSI value for the last packet received
pub(super) async fn sub_get_rssi_inst(&mut self) -> Result<i8, RadioError<BUS>> {
let mut buffer = [0x00u8];
self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?;
let rssi: i8 = ((-(buffer[0] as i32)) >> 1) as i8; // check this ???
Ok(rssi)
}
// Get the last received packet buffer status
pub(super) async fn sub_get_rx_buffer_status(&mut self) -> Result<(u8, u8), RadioError<BUS>> {
if self.packet_params.is_some() {
let mut status = [0x00u8; 2];
let mut payload_length_buffer = [0x00u8];
self.brd_read_command(OpCode::GetRxBufferStatus, &mut status).await?;
if (self.sub_get_packet_type() == PacketType::LoRa) && self.packet_params.unwrap().implicit_header {
self.brd_read_registers(Register::PayloadLength, &mut payload_length_buffer)
.await?;
} else {
payload_length_buffer[0] = status[0];
}
let payload_length = payload_length_buffer[0];
let offset = status[1];
Ok((payload_length, offset))
} else {
Err(RadioError::PacketParamsMissing)
}
}
// Get the last received packet payload status
pub(super) async fn sub_get_packet_status(&mut self) -> Result<PacketStatus, RadioError<BUS>> {
let mut status = [0x00u8; 3];
self.brd_read_command(OpCode::GetPacketStatus, &mut status).await?;
// check this ???
let rssi = ((-(status[0] as i32)) >> 1) as i8;
let snr = ((status[1] as i8) + 2) >> 2;
let signal_rssi = ((-(status[2] as i32)) >> 1) as i8;
let freq_error = self.frequency_error;
Ok(PacketStatus {
rssi,
snr,
signal_rssi,
freq_error,
})
}
// Get the possible system errors
pub(super) async fn sub_get_device_errors(&mut self) -> Result<RadioSystemError, RadioError<BUS>> {
let mut errors = [0x00u8; 2];
self.brd_read_command(OpCode::GetErrors, &mut errors).await?;
Ok(RadioSystemError {
rc_64khz_calibration: (errors[1] & (1 << 0)) != 0,
rc_13mhz_calibration: (errors[1] & (1 << 1)) != 0,
pll_calibration: (errors[1] & (1 << 2)) != 0,
adc_calibration: (errors[1] & (1 << 3)) != 0,
image_calibration: (errors[1] & (1 << 4)) != 0,
xosc_start: (errors[1] & (1 << 5)) != 0,
pll_lock: (errors[1] & (1 << 6)) != 0,
pa_ramp: (errors[0] & (1 << 0)) != 0,
})
}
// Clear all the errors in the device
pub(super) async fn sub_clear_device_errors(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::ClrErrors, &[0x00u8, 0x00u8]).await?;
Ok(())
}
// Clear the IRQs
pub(super) async fn sub_clear_irq_status(&mut self, irq: u16) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8, 0x00u8];
buffer[0] = ((irq >> 8) & 0xFF) as u8;
buffer[1] = (irq & 0xFF) as u8;
self.brd_write_command(OpCode::ClrIrqStatus, &buffer).await?;
Ok(())
}
// Utility functions
fn timeout_1(timeout: u32) -> u8 {
((timeout >> 16) & 0xFF) as u8
}
fn timeout_2(timeout: u32) -> u8 {
((timeout >> 8) & 0xFF) as u8
}
fn timeout_3(timeout: u32) -> u8 {
(timeout & 0xFF) as u8
}
// check this ???
fn convert_u8_buffer_to_u32(buffer: &[u8; 4]) -> u32 {
let b0 = buffer[0] as u32;
let b1 = buffer[1] as u32;
let b2 = buffer[2] as u32;
let b3 = buffer[3] as u32;
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}
fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 {
// Get integer and fractional parts of the frequency computed with a PLL step scaled value
let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED;
let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED);
(steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT)
+ (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED)
}
}

View File

@ -1,217 +0,0 @@
use core::future::Future;
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::*;
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
use lorawan_device::async_device::Timings;
mod sx127x_lora;
use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ};
/// Trait representing a radio switch for boards using the Sx127x radio. One some
/// boards, this will be a dummy implementation that does nothing.
pub trait RadioSwitch {
fn set_tx(&mut self);
fn set_rx(&mut self);
}
/// Semtech Sx127x radio peripheral
pub struct Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
E: 'static,
CS: OutputPin + 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
{
radio: LoRa<SPI, CS, RESET>,
rfs: RFS,
irq: I,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
Idle,
Txing,
Rxing,
}
impl<SPI, CS, RESET, E, I, RFS> Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
CS: OutputPin + 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
E: 'static,
{
pub async fn new(
spi: SPI,
cs: CS,
reset: RESET,
irq: I,
rfs: RFS,
) -> Result<Self, RadioError<E, CS::Error, RESET::Error>> {
let mut radio = LoRa::new(spi, cs, reset);
radio.reset().await?;
Ok(Self { radio, irq, rfs })
}
}
impl<SPI, CS, RESET, E, I, RFS> Timings for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
CS: OutputPin + 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
{
fn get_rx_window_offset_ms(&self) -> i32 {
-500
}
fn get_rx_window_duration_ms(&self) -> u32 {
800
}
}
impl<SPI, CS, RESET, E, I, RFS> PhyRxTx for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
CS: OutputPin + 'static,
E: 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
{
type PhyError = Sx127xError;
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
where
SPI: 'm,
CS: 'm,
RESET: 'm,
E: 'm,
I: 'm,
RFS: 'm;
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
trace!("TX START");
async move {
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
self.rfs.set_tx();
self.radio.set_tx_power(14, 0).await?;
self.radio.set_frequency(config.rf.frequency).await?;
// TODO: Modify radio to support other coding rates
self.radio.set_coding_rate_4(5).await?;
self.radio
.set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))
.await?;
self.radio
.set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))
.await?;
self.radio.set_preamble_length(8).await?;
self.radio.set_lora_pa_ramp().await?;
self.radio.set_lora_sync_word().await?;
self.radio.set_invert_iq(false).await?;
self.radio.set_crc(true).await?;
self.radio.set_dio0_tx_done().await?;
self.radio.transmit_start(buf).await?;
loop {
self.irq.wait_for_rising_edge().await.unwrap();
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
let irq = self.radio.clear_irq().await.ok().unwrap();
if (irq & IRQ::IrqTxDoneMask.addr()) != 0 {
trace!("TX DONE");
return Ok(0);
}
}
}
}
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
where
SPI: 'm,
CS: 'm,
RESET: 'm,
E: 'm,
I: 'm,
RFS: 'm;
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
trace!("RX START");
async move {
self.rfs.set_rx();
self.radio.reset_payload_length().await?;
self.radio.set_frequency(config.frequency).await?;
// TODO: Modify radio to support other coding rates
self.radio.set_coding_rate_4(5).await?;
self.radio
.set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))
.await?;
self.radio
.set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))
.await?;
self.radio.set_preamble_length(8).await?;
self.radio.set_lora_sync_word().await?;
self.radio.set_invert_iq(true).await?;
self.radio.set_crc(true).await?;
self.radio.set_dio0_rx_done().await?;
self.radio.set_mode(RadioMode::RxContinuous).await?;
loop {
self.irq.wait_for_rising_edge().await.unwrap();
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
let irq = self.radio.clear_irq().await.ok().unwrap();
if (irq & IRQ::IrqRxDoneMask.addr()) != 0 {
let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16;
let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8;
let response = if let Ok(size) = self.radio.read_packet_size().await {
self.radio.read_packet(buf).await?;
Ok((size, RxQuality::new(rssi, snr)))
} else {
Ok((0, RxQuality::new(rssi, snr)))
};
trace!("RX DONE");
return response;
}
}
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Sx127xError;
impl<A, B, C> From<sx127x_lora::Error<A, B, C>> for Sx127xError {
fn from(_: sx127x_lora::Error<A, B, C>) -> Self {
Sx127xError
}
}
fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 {
match sf {
SpreadingFactor::_7 => 7,
SpreadingFactor::_8 => 8,
SpreadingFactor::_9 => 9,
SpreadingFactor::_10 => 10,
SpreadingFactor::_11 => 11,
SpreadingFactor::_12 => 12,
}
}
fn bandwidth_to_i64(bw: Bandwidth) -> i64 {
match bw {
Bandwidth::_125KHz => 125_000,
Bandwidth::_250KHz => 250_000,
Bandwidth::_500KHz => 500_000,
}
}

View File

@ -1,539 +0,0 @@
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
// license
//
// Modifications made to make the driver work with the rust-lorawan link layer.
#![allow(dead_code)]
use bit_field::BitField;
use embassy_time::{Duration, Timer};
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::spi::SpiBus;
mod register;
pub use self::register::IRQ;
use self::register::{PaConfig, Register};
/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi
pub struct LoRa<SPI, CS, RESET> {
spi: SPI,
cs: CS,
reset: RESET,
pub explicit_header: bool,
pub mode: RadioMode,
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<SPI, CS, RESET> {
Uninformative,
VersionMismatch(u8),
CS(CS),
Reset(RESET),
SPI(SPI),
Transmitting,
}
use Error::*;
use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown};
#[cfg(not(feature = "version_0x09"))]
const VERSION_CHECK: u8 = 0x12;
#[cfg(feature = "version_0x09")]
const VERSION_CHECK: u8 = 0x09;
impl<SPI, CS, RESET, E> LoRa<SPI, CS, RESET>
where
SPI: SpiBus<u8, Error = E>,
CS: OutputPin,
RESET: OutputPin,
{
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time.
/// This also preforms a hardware reset of the module and then puts it in standby.
pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self {
Self {
spi,
cs,
reset,
explicit_header: true,
mode: RadioMode::Sleep,
}
}
pub async fn reset(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.reset.set_low().map_err(Reset)?;
Timer::after(Duration::from_millis(10)).await;
self.reset.set_high().map_err(Reset)?;
Timer::after(Duration::from_millis(10)).await;
let version = self.read_register(Register::RegVersion.addr()).await?;
if version == VERSION_CHECK {
self.set_mode(RadioMode::Sleep).await?;
self.write_register(Register::RegFifoTxBaseAddr.addr(), 0).await?;
self.write_register(Register::RegFifoRxBaseAddr.addr(), 0).await?;
let lna = self.read_register(Register::RegLna.addr()).await?;
self.write_register(Register::RegLna.addr(), lna | 0x03).await?;
self.write_register(Register::RegModemConfig3.addr(), 0x04).await?;
self.set_tcxo(true).await?;
self.set_mode(RadioMode::Stdby).await?;
self.cs.set_high().map_err(CS)?;
Ok(())
} else {
Err(Error::VersionMismatch(version))
}
}
pub async fn set_dio0_tx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)
.await?;
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40)
.await
}
pub async fn set_dio0_rx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)
.await?;
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F)
.await
}
pub async fn transmit_start(&mut self, buffer: &[u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
assert!(buffer.len() < 255);
if self.transmitting().await? {
//trace!("ALREADY TRANSMNITTING");
Err(Transmitting)
} else {
self.set_mode(RadioMode::Stdby).await?;
if self.explicit_header {
self.set_explicit_header_mode().await?;
} else {
self.set_implicit_header_mode().await?;
}
self.write_register(Register::RegIrqFlags.addr(), 0).await?;
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
self.write_register(Register::RegPayloadLength.addr(), 0).await?;
for byte in buffer.iter() {
self.write_register(Register::RegFifo.addr(), *byte).await?;
}
self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)
.await?;
self.set_mode(RadioMode::Tx).await?;
Ok(())
}
}
pub async fn packet_ready(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegIrqFlags.addr()).await?.get_bit(6))
}
pub async fn irq_flags_mask(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegIrqFlagsMask.addr()).await? as u8)
}
pub async fn irq_flags(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegIrqFlags.addr()).await? as u8)
}
pub async fn read_packet_size(&mut self) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
Ok(size as usize)
}
/// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a
/// new packet ready to be read.
pub async fn read_packet(&mut self, buffer: &mut [u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.clear_irq().await?;
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
assert!(size as usize <= buffer.len());
let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr()).await?;
self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr).await?;
for i in 0..size {
let byte = self.read_register(Register::RegFifo.addr()).await?;
buffer[i as usize] = byte;
}
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
Ok(())
}
/// Returns true if the radio is currently transmitting a packet.
pub async fn transmitting(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
if (self.read_register(Register::RegOpMode.addr()).await?) & RadioMode::Tx.addr() == RadioMode::Tx.addr() {
Ok(true)
} else {
if (self.read_register(Register::RegIrqFlags.addr()).await? & IRQ::IrqTxDoneMask.addr()) == 1 {
self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())
.await?;
}
Ok(false)
}
}
/// Clears the radio's IRQ registers.
pub async fn clear_irq(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
let irq_flags = self.read_register(Register::RegIrqFlags.addr()).await?;
self.write_register(Register::RegIrqFlags.addr(), 0xFF).await?;
Ok(irq_flags)
}
/// Sets the transmit power and pin. Levels can range from 0-14 when the output
/// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB.
/// Default value is `17`.
pub async fn set_tx_power(
&mut self,
mut level: i32,
output_pin: u8,
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if PaConfig::PaOutputRfoPin.addr() == output_pin {
// RFO
if level < 0 {
level = 0;
} else if level > 14 {
level = 14;
}
self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8)
.await
} else {
// PA BOOST
if level > 17 {
if level > 20 {
level = 20;
}
// subtract 3 from level, so 18 - 20 maps to 15 - 17
level -= 3;
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
self.write_register(Register::RegPaDac.addr(), 0x87).await?;
self.set_ocp(140).await?;
} else {
if level < 2 {
level = 2;
}
//Default value PA_HF/LF or +17dBm
self.write_register(Register::RegPaDac.addr(), 0x84).await?;
self.set_ocp(100).await?;
}
level -= 2;
self.write_register(Register::RegPaConfig.addr(), PaConfig::PaBoost.addr() | level as u8)
.await
}
}
pub async fn get_modem_stat(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegModemStat.addr()).await? as u8)
}
/// Sets the over current protection on the radio(mA).
pub async fn set_ocp(&mut self, ma: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let mut ocp_trim: u8 = 27;
if ma <= 120 {
ocp_trim = (ma - 45) / 5;
} else if ma <= 240 {
ocp_trim = (ma + 30) / 10;
}
self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim))
.await
}
/// Sets the state of the radio. Default mode after initiation is `Standby`.
pub async fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if self.explicit_header {
self.set_explicit_header_mode().await?;
} else {
self.set_implicit_header_mode().await?;
}
self.write_register(
Register::RegOpMode.addr(),
RadioMode::LongRangeMode.addr() | mode.addr(),
)
.await?;
self.mode = mode;
Ok(())
}
pub async fn reset_payload_length(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegPayloadLength.addr(), 0xFF).await
}
/// Sets the frequency of the radio. Values are in megahertz.
/// I.E. 915 MHz must be used for North America. Check regulation for your area.
pub async fn set_frequency(&mut self, freq: u32) -> Result<(), Error<E, CS::Error, RESET::Error>> {
const FREQ_STEP: f64 = 61.03515625;
// calculate register values
let frf = (freq as f64 / FREQ_STEP) as u32;
// write registers
self.write_register(Register::RegFrfMsb.addr(), ((frf & 0x00FF_0000) >> 16) as u8)
.await?;
self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)
.await?;
self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8)
.await
}
/// Sets the radio to use an explicit header. Default state is `ON`.
async fn set_explicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)
.await?;
self.explicit_header = true;
Ok(())
}
/// Sets the radio to use an implicit header. Default state is `OFF`.
async fn set_implicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)
.await?;
self.explicit_header = false;
Ok(())
}
/// Sets the spreading factor of the radio. Supported values are between 6 and 12.
/// If a spreading factor of 6 is set, implicit header mode must be used to transmit
/// and receive packets. Default value is `7`.
pub async fn set_spreading_factor(&mut self, mut sf: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if sf < 6 {
sf = 6;
} else if sf > 12 {
sf = 12;
}
if sf == 6 {
self.write_register(Register::RegDetectionOptimize.addr(), 0xc5).await?;
self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)
.await?;
} else {
self.write_register(Register::RegDetectionOptimize.addr(), 0xc3).await?;
self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)
.await?;
}
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
self.write_register(
Register::RegModemConfig2.addr(),
(modem_config_2 & 0x0f) | ((sf << 4) & 0xf0),
)
.await?;
self.set_ldo_flag().await?;
self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05).await?;
Ok(())
}
pub async fn set_tcxo(&mut self, external: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if external {
self.write_register(Register::RegTcxo.addr(), 0x10).await
} else {
self.write_register(Register::RegTcxo.addr(), 0x00).await
}
}
/// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`,
/// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz`
/// Default value is `125000 Hz`
pub async fn set_signal_bandwidth(&mut self, sbw: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let bw: i64 = match sbw {
7_800 => 0,
10_400 => 1,
15_600 => 2,
20_800 => 3,
31_250 => 4,
41_700 => 5,
62_500 => 6,
125_000 => 7,
250_000 => 8,
_ => 9,
};
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(
Register::RegModemConfig1.addr(),
(modem_config_1 & 0x0f) | ((bw << 4) as u8),
)
.await?;
self.set_ldo_flag().await?;
Ok(())
}
/// Sets the coding rate of the radio with the numerator fixed at 4. Supported values
/// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`.
/// Default value is `5`.
pub async fn set_coding_rate_4(&mut self, mut denominator: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if denominator < 5 {
denominator = 5;
} else if denominator > 8 {
denominator = 8;
}
let cr = denominator - 4;
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(Register::RegModemConfig1.addr(), (modem_config_1 & 0xf1) | (cr << 1))
.await
}
/// Sets the preamble length of the radio. Values are between 6 and 65535.
/// Default value is `8`.
pub async fn set_preamble_length(&mut self, length: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)
.await?;
self.write_register(Register::RegPreambleLsb.addr(), length as u8).await
}
/// Enables are disables the radio's CRC check. Default value is `false`.
pub async fn set_crc(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
if value {
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04)
.await
} else {
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb)
.await
}
}
/// Inverts the radio's IQ signals. Default value is `false`.
pub async fn set_invert_iq(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if value {
self.write_register(Register::RegInvertiq.addr(), 0x66).await?;
self.write_register(Register::RegInvertiq2.addr(), 0x19).await
} else {
self.write_register(Register::RegInvertiq.addr(), 0x27).await?;
self.write_register(Register::RegInvertiq2.addr(), 0x1d).await
}
}
/// Returns the spreading factor of the radio.
pub async fn get_spreading_factor(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegModemConfig2.addr()).await? >> 4)
}
/// Returns the signal bandwidth of the radio.
pub async fn get_signal_bandwidth(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
let bw = self.read_register(Register::RegModemConfig1.addr()).await? >> 4;
let bw = match bw {
0 => 7_800,
1 => 10_400,
2 => 15_600,
3 => 20_800,
4 => 31_250,
5 => 41_700,
6 => 62_500,
7 => 125_000,
8 => 250_000,
9 => 500_000,
_ => -1,
};
Ok(bw)
}
/// Returns the RSSI of the last received packet.
pub async fn get_packet_rssi(&mut self) -> Result<i32, Error<E, CS::Error, RESET::Error>> {
Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr()).await?) - 157)
}
/// Returns the signal to noise radio of the the last received packet.
pub async fn get_packet_snr(&mut self) -> Result<f64, Error<E, CS::Error, RESET::Error>> {
Ok(f64::from(self.read_register(Register::RegPktSnrValue.addr()).await?))
}
/// Returns the frequency error of the last received packet in Hz.
pub async fn get_packet_frequency_error(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
let mut freq_error: i32;
freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr()).await? & 0x7);
freq_error <<= 8i64;
freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr()).await?);
freq_error <<= 8i64;
freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr()).await?);
let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal))
* (self.get_signal_bandwidth().await? as f64 / 500_000.0f64); // p. 37
Ok(f_error as i64)
}
async fn set_ldo_flag(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let sw = self.get_signal_bandwidth().await?;
// Section 4.1.1.5
let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor().await?));
// Section 4.1.1.6
let ldo_on = symbol_duration > 16;
let mut config_3 = self.read_register(Register::RegModemConfig3.addr()).await?;
config_3.set_bit(3, ldo_on);
//config_3.set_bit(2, true);
self.write_register(Register::RegModemConfig3.addr(), config_3).await
}
async fn read_register(&mut self, reg: u8) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
let mut buffer = [reg & 0x7f, 0];
self.cs.set_low().map_err(CS)?;
let _ = self.spi.transfer(&mut buffer, &[reg & 0x7f, 0]).await.map_err(SPI)?;
self.cs.set_high().map_err(CS)?;
Ok(buffer[1])
}
async fn write_register(&mut self, reg: u8, byte: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.cs.set_low().map_err(CS)?;
let buffer = [reg | 0x80, byte];
self.spi.write(&buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(CS)?;
Ok(())
}
pub async fn put_in_fsk_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
// Put in FSK mode
let mut op_mode = 0;
op_mode
.set_bit(7, false) // FSK mode
.set_bits(5..6, 0x00) // FSK modulation
.set_bit(3, false) //Low freq registers
.set_bits(0..2, 0b011); // Mode
self.write_register(Register::RegOpMode as u8, op_mode).await
}
pub async fn set_fsk_pa_ramp(
&mut self,
modulation_shaping: FskDataModulationShaping,
ramp: FskRampUpRamDown,
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let mut pa_ramp = 0;
pa_ramp
.set_bits(5..6, modulation_shaping as u8)
.set_bits(0..3, ramp as u8);
self.write_register(Register::RegPaRamp as u8, pa_ramp).await
}
pub async fn set_lora_pa_ramp(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegPaRamp as u8, 0b1000).await
}
pub async fn set_lora_sync_word(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegSyncWord as u8, 0x34).await
}
}
/// Modes of the radio and their corresponding register values.
#[derive(Clone, Copy)]
pub enum RadioMode {
LongRangeMode = 0x80,
Sleep = 0x00,
Stdby = 0x01,
Tx = 0x03,
RxContinuous = 0x05,
RxSingle = 0x06,
}
impl RadioMode {
/// Returns the address of the mode.
pub fn addr(self) -> u8 {
self as u8
}
}

View File

@ -1,107 +0,0 @@
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
// license
//
// Modifications made to make the driver work with the rust-lorawan link layer.
#![allow(dead_code, clippy::enum_variant_names)]
#[derive(Clone, Copy)]
pub enum Register {
RegFifo = 0x00,
RegOpMode = 0x01,
RegFrfMsb = 0x06,
RegFrfMid = 0x07,
RegFrfLsb = 0x08,
RegPaConfig = 0x09,
RegPaRamp = 0x0a,
RegOcp = 0x0b,
RegLna = 0x0c,
RegFifoAddrPtr = 0x0d,
RegFifoTxBaseAddr = 0x0e,
RegFifoRxBaseAddr = 0x0f,
RegFifoRxCurrentAddr = 0x10,
RegIrqFlagsMask = 0x11,
RegIrqFlags = 0x12,
RegRxNbBytes = 0x13,
RegPktSnrValue = 0x19,
RegModemStat = 0x18,
RegPktRssiValue = 0x1a,
RegModemConfig1 = 0x1d,
RegModemConfig2 = 0x1e,
RegSymbTimeoutLsb = 0x1f,
RegPreambleMsb = 0x20,
RegPreambleLsb = 0x21,
RegPayloadLength = 0x22,
RegMaxPayloadLength = 0x23,
RegModemConfig3 = 0x26,
RegFreqErrorMsb = 0x28,
RegFreqErrorMid = 0x29,
RegFreqErrorLsb = 0x2a,
RegRssiWideband = 0x2c,
RegDetectionOptimize = 0x31,
RegInvertiq = 0x33,
RegDetectionThreshold = 0x37,
RegSyncWord = 0x39,
RegInvertiq2 = 0x3b,
RegDioMapping1 = 0x40,
RegVersion = 0x42,
RegTcxo = 0x4b,
RegPaDac = 0x4d,
}
#[derive(Clone, Copy)]
pub enum PaConfig {
PaBoost = 0x80,
PaOutputRfoPin = 0,
}
#[derive(Clone, Copy)]
pub enum IRQ {
IrqTxDoneMask = 0x08,
IrqPayloadCrcErrorMask = 0x20,
IrqRxDoneMask = 0x40,
}
impl Register {
pub fn addr(self) -> u8 {
self as u8
}
}
impl PaConfig {
pub fn addr(self) -> u8 {
self as u8
}
}
impl IRQ {
pub fn addr(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum FskDataModulationShaping {
None = 1,
GaussianBt1d0 = 2,
GaussianBt0d5 = 10,
GaussianBt0d3 = 11,
}
#[derive(Clone, Copy)]
pub enum FskRampUpRamDown {
_3d4ms = 0b000,
_2ms = 0b0001,
_1ms = 0b0010,
_500us = 0b0011,
_250us = 0b0100,
_125us = 0b0101,
_100us = 0b0110,
_62us = 0b0111,
_50us = 0b1000,
_40us = 0b1001,
_31us = 0b1010,
_25us = 0b1011,
_20us = 0b1100,
_15us = 0b1101,
_12us = 0b1110,
_10us = 0b1111,
}

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-macros"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"

View File

@ -6,7 +6,10 @@ pub fn run(name: syn::Ident) -> Result<TokenStream, TokenStream> {
let name_interrupt = format_ident!("{}", name);
let name_handler = format!("__EMBASSY_{}_HANDLER", name);
let doc = format!("{} interrupt singleton.", name);
let result = quote! {
#[doc = #doc]
#[allow(non_camel_case_types)]
pub struct #name_interrupt(());
unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt {
@ -18,9 +21,9 @@ pub fn run(name: syn::Ident) -> Result<TokenStream, TokenStream> {
unsafe fn steal() -> Self {
Self(())
}
unsafe fn __handler(&self) -> &'static ::embassy_cortex_m::interrupt::Handler {
unsafe fn __handler(&self) -> &'static ::embassy_cortex_m::interrupt::DynHandler {
#[export_name = #name_handler]
static HANDLER: ::embassy_cortex_m::interrupt::Handler = ::embassy_cortex_m::interrupt::Handler::new();
static HANDLER: ::embassy_cortex_m::interrupt::DynHandler = ::embassy_cortex_m::interrupt::DynHandler::new();
&HANDLER
}
}

View File

@ -10,12 +10,12 @@ pub fn run(name: syn::Ident) -> Result<TokenStream, TokenStream> {
let (isr_enter, isr_exit) = (
quote! {
::embassy_executor::rtos_trace_interrupt! {
::embassy_executor::export::trace::isr_enter();
::embassy_executor::_export::trace::isr_enter();
}
},
quote! {
::embassy_executor::rtos_trace_interrupt! {
::embassy_executor::export::trace::isr_exit();
::embassy_executor::_export::trace::isr_exit();
}
},
);
@ -30,7 +30,7 @@ pub fn run(name: syn::Ident) -> Result<TokenStream, TokenStream> {
pub unsafe extern "C" fn trampoline() {
extern "C" {
#[link_name = #name_handler]
static HANDLER: interrupt::Handler;
static HANDLER: interrupt::DynHandler;
}
let func = HANDLER.func.load(interrupt::_export::atomic::Ordering::Relaxed);

View File

@ -1,6 +1,7 @@
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{ReturnType, Type};
use crate::util::ctxt::Ctxt;
@ -76,6 +77,26 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn, main: TokenStream) -> Resul
if !f.sig.generics.params.is_empty() {
ctxt.error_spanned_by(&f.sig, "main function must not be generic");
}
if !f.sig.generics.where_clause.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
}
if !f.sig.abi.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
}
if !f.sig.variadic.is_none() {
ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
}
match &f.sig.output {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => ctxt.error_spanned_by(
&f.sig,
"main function must either not return a value, return `()` or return `!`",
),
},
}
if fargs.len() != 1 {
ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
@ -84,10 +105,11 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn, main: TokenStream) -> Resul
ctxt.check()?;
let f_body = f.block;
let out = &f.sig.output;
let result = quote! {
#[::embassy_executor::task()]
async fn __embassy_main(#fargs) {
async fn __embassy_main(#fargs) #out {
#f_body
}

View File

@ -1,6 +1,7 @@
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_quote, ItemFn, ReturnType, Type};
use crate::util::ctxt::Ctxt;
@ -23,6 +24,27 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
if !f.sig.generics.params.is_empty() {
ctxt.error_spanned_by(&f.sig, "task functions must not be generic");
}
if !f.sig.generics.where_clause.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses");
}
if !f.sig.abi.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier");
}
if !f.sig.variadic.is_none() {
ctxt.error_spanned_by(&f.sig, "task functions must not be variadic");
}
match &f.sig.output {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => ctxt.error_spanned_by(
&f.sig,
"task functions must either not return a value, return `()` or return `!`",
),
},
}
if pool_size < 1 {
ctxt.error_spanned_by(&f.sig, "pool_size must be 1 or greater");
}
@ -57,13 +79,7 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
task_inner.vis = syn::Visibility::Inherited;
task_inner.sig.ident = task_inner_ident.clone();
let result = quote! {
// This is the user's task function, renamed.
// We put it outside the #task_ident fn below, because otherwise
// the items defined there (such as POOL) would be in scope
// in the user's code.
#task_inner
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
type Fut = impl ::core::future::Future + 'static;
static POOL: ::embassy_executor::raw::TaskPool<Fut, #pool_size> = ::embassy_executor::raw::TaskPool::new();
@ -71,5 +87,18 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
}
};
task_outer.attrs.append(&mut task_inner.attrs.clone());
let result = quote! {
// This is the user's task function, renamed.
// We put it outside the #task_ident fn below, because otherwise
// the items defined there (such as POOL) would be in scope
// in the user's code.
#[doc(hidden)]
#task_inner
#task_outer
};
Ok(result)
}

View File

@ -0,0 +1,18 @@
[package]
name = "embassy-net-driver-channel"
version = "0.1.0"
edition = "2021"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/"
features = ["defmt"]
target = "thumbv7em-none-eabi"
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }

View File

@ -0,0 +1,225 @@
#![macro_use]
#![allow(unused_macros)]
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}

View File

@ -0,0 +1,549 @@
#![no_std]
// must go first!
mod fmt;
use core::cell::RefCell;
use core::mem::MaybeUninit;
use core::task::{Context, Poll};
pub use embassy_net_driver as driver;
use embassy_net_driver::{Capabilities, LinkState, Medium};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration;
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
rx: [PacketBuf<MTU>; N_RX],
tx: [PacketBuf<MTU>; N_TX],
inner: MaybeUninit<StateInner<'static, MTU>>,
}
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
const NEW_PACKET: PacketBuf<MTU> = PacketBuf::new();
pub const fn new() -> Self {
Self {
rx: [Self::NEW_PACKET; N_RX],
tx: [Self::NEW_PACKET; N_TX],
inner: MaybeUninit::uninit(),
}
}
}
struct StateInner<'d, const MTU: usize> {
rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf<MTU>>,
tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf<MTU>>,
shared: Mutex<NoopRawMutex, RefCell<Shared>>,
}
/// State of the LinkState
struct Shared {
link_state: LinkState,
waker: WakerRegistration,
ethernet_address: [u8; 6],
}
pub struct Runner<'d, const MTU: usize> {
tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf<MTU>>,
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
shared: &'d Mutex<NoopRawMutex, RefCell<Shared>>,
}
#[derive(Clone, Copy)]
pub struct StateRunner<'d> {
shared: &'d Mutex<NoopRawMutex, RefCell<Shared>>,
}
pub struct RxRunner<'d, const MTU: usize> {
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
}
pub struct TxRunner<'d, const MTU: usize> {
tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf<MTU>>,
}
impl<'d, const MTU: usize> Runner<'d, MTU> {
pub fn split(self) -> (StateRunner<'d>, RxRunner<'d, MTU>, TxRunner<'d, MTU>) {
(
StateRunner { shared: self.shared },
RxRunner { rx_chan: self.rx_chan },
TxRunner { tx_chan: self.tx_chan },
)
}
pub fn state_runner(&self) -> StateRunner<'d> {
StateRunner { shared: self.shared }
}
pub fn set_link_state(&mut self, state: LinkState) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.link_state = state;
s.waker.wake();
});
}
pub fn set_ethernet_address(&mut self, address: [u8; 6]) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.ethernet_address = address;
s.waker.wake();
});
}
pub async fn rx_buf(&mut self) -> &mut [u8] {
let p = self.rx_chan.send().await;
&mut p.buf
}
pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.rx_chan.try_send()?;
Some(&mut p.buf)
}
pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.rx_chan.poll_send(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf),
Poll::Pending => Poll::Pending,
}
}
pub fn rx_done(&mut self, len: usize) {
let p = self.rx_chan.try_send().unwrap();
p.len = len;
self.rx_chan.send_done();
}
pub async fn tx_buf(&mut self) -> &mut [u8] {
let p = self.tx_chan.recv().await;
&mut p.buf[..p.len]
}
pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.tx_chan.try_recv()?;
Some(&mut p.buf[..p.len])
}
pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.tx_chan.poll_recv(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]),
Poll::Pending => Poll::Pending,
}
}
pub fn tx_done(&mut self) {
self.tx_chan.recv_done();
}
}
impl<'d> StateRunner<'d> {
pub fn set_link_state(&self, state: LinkState) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.link_state = state;
s.waker.wake();
});
}
pub fn set_ethernet_address(&self, address: [u8; 6]) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.ethernet_address = address;
s.waker.wake();
});
}
}
impl<'d, const MTU: usize> RxRunner<'d, MTU> {
pub async fn rx_buf(&mut self) -> &mut [u8] {
let p = self.rx_chan.send().await;
&mut p.buf
}
pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.rx_chan.try_send()?;
Some(&mut p.buf)
}
pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.rx_chan.poll_send(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf),
Poll::Pending => Poll::Pending,
}
}
pub fn rx_done(&mut self, len: usize) {
let p = self.rx_chan.try_send().unwrap();
p.len = len;
self.rx_chan.send_done();
}
}
impl<'d, const MTU: usize> TxRunner<'d, MTU> {
pub async fn tx_buf(&mut self) -> &mut [u8] {
let p = self.tx_chan.recv().await;
&mut p.buf[..p.len]
}
pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.tx_chan.try_recv()?;
Some(&mut p.buf[..p.len])
}
pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.tx_chan.poll_recv(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]),
Poll::Pending => Poll::Pending,
}
}
pub fn tx_done(&mut self) {
self.tx_chan.recv_done();
}
}
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],
) -> (Runner<'d, MTU>, Device<'d, MTU>) {
let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU;
caps.medium = Medium::Ethernet;
// safety: this is a self-referential struct, however:
// - it can't move while the `'d` borrow is active.
// - when the borrow ends, the dangling references inside the MaybeUninit will never be used again.
let state_uninit: *mut MaybeUninit<StateInner<'d, MTU>> =
(&mut state.inner as *mut MaybeUninit<StateInner<'static, MTU>>).cast();
let state = unsafe { &mut *state_uninit }.write(StateInner {
rx: zerocopy_channel::Channel::new(&mut state.rx[..]),
tx: zerocopy_channel::Channel::new(&mut state.tx[..]),
shared: Mutex::new(RefCell::new(Shared {
link_state: LinkState::Down,
ethernet_address,
waker: WakerRegistration::new(),
})),
});
let (rx_sender, rx_receiver) = state.rx.split();
let (tx_sender, tx_receiver) = state.tx.split();
(
Runner {
tx_chan: tx_receiver,
rx_chan: rx_sender,
shared: &state.shared,
},
Device {
caps,
shared: &state.shared,
rx: rx_receiver,
tx: tx_sender,
},
)
}
pub struct PacketBuf<const MTU: usize> {
len: usize,
buf: [u8; MTU],
}
impl<const MTU: usize> PacketBuf<MTU> {
pub const fn new() -> Self {
Self { len: 0, buf: [0; MTU] }
}
}
pub struct Device<'d, const MTU: usize> {
rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf<MTU>>,
tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
shared: &'d Mutex<NoopRawMutex, RefCell<Shared>>,
caps: Capabilities,
}
impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ;
type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() {
Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() }))
} else {
None
}
}
/// Construct a transmit token.
fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>> {
if self.tx.poll_send(cx).is_ready() {
Some(TxToken { tx: self.tx.borrow() })
} else {
None
}
}
/// Get a description of device capabilities.
fn capabilities(&self) -> Capabilities {
self.caps.clone()
}
fn ethernet_address(&self) -> [u8; 6] {
self.shared.lock(|s| s.borrow().ethernet_address)
}
fn link_state(&mut self, cx: &mut Context) -> LinkState {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.waker.register(cx.waker());
s.link_state
})
}
}
pub struct RxToken<'a, const MTU: usize> {
rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf<MTU>>,
}
impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
// NOTE(unwrap): we checked the queue wasn't full when creating the token.
let pkt = unwrap!(self.rx.try_recv());
let r = f(&mut pkt.buf[..pkt.len]);
self.rx.recv_done();
r
}
}
pub struct TxToken<'a, const MTU: usize> {
tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf<MTU>>,
}
impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> {
fn consume<R, F>(mut self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
// NOTE(unwrap): we checked the queue wasn't full when creating the token.
let pkt = unwrap!(self.tx.try_send());
let r = f(&mut pkt.buf[..len]);
pkt.len = len;
self.tx.send_done();
r
}
}
mod zerocopy_channel {
use core::cell::RefCell;
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::{Context, Poll};
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration;
pub struct Channel<'a, M: RawMutex, T> {
buf: *mut T,
phantom: PhantomData<&'a mut T>,
state: Mutex<M, RefCell<State>>,
}
impl<'a, M: RawMutex, T> Channel<'a, M, T> {
pub fn new(buf: &'a mut [T]) -> Self {
let len = buf.len();
assert!(len != 0);
Self {
buf: buf.as_mut_ptr(),
phantom: PhantomData,
state: Mutex::new(RefCell::new(State {
len,
front: 0,
back: 0,
full: false,
send_waker: WakerRegistration::new(),
recv_waker: WakerRegistration::new(),
})),
}
}
pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) {
(Sender { channel: self }, Receiver { channel: self })
}
}
pub struct Sender<'a, M: RawMutex, T> {
channel: &'a Channel<'a, M, T>,
}
impl<'a, M: RawMutex, T> Sender<'a, M, T> {
pub fn borrow(&mut self) -> Sender<'_, M, T> {
Sender { channel: self.channel }
}
pub fn try_send(&mut self) -> Option<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }),
None => None,
}
})
}
pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }),
None => {
s.recv_waker.register(cx.waker());
Poll::Pending
}
}
})
}
pub async fn send(&mut self) -> &mut T {
let i = poll_fn(|cx| {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Poll::Ready(i),
None => {
s.recv_waker.register(cx.waker());
Poll::Pending
}
}
})
})
.await;
unsafe { &mut *self.channel.buf.add(i) }
}
pub fn send_done(&mut self) {
self.channel.state.lock(|s| s.borrow_mut().push_done())
}
}
pub struct Receiver<'a, M: RawMutex, T> {
channel: &'a Channel<'a, M, T>,
}
impl<'a, M: RawMutex, T> Receiver<'a, M, T> {
pub fn borrow(&mut self) -> Receiver<'_, M, T> {
Receiver { channel: self.channel }
}
pub fn try_recv(&mut self) -> Option<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }),
None => None,
}
})
}
pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }),
None => {
s.send_waker.register(cx.waker());
Poll::Pending
}
}
})
}
pub async fn recv(&mut self) -> &mut T {
let i = poll_fn(|cx| {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Poll::Ready(i),
None => {
s.send_waker.register(cx.waker());
Poll::Pending
}
}
})
})
.await;
unsafe { &mut *self.channel.buf.add(i) }
}
pub fn recv_done(&mut self) {
self.channel.state.lock(|s| s.borrow_mut().pop_done())
}
}
struct State {
len: usize,
/// Front index. Always 0..=(N-1)
front: usize,
/// Back index. Always 0..=(N-1).
back: usize,
/// Used to distinguish "empty" and "full" cases when `front == back`.
/// May only be `true` if `front == back`, always `false` otherwise.
full: bool,
send_waker: WakerRegistration,
recv_waker: WakerRegistration,
}
impl State {
fn increment(&self, i: usize) -> usize {
if i + 1 == self.len {
0
} else {
i + 1
}
}
fn is_full(&self) -> bool {
self.full
}
fn is_empty(&self) -> bool {
self.front == self.back && !self.full
}
fn push_index(&mut self) -> Option<usize> {
match self.is_full() {
true => None,
false => Some(self.back),
}
}
fn push_done(&mut self) {
assert!(!self.is_full());
self.back = self.increment(self.back);
if self.back == self.front {
self.full = true;
}
self.send_waker.wake();
}
fn pop_index(&mut self) -> Option<usize> {
match self.is_empty() {
true => None,
false => Some(self.front),
}
}
fn pop_done(&mut self) {
assert!(!self.is_empty());
self.front = self.increment(self.front);
self.full = false;
self.recv_waker.wake();
}
}
}

View File

@ -0,0 +1,15 @@
[package]
name = "embassy-net-driver"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/"
features = ["defmt"]
target = "thumbv7em-none-eabi"
[dependencies]
defmt = { version = "0.3", optional = true }

View File

@ -0,0 +1,175 @@
#![no_std]
use core::task::Context;
pub trait Driver {
type RxToken<'a>: RxToken
where
Self: 'a;
type TxToken<'a>: TxToken
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;
fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>>;
fn link_state(&mut self, cx: &mut Context) -> LinkState;
fn capabilities(&self) -> Capabilities;
fn ethernet_address(&self) -> [u8; 6];
}
impl<T: ?Sized + Driver> Driver for &mut T {
type RxToken<'a> = T::RxToken<'a>
where
Self: 'a;
type TxToken<'a> = T::TxToken<'a>
where
Self: 'a;
fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>> {
T::transmit(self, cx)
}
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
T::receive(self, cx)
}
fn capabilities(&self) -> Capabilities {
T::capabilities(self)
}
fn link_state(&mut self, cx: &mut Context) -> LinkState {
T::link_state(self, cx)
}
fn ethernet_address(&self) -> [u8; 6] {
T::ethernet_address(self)
}
}
/// A token to receive a single network packet.
pub trait RxToken {
/// Consumes the token to receive a single network packet.
///
/// This method receives a packet and then calls the given closure `f` with the raw
/// packet bytes as argument.
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R;
}
/// A token to transmit a single network packet.
pub trait TxToken {
/// Consumes the token to send a single network packet.
///
/// This method constructs a transmit buffer of size `len` and calls the passed
/// closure `f` with a mutable reference to that buffer. The closure should construct
/// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure
/// returns, the transmit buffer is sent out.
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R;
}
/// A description of device capabilities.
///
/// Higher-level protocols may achieve higher throughput or lower latency if they consider
/// the bandwidth or packet size limitations.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Capabilities {
/// Medium of the device.
///
/// This indicates what kind of packet the sent/received bytes are, and determines
/// some behaviors of Interface. For example, ARP/NDISC address resolution is only done
/// for Ethernet mediums.
pub medium: Medium,
/// Maximum transmission unit.
///
/// The network device is unable to send or receive frames larger than the value returned
/// by this function.
///
/// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but
/// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14.
///
/// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet
/// devices. This is a common source of confusion.
///
/// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets.
pub max_transmission_unit: usize,
/// Maximum burst size, in terms of MTU.
///
/// The network device is unable to send or receive bursts large than the value returned
/// by this function.
///
/// If `None`, there is no fixed limit on burst size, e.g. if network buffers are
/// dynamically allocated.
pub max_burst_size: Option<usize>,
/// Checksum behavior.
///
/// If the network device is capable of verifying or computing checksums for some protocols,
/// it can request that the stack not do so in software to improve performance.
pub checksum: ChecksumCapabilities,
}
/// Type of medium of a device.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Medium {
/// Ethernet medium. Devices of this type send and receive Ethernet frames,
/// and interfaces using it must do neighbor discovery via ARP or NDISC.
///
/// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
Ethernet,
/// IP medium. Devices of this type send and receive IP frames, without an
/// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
///
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
Ip,
}
impl Default for Medium {
fn default() -> Medium {
Medium::Ethernet
}
}
/// A description of checksum behavior for every supported protocol.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct ChecksumCapabilities {
pub ipv4: Checksum,
pub udp: Checksum,
pub tcp: Checksum,
pub icmpv4: Checksum,
pub icmpv6: Checksum,
}
/// A description of checksum behavior for a particular protocol.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Checksum {
/// Verify checksum when receiving and compute checksum when sending.
Both,
/// Verify checksum when receiving.
Rx,
/// Compute checksum before sending.
Tx,
/// Ignore checksum completely.
None,
}
impl Default for Checksum {
fn default() -> Checksum {
Checksum::Both
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LinkState {
Down,
Up,
}

View File

@ -8,41 +8,43 @@ license = "MIT OR Apache-2.0"
[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 = ["pool-4", "defmt", "tcp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"]
features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"]
target = "thumbv7em-none-eabi"
[features]
default = []
std = []
defmt = ["dep:defmt", "smoltcp/defmt"]
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"]
nightly = ["dep:embedded-io", "embedded-io?/async", "dep:embedded-nal-async"]
unstable-traits = []
udp = ["smoltcp/socket-udp"]
tcp = ["smoltcp/socket-tcp"]
dns = ["smoltcp/socket-dns"]
dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"]
dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"]
proto-ipv6 = ["smoltcp/proto-ipv6"]
medium-ethernet = ["smoltcp/medium-ethernet"]
medium-ip = ["smoltcp/medium-ip"]
pool-4 = []
pool-8 = []
pool-16 = []
pool-32 = []
pool-64 = []
pool-128 = []
igmp = ["smoltcp/proto-igmp"]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
smoltcp = { version = "0.9.0", default-features = false, features = [
"proto-ipv4",
"socket",
"async",
]}
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-sync = { version = "0.1.0", path = "../embassy-sync" }
embedded-io = { version = "0.3.1", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embedded-io = { version = "0.4.0", optional = true }
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
heapless = { version = "0.7.5", default-features = false }
@ -51,16 +53,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"
atomic-polyfill = "1.0.1"
embedded-nal-async = { version = "0.2.0", optional = true }
[dependencies.smoltcp]
version = "0.8.0"
git = "https://github.com/smoltcp-rs/smoltcp"
rev = "ed0cf16750a42f30e31fcaf5347915592924b1e3"
default-features = false
features = [
"proto-ipv4",
"socket",
"async",
]
embedded-nal-async = { version = "0.4.0", optional = true }
atomic-polyfill = { version = "1.0" }

View File

@ -17,11 +17,13 @@ sudo ip -6 route add fe80::/64 dev tap0
sudo ip -6 route add fdaa::/64 dev tap0
```
Second, have something listening there. For example `nc -l 8000`
Then run the example located in the `examples` folder:
```sh
cd $EMBASSY_ROOT/examples/std/
cargo run --bin net
cargo run --bin net -- --static-ip
```
## License

View File

@ -1,129 +1,102 @@
use core::task::Waker;
use core::task::Context;
use smoltcp::phy::{Device as SmolDevice, DeviceCapabilities};
use smoltcp::time::Instant as SmolInstant;
use embassy_net_driver::{Capabilities, Checksum, Driver, Medium, RxToken, TxToken};
use smoltcp::phy;
use smoltcp::time::Instant;
use crate::packet_pool::PacketBoxExt;
use crate::{Packet, PacketBox, PacketBuf};
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum LinkState {
Down,
Up,
pub(crate) struct DriverAdapter<'d, 'c, T>
where
T: Driver,
{
// must be Some when actually using this to rx/tx
pub cx: Option<&'d mut Context<'c>>,
pub inner: &'d mut T,
}
// 'static required due to the "fake GAT" in smoltcp::phy::Device.
// https://github.com/smoltcp-rs/smoltcp/pull/572
pub trait Device {
fn is_transmit_ready(&mut self) -> bool;
fn transmit(&mut self, pkt: PacketBuf);
fn receive(&mut self) -> Option<PacketBuf>;
impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
where
T: Driver,
{
type RxToken<'a> = RxTokenAdapter<T::RxToken<'a>> where Self: 'a;
type TxToken<'a> = TxTokenAdapter<T::TxToken<'a>> where Self: 'a;
fn register_waker(&mut self, waker: &Waker);
fn capabilities(&self) -> DeviceCapabilities;
fn link_state(&mut self) -> LinkState;
fn ethernet_address(&self) -> [u8; 6];
}
impl<T: ?Sized + Device> Device for &'static mut T {
fn is_transmit_ready(&mut self) -> bool {
T::is_transmit_ready(self)
}
fn transmit(&mut self, pkt: PacketBuf) {
T::transmit(self, pkt)
}
fn receive(&mut self) -> Option<PacketBuf> {
T::receive(self)
}
fn register_waker(&mut self, waker: &Waker) {
T::register_waker(self, waker)
}
fn capabilities(&self) -> DeviceCapabilities {
T::capabilities(self)
}
fn link_state(&mut self) -> LinkState {
T::link_state(self)
}
fn ethernet_address(&self) -> [u8; 6] {
T::ethernet_address(self)
}
}
pub struct DeviceAdapter<D: Device> {
pub device: D,
caps: DeviceCapabilities,
}
impl<D: Device> DeviceAdapter<D> {
pub(crate) fn new(device: D) -> Self {
Self {
caps: device.capabilities(),
device,
}
}
}
impl<'a, D: Device + 'static> SmolDevice<'a> for DeviceAdapter<D> {
type RxToken = RxToken;
type TxToken = TxToken<'a, D>;
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
let tx_pkt = PacketBox::new(Packet::new())?;
let rx_pkt = self.device.receive()?;
let rx_token = RxToken { pkt: rx_pkt };
let tx_token = TxToken {
device: &mut self.device,
pkt: tx_pkt,
};
Some((rx_token, tx_token))
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.inner
.receive(self.cx.as_deref_mut().unwrap())
.map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx)))
}
/// Construct a transmit token.
fn transmit(&'a mut self) -> Option<Self::TxToken> {
if !self.device.is_transmit_ready() {
return None;
}
let tx_pkt = PacketBox::new(Packet::new())?;
Some(TxToken {
device: &mut self.device,
pkt: tx_pkt,
})
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
self.inner.transmit(self.cx.as_deref_mut().unwrap()).map(TxTokenAdapter)
}
/// Get a description of device capabilities.
fn capabilities(&self) -> DeviceCapabilities {
self.caps.clone()
fn capabilities(&self) -> phy::DeviceCapabilities {
fn convert(c: Checksum) -> phy::Checksum {
match c {
Checksum::Both => phy::Checksum::Both,
Checksum::Tx => phy::Checksum::Tx,
Checksum::Rx => phy::Checksum::Rx,
Checksum::None => phy::Checksum::None,
}
}
let caps: Capabilities = self.inner.capabilities();
let mut smolcaps = phy::DeviceCapabilities::default();
smolcaps.max_transmission_unit = caps.max_transmission_unit;
smolcaps.max_burst_size = caps.max_burst_size;
smolcaps.medium = match caps.medium {
#[cfg(feature = "medium-ethernet")]
Medium::Ethernet => phy::Medium::Ethernet,
#[cfg(feature = "medium-ip")]
Medium::Ip => phy::Medium::Ip,
_ => panic!(
"Unsupported medium {:?}. MAke sure to enable it in embassy-net's Cargo features.",
caps.medium
),
};
smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4);
smolcaps.checksum.tcp = convert(caps.checksum.tcp);
smolcaps.checksum.udp = convert(caps.checksum.udp);
smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4);
#[cfg(feature = "proto-ipv6")]
{
smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6);
}
smolcaps
}
}
pub struct RxToken {
pkt: PacketBuf,
}
pub(crate) struct RxTokenAdapter<T>(T)
where
T: RxToken;
impl smoltcp::phy::RxToken for RxToken {
fn consume<R, F>(mut self, _timestamp: SmolInstant, f: F) -> smoltcp::Result<R>
impl<T> phy::RxToken for RxTokenAdapter<T>
where
T: RxToken,
{
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
F: FnOnce(&mut [u8]) -> R,
{
f(&mut self.pkt)
self.0.consume(|buf| f(buf))
}
}
pub struct TxToken<'a, D: Device> {
device: &'a mut D,
pkt: PacketBox,
}
pub(crate) struct TxTokenAdapter<T>(T)
where
T: TxToken;
impl<'a, D: Device> smoltcp::phy::TxToken for TxToken<'a, D> {
fn consume<R, F>(self, _timestamp: SmolInstant, len: usize, f: F) -> smoltcp::Result<R>
impl<T> phy::TxToken for TxTokenAdapter<T>
where
T: TxToken,
{
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
F: FnOnce(&mut [u8]) -> R,
{
let mut buf = self.pkt.slice(0..len);
let r = f(&mut buf)?;
self.device.transmit(buf);
Ok(r)
self.0.consume(len, |buf| f(buf))
}
}

97
embassy-net/src/dns.rs Normal file
View File

@ -0,0 +1,97 @@
//! DNS socket with async support.
use heapless::Vec;
pub use smoltcp::socket::dns::{DnsQuery, Socket};
pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError};
pub use smoltcp::wire::{DnsQueryType, IpAddress};
use crate::{Driver, Stack};
/// Errors returned by DnsSocket.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Invalid name
InvalidName,
/// Name too long
NameTooLong,
/// Name lookup failed
Failed,
}
impl From<GetQueryResultError> for Error {
fn from(_: GetQueryResultError) -> Self {
Self::Failed
}
}
impl From<StartQueryError> for Error {
fn from(e: StartQueryError) -> Self {
match e {
StartQueryError::NoFreeSlot => Self::Failed,
StartQueryError::InvalidName => Self::InvalidName,
StartQueryError::NameTooLong => Self::NameTooLong,
}
}
}
/// Async socket for making DNS queries.
pub struct DnsSocket<'a, D>
where
D: Driver + 'static,
{
stack: &'a Stack<D>,
}
impl<'a, D> DnsSocket<'a, D>
where
D: Driver + 'static,
{
/// Create a new DNS socket using the provided stack.
///
/// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated.
pub fn new(stack: &'a Stack<D>) -> Self {
Self { stack }
}
/// Make a query for a given name and return the corresponding IP addresses.
pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result<Vec<IpAddress, 1>, Error> {
self.stack.dns_query(name, qtype).await
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
where
D: Driver + 'static,
{
type Error = Error;
async fn get_host_by_name(
&self,
host: &str,
addr_type: embedded_nal_async::AddrType,
) -> Result<embedded_nal_async::IpAddr, Self::Error> {
use embedded_nal_async::{AddrType, IpAddr};
let qtype = match addr_type {
AddrType::IPv6 => DnsQueryType::Aaaa,
_ => DnsQueryType::A,
};
let addrs = self.query(host, qtype).await?;
if let Some(first) = addrs.get(0) {
Ok(match first {
IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()),
#[cfg(feature = "proto-ipv6")]
IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()),
})
} else {
Err(Error::Failed)
}
}
async fn get_host_by_address(
&self,
_addr: embedded_nal_async::IpAddr,
) -> Result<heapless::String<256>, Self::Error> {
todo!()
}
}

View File

@ -1,25 +1,38 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
#![cfg_attr(
feature = "nightly",
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections)
)]
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub use embassy_net_driver as driver;
mod device;
mod packet_pool;
mod stack;
pub use device::{Device, LinkState};
pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf, MTU};
pub use stack::{Config, ConfigStrategy, Stack, StackResources};
#[cfg(feature = "dns")]
pub mod dns;
#[cfg(feature = "tcp")]
pub mod tcp;
#[cfg(feature = "udp")]
pub mod udp;
use core::cell::RefCell;
use core::future::{poll_fn, Future};
use core::task::{Context, Poll};
use embassy_net_driver::{Driver, LinkState, Medium};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
use futures::pin_mut;
use heapless::Vec;
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4::{self, RetryConfig};
#[cfg(feature = "dhcpv4")]
use smoltcp::time::Duration;
// smoltcp reexports
pub use smoltcp::phy::{DeviceCapabilities, Medium};
pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant};
#[cfg(feature = "medium-ethernet")]
pub use smoltcp::wire::{EthernetAddress, HardwareAddress};
@ -28,3 +41,438 @@ pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
#[cfg(feature = "udp")]
pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint};
use crate::device::DriverAdapter;
const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535;
#[cfg(feature = "dns")]
const MAX_QUERIES: usize = 4;
pub struct StackResources<const SOCK: usize> {
sockets: [SocketStorage<'static>; SOCK],
#[cfg(feature = "dns")]
queries: [Option<dns::DnsQuery>; MAX_QUERIES],
}
impl<const SOCK: usize> StackResources<SOCK> {
pub fn new() -> Self {
#[cfg(feature = "dns")]
const INIT: Option<dns::DnsQuery> = None;
Self {
sockets: [SocketStorage::EMPTY; SOCK],
#[cfg(feature = "dns")]
queries: [INIT; MAX_QUERIES],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticConfig {
pub address: Ipv4Cidr,
pub gateway: Option<Ipv4Address>,
pub dns_servers: Vec<Ipv4Address, 3>,
}
#[cfg(feature = "dhcpv4")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DhcpConfig {
pub max_lease_duration: Option<Duration>,
pub retry_config: RetryConfig,
/// Ignore NAKs.
pub ignore_naks: bool,
/// Server port config
pub server_port: u16,
/// Client port config
pub client_port: u16,
}
#[cfg(feature = "dhcpv4")]
impl Default for DhcpConfig {
fn default() -> Self {
Self {
max_lease_duration: Default::default(),
retry_config: Default::default(),
ignore_naks: Default::default(),
server_port: smoltcp::wire::DHCP_SERVER_PORT,
client_port: smoltcp::wire::DHCP_CLIENT_PORT,
}
}
}
pub enum Config {
Static(StaticConfig),
#[cfg(feature = "dhcpv4")]
Dhcp(DhcpConfig),
}
pub struct Stack<D: Driver> {
pub(crate) socket: RefCell<SocketStack>,
inner: RefCell<Inner<D>>,
}
struct Inner<D: Driver> {
device: D,
link_up: bool,
config: Option<StaticConfig>,
#[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>,
#[cfg(feature = "dns")]
dns_socket: SocketHandle,
#[cfg(feature = "dns")]
dns_waker: WakerRegistration,
}
pub(crate) struct SocketStack {
pub(crate) sockets: SocketSet<'static>,
pub(crate) iface: Interface,
pub(crate) waker: WakerRegistration,
next_local_port: u16,
}
impl<D: Driver + 'static> Stack<D> {
pub fn new<const SOCK: usize>(
mut device: D,
config: Config,
resources: &'static mut StackResources<SOCK>,
random_seed: u64,
) -> Self {
#[cfg(feature = "medium-ethernet")]
let medium = device.capabilities().medium;
let mut iface_cfg = smoltcp::iface::Config::new();
iface_cfg.random_seed = random_seed;
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
iface_cfg.hardware_addr = Some(HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())));
}
let iface = Interface::new(
iface_cfg,
&mut DriverAdapter {
inner: &mut device,
cx: None,
},
);
let sockets = SocketSet::new(&mut resources.sockets[..]);
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
let mut socket = SocketStack {
sockets,
iface,
waker: WakerRegistration::new(),
next_local_port,
};
let mut inner = Inner {
device,
link_up: false,
config: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
#[cfg(feature = "dns")]
dns_socket: socket.sockets.add(dns::Socket::new(
&[],
managed::ManagedSlice::Borrowed(&mut resources.queries),
)),
#[cfg(feature = "dns")]
dns_waker: WakerRegistration::new(),
};
match config {
Config::Static(config) => {
inner.apply_config(&mut socket, config);
}
#[cfg(feature = "dhcpv4")]
Config::Dhcp(config) => {
let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new();
inner.apply_dhcp_config(&mut dhcp_socket, config);
let handle = socket.sockets.add(dhcp_socket);
inner.dhcp_socket = Some(handle);
}
}
Self {
socket: RefCell::new(socket),
inner: RefCell::new(inner),
}
}
fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
f(&*self.socket.borrow(), &*self.inner.borrow())
}
fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
}
pub fn ethernet_address(&self) -> [u8; 6] {
self.with(|_s, i| i.device.ethernet_address())
}
pub fn is_link_up(&self) -> bool {
self.with(|_s, i| i.link_up)
}
pub fn is_config_up(&self) -> bool {
self.with(|_s, i| i.config.is_some())
}
pub fn config(&self) -> Option<StaticConfig> {
self.with(|_s, i| i.config.clone())
}
pub async fn run(&self) -> ! {
poll_fn(|cx| {
self.with_mut(|s, i| i.poll(cx, s));
Poll::<()>::Pending
})
.await;
unreachable!()
}
/// Make a query for a given name and return the corresponding IP addresses.
#[cfg(feature = "dns")]
pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result<Vec<IpAddress, 1>, dns::Error> {
// For A and AAAA queries we try detect whether `name` is just an IP address
match qtype {
dns::DnsQueryType::A => {
if let Ok(ip) = name.parse().map(IpAddress::Ipv4) {
return Ok([ip].into_iter().collect());
}
}
#[cfg(feature = "proto-ipv6")]
dns::DnsQueryType::Aaaa => {
if let Ok(ip) = name.parse().map(IpAddress::Ipv6) {
return Ok([ip].into_iter().collect());
}
}
_ => {}
}
let query = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.start_query(s.iface.context(), name, qtype) {
Ok(handle) => Poll::Ready(Ok(handle)),
Err(dns::StartQueryError::NoFreeSlot) => {
i.dns_waker.register(cx.waker());
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
})
})
.await?;
use embassy_hal_common::drop::OnDrop;
let drop = OnDrop::new(|| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
socket.cancel_query(query);
s.waker.wake();
i.dns_waker.wake();
})
});
let res = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.get_query_result(query) {
Ok(addrs) => {
i.dns_waker.wake();
Poll::Ready(Ok(addrs))
}
Err(dns::GetQueryResultError::Pending) => {
socket.register_query_waker(query, cx.waker());
Poll::Pending
}
Err(e) => {
i.dns_waker.wake();
Poll::Ready(Err(e.into()))
}
}
})
})
.await;
drop.defuse();
res
}
}
#[cfg(feature = "igmp")]
impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> {
pub fn join_multicast_group<T>(&self, addr: T) -> Result<bool, smoltcp::iface::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()))
})
}
pub fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, smoltcp::iface::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()))
})
}
pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
self.socket.borrow().iface.has_multicast_group(addr)
}
}
impl SocketStack {
#[allow(clippy::absurd_extreme_comparisons, dead_code)]
pub fn get_local_port(&mut self) -> u16 {
let res = self.next_local_port;
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
res
}
}
impl<D: Driver + 'static> Inner<D> {
fn apply_config(&mut self, s: &mut SocketStack, config: StaticConfig) {
#[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);
}
});
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
} else {
debug!(" Default gateway: None");
s.iface.routes_mut().remove_default_ipv4_route();
}
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
#[cfg(feature = "dns")]
{
let socket = s.sockets.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket);
let servers: Vec<IpAddress, 3> = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect();
socket.update_servers(&servers[..]);
}
self.config = Some(config)
}
#[cfg(feature = "dhcpv4")]
fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) {
socket.set_ignore_naks(config.ignore_naks);
socket.set_max_lease_duration(config.max_lease_duration);
socket.set_ports(config.server_port, config.client_port);
socket.set_retry_config(config.retry_config);
}
#[allow(unused)] // used only with dhcp
fn unapply_config(&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());
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
s.iface.routes_mut().remove_default_ipv4_route();
}
self.config = None
}
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(),
)));
}
let timestamp = instant_to_smoltcp(Instant::now());
let mut smoldev = DriverAdapter {
cx: Some(cx),
inner: &mut self.device,
};
s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
// Update link up
let old_link_up = self.link_up;
self.link_up = self.device.link_state(cx) == LinkState::Up;
// Print when changed
if old_link_up != self.link_up {
info!("link_up = {:?}", self.link_up);
}
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up {
match socket.poll() {
None => {}
Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
Some(dhcpv4::Event::Configured(config)) => {
let config = StaticConfig {
address: config.address,
gateway: config.router,
dns_servers: config.dns_servers,
};
self.apply_config(s, config)
}
}
} else if old_link_up {
socket.reset();
self.unapply_config(s);
}
}
//if old_link_up || self.link_up {
// self.poll_configurator(timestamp)
//}
//
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
let t = Timer::at(instant_from_smoltcp(poll_at));
pin_mut!(t);
if t.poll(cx).is_ready() {
cx.waker().wake_by_ref();
}
}
}
}
fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
SmolInstant::from_millis(instant.as_millis() as i64)
}
fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
Instant::from_millis(instant.total_millis() as u64)
}

View File

@ -1,107 +0,0 @@
use core::ops::{Deref, DerefMut, Range};
use as_slice::{AsMutSlice, AsSlice};
use atomic_pool::{pool, Box};
pub const MTU: usize = 1516;
#[cfg(feature = "pool-4")]
pub const PACKET_POOL_SIZE: usize = 4;
#[cfg(feature = "pool-8")]
pub const PACKET_POOL_SIZE: usize = 8;
#[cfg(feature = "pool-16")]
pub const PACKET_POOL_SIZE: usize = 16;
#[cfg(feature = "pool-32")]
pub const PACKET_POOL_SIZE: usize = 32;
#[cfg(feature = "pool-64")]
pub const PACKET_POOL_SIZE: usize = 64;
#[cfg(feature = "pool-128")]
pub const PACKET_POOL_SIZE: usize = 128;
pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]);
pub type PacketBox = Box<PacketPool>;
#[repr(align(4))]
pub struct Packet(pub [u8; MTU]);
impl Packet {
pub const fn new() -> Self {
Self([0; MTU])
}
}
pub trait PacketBoxExt {
fn slice(self, range: Range<usize>) -> PacketBuf;
}
impl PacketBoxExt for PacketBox {
fn slice(self, range: Range<usize>) -> PacketBuf {
PacketBuf { packet: self, range }
}
}
impl AsSlice for Packet {
type Element = u8;
fn as_slice(&self) -> &[Self::Element] {
&self.deref()[..]
}
}
impl AsMutSlice for Packet {
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
&mut self.deref_mut()[..]
}
}
impl Deref for Packet {
type Target = [u8; MTU];
fn deref(&self) -> &[u8; MTU] {
&self.0
}
}
impl DerefMut for Packet {
fn deref_mut(&mut self) -> &mut [u8; MTU] {
&mut self.0
}
}
pub struct PacketBuf {
packet: PacketBox,
range: Range<usize>,
}
impl AsSlice for PacketBuf {
type Element = u8;
fn as_slice(&self) -> &[Self::Element] {
&self.packet[self.range.clone()]
}
}
impl AsMutSlice for PacketBuf {
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
&mut self.packet[self.range.clone()]
}
}
impl Deref for PacketBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.packet[self.range.clone()]
}
}
impl DerefMut for PacketBuf {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.packet[self.range.clone()]
}
}

View File

@ -1,315 +0,0 @@
use core::cell::UnsafeCell;
use core::future::{poll_fn, Future};
use core::task::{Context, Poll};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
use futures::pin_mut;
use heapless::Vec;
#[cfg(feature = "dhcpv4")]
use smoltcp::iface::SocketHandle;
use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage};
#[cfg(feature = "medium-ethernet")]
use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes};
#[cfg(feature = "medium-ethernet")]
use smoltcp::phy::{Device as _, Medium};
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4;
use smoltcp::time::Instant as SmolInstant;
#[cfg(feature = "medium-ethernet")]
use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress};
use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr};
use crate::device::{Device, DeviceAdapter, LinkState};
const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535;
pub struct StackResources<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> {
addresses: [IpCidr; ADDR],
sockets: [SocketStorage<'static>; SOCK],
#[cfg(feature = "medium-ethernet")]
routes: [Option<(IpCidr, Route)>; 1],
#[cfg(feature = "medium-ethernet")]
neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR],
}
impl<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> StackResources<ADDR, SOCK, NEIGHBOR> {
pub fn new() -> Self {
Self {
addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR],
sockets: [SocketStorage::EMPTY; SOCK],
#[cfg(feature = "medium-ethernet")]
routes: [None; 1],
#[cfg(feature = "medium-ethernet")]
neighbor_cache: [None; NEIGHBOR],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
pub address: Ipv4Cidr,
pub gateway: Option<Ipv4Address>,
pub dns_servers: Vec<Ipv4Address, 3>,
}
pub enum ConfigStrategy {
Static(Config),
#[cfg(feature = "dhcpv4")]
Dhcp,
}
pub struct Stack<D: Device> {
pub(crate) socket: UnsafeCell<SocketStack>,
inner: UnsafeCell<Inner<D>>,
}
struct Inner<D: Device> {
device: DeviceAdapter<D>,
link_up: bool,
config: Option<Config>,
#[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>,
}
pub(crate) struct SocketStack {
pub(crate) sockets: SocketSet<'static>,
pub(crate) iface: Interface<'static>,
pub(crate) waker: WakerRegistration,
next_local_port: u16,
}
unsafe impl<D: Device> Send for Stack<D> {}
impl<D: Device + 'static> Stack<D> {
pub fn new<const ADDR: usize, const SOCK: usize, const NEIGH: usize>(
device: D,
config: ConfigStrategy,
resources: &'static mut StackResources<ADDR, SOCK, NEIGH>,
random_seed: u64,
) -> Self {
#[cfg(feature = "medium-ethernet")]
let medium = device.capabilities().medium;
#[cfg(feature = "medium-ethernet")]
let ethernet_addr = if medium == Medium::Ethernet {
device.ethernet_address()
} else {
[0, 0, 0, 0, 0, 0]
};
let mut device = DeviceAdapter::new(device);
let mut b = InterfaceBuilder::new();
b = b.ip_addrs(&mut resources.addresses[..]);
b = b.random_seed(random_seed);
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr)));
b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..]));
b = b.routes(Routes::new(&mut resources.routes[..]));
}
let iface = b.finalize(&mut device);
let sockets = SocketSet::new(&mut resources.sockets[..]);
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
let mut inner = Inner {
device,
link_up: false,
config: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
};
let mut socket = SocketStack {
sockets,
iface,
waker: WakerRegistration::new(),
next_local_port,
};
match config {
ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config),
#[cfg(feature = "dhcpv4")]
ConfigStrategy::Dhcp => {
let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new());
inner.dhcp_socket = Some(handle);
}
}
Self {
socket: UnsafeCell::new(socket),
inner: UnsafeCell::new(inner),
}
}
/// SAFETY: must not call reentrantly.
unsafe fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
f(&*self.socket.get(), &*self.inner.get())
}
/// SAFETY: must not call reentrantly.
unsafe fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
f(&mut *self.socket.get(), &mut *self.inner.get())
}
pub fn ethernet_address(&self) -> [u8; 6] {
unsafe { self.with(|_s, i| i.device.device.ethernet_address()) }
}
pub fn is_link_up(&self) -> bool {
unsafe { self.with(|_s, i| i.link_up) }
}
pub fn is_config_up(&self) -> bool {
unsafe { self.with(|_s, i| i.config.is_some()) }
}
pub fn config(&self) -> Option<Config> {
unsafe { self.with(|_s, i| i.config.clone()) }
}
pub async fn run(&self) -> ! {
poll_fn(|cx| {
unsafe { self.with_mut(|s, i| i.poll(cx, s)) }
Poll::<()>::Pending
})
.await;
unreachable!()
}
}
impl SocketStack {
#[allow(clippy::absurd_extreme_comparisons)]
pub fn get_local_port(&mut self) -> u16 {
let res = self.next_local_port;
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
res
}
}
impl<D: Device + 'static> Inner<D> {
fn apply_config(&mut self, s: &mut SocketStack, config: Config) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Acquired IP configuration:");
debug!(" IP address: {}", config.address);
self.set_ipv4_addr(s, config.address);
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
} else {
debug!(" Default gateway: None");
s.iface.routes_mut().remove_default_ipv4_route();
}
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
self.config = Some(config)
}
#[allow(unused)] // used only with dhcp
fn unapply_config(&mut self, s: &mut SocketStack) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Lost IP configuration");
self.set_ipv4_addr(s, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
s.iface.routes_mut().remove_default_ipv4_route();
}
self.config = None
}
fn set_ipv4_addr(&mut self, s: &mut SocketStack, cidr: Ipv4Cidr) {
s.iface.update_ip_addrs(|addrs| {
let dest = addrs.iter_mut().next().unwrap();
*dest = IpCidr::Ipv4(cidr);
});
}
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
self.device.device.register_waker(cx.waker());
s.waker.register(cx.waker());
let timestamp = instant_to_smoltcp(Instant::now());
if s.iface.poll(timestamp, &mut self.device, &mut s.sockets).is_err() {
// If poll() returns error, it may not be done yet, so poll again later.
cx.waker().wake_by_ref();
return;
}
// Update link up
let old_link_up = self.link_up;
self.link_up = self.device.device.link_state() == LinkState::Up;
// Print when changed
if old_link_up != self.link_up {
info!("link_up = {:?}", self.link_up);
}
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up {
match socket.poll() {
None => {}
Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
Some(dhcpv4::Event::Configured(config)) => {
let mut dns_servers = Vec::new();
for s in &config.dns_servers {
if let Some(addr) = s {
dns_servers.push(addr.clone()).unwrap();
}
}
self.apply_config(
s,
Config {
address: config.address,
gateway: config.router,
dns_servers,
},
)
}
}
} else if old_link_up {
socket.reset();
self.unapply_config(s);
}
}
//if old_link_up || self.link_up {
// self.poll_configurator(timestamp)
//}
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
let t = Timer::at(instant_from_smoltcp(poll_at));
pin_mut!(t);
if t.poll(cx).is_ready() {
cx.waker().wake_by_ref();
}
}
}
}
fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
SmolInstant::from_millis(instant.as_millis() as i64)
}
fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
Instant::from_millis(instant.total_millis() as u64)
}

View File

@ -1,16 +1,15 @@
use core::cell::UnsafeCell;
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use embassy_net_driver::Driver;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::tcp;
use smoltcp::time::Duration;
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
use super::stack::Stack;
use crate::stack::SocketStack;
use crate::Device;
use crate::{SocketStack, Stack};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -64,12 +63,15 @@ impl<'a> TcpWriter<'a> {
pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
self.io.write(buf).await
}
pub async fn flush(&mut self) -> Result<(), Error> {
self.io.flush().await
}
}
impl<'a> TcpSocket<'a> {
pub fn new<D: Device>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
// safety: not accessed reentrantly.
let s = unsafe { &mut *stack.socket.get() };
pub fn new<D: Driver>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
let s = &mut *stack.socket.borrow_mut();
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
let handle = s.sockets.add(tcp::Socket::new(
@ -93,17 +95,18 @@ impl<'a> TcpSocket<'a> {
where
T: Into<IpEndpoint>,
{
// safety: not accessed reentrantly.
let local_port = unsafe { &mut *self.io.stack.get() }.get_local_port();
let local_port = self.io.stack.borrow_mut().get_local_port();
// safety: not accessed reentrantly.
match unsafe { self.io.with_mut(|s, i| s.connect(i, remote_endpoint, local_port)) } {
match {
self.io
.with_mut(|s, i| s.connect(i.context(), remote_endpoint, local_port))
} {
Ok(()) => {}
Err(tcp::ConnectError::InvalidState) => return Err(ConnectError::InvalidState),
Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute),
}
poll_fn(|cx| unsafe {
poll_fn(|cx| {
self.io.with_mut(|s, _| match s.state() {
tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)),
tcp::State::Listen => unreachable!(),
@ -121,14 +124,13 @@ impl<'a> TcpSocket<'a> {
where
T: Into<IpListenEndpoint>,
{
// safety: not accessed reentrantly.
match unsafe { self.io.with_mut(|s, _| s.listen(local_endpoint)) } {
match self.io.with_mut(|s, _| s.listen(local_endpoint)) {
Ok(()) => {}
Err(tcp::ListenError::InvalidState) => return Err(AcceptError::InvalidState),
Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort),
}
poll_fn(|cx| unsafe {
poll_fn(|cx| {
self.io.with_mut(|s, _| match s.state() {
tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => {
s.register_send_waker(cx.waker());
@ -148,52 +150,54 @@ impl<'a> TcpSocket<'a> {
self.io.write(buf).await
}
pub async fn flush(&mut self) -> Result<(), Error> {
self.io.flush().await
}
pub fn set_timeout(&mut self, duration: Option<Duration>) {
unsafe { self.io.with_mut(|s, _| s.set_timeout(duration)) }
self.io.with_mut(|s, _| s.set_timeout(duration))
}
pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
unsafe { self.io.with_mut(|s, _| s.set_keep_alive(interval)) }
self.io.with_mut(|s, _| s.set_keep_alive(interval))
}
pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
unsafe { self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) }
self.io.with_mut(|s, _| s.set_hop_limit(hop_limit))
}
pub fn local_endpoint(&self) -> Option<IpEndpoint> {
unsafe { self.io.with(|s, _| s.local_endpoint()) }
self.io.with(|s, _| s.local_endpoint())
}
pub fn remote_endpoint(&self) -> Option<IpEndpoint> {
unsafe { self.io.with(|s, _| s.remote_endpoint()) }
self.io.with(|s, _| s.remote_endpoint())
}
pub fn state(&self) -> tcp::State {
unsafe { self.io.with(|s, _| s.state()) }
self.io.with(|s, _| s.state())
}
pub fn close(&mut self) {
unsafe { self.io.with_mut(|s, _| s.close()) }
self.io.with_mut(|s, _| s.close())
}
pub fn abort(&mut self) {
unsafe { self.io.with_mut(|s, _| s.abort()) }
self.io.with_mut(|s, _| s.abort())
}
pub fn may_send(&self) -> bool {
unsafe { self.io.with(|s, _| s.may_send()) }
self.io.with(|s, _| s.may_send())
}
pub fn may_recv(&self) -> bool {
unsafe { self.io.with(|s, _| s.may_recv()) }
self.io.with(|s, _| s.may_recv())
}
}
impl<'a> Drop for TcpSocket<'a> {
fn drop(&mut self) {
// safety: not accessed reentrantly.
let s = unsafe { &mut *self.io.stack.get() };
s.sockets.remove(self.io.handle);
self.io.stack.borrow_mut().sockets.remove(self.io.handle);
}
}
@ -201,21 +205,19 @@ impl<'a> Drop for TcpSocket<'a> {
#[derive(Copy, Clone)]
struct TcpIo<'a> {
stack: &'a UnsafeCell<SocketStack>,
stack: &'a RefCell<SocketStack>,
handle: SocketHandle,
}
impl<'d> TcpIo<'d> {
/// SAFETY: must not call reentrantly.
unsafe fn with<R>(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.get();
fn with<R>(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.borrow();
let socket = s.sockets.get::<tcp::Socket>(self.handle);
f(socket, &s.iface)
}
/// SAFETY: must not call reentrantly.
unsafe fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.get();
fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<tcp::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
@ -223,7 +225,7 @@ impl<'d> TcpIo<'d> {
}
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
// CAUTION: smoltcp semantics around EOF are different to what you'd expect
// from posix-like IO, so we have to tweak things here.
self.with_mut(|s, _| match s.recv_slice(buf) {
@ -244,7 +246,7 @@ impl<'d> TcpIo<'d> {
}
async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.send_slice(buf) {
// Not ready to send (no space in the tx buffer)
Ok(0) => {
@ -260,10 +262,19 @@ impl<'d> TcpIo<'d> {
.await
}
#[allow(unused)]
async fn flush(&mut self) -> Result<(), Error> {
poll_fn(move |_| {
Poll::Ready(Ok(())) // TODO: Is there a better implementation for this?
poll_fn(move |cx| {
self.with_mut(|s, _| {
// If there are outstanding send operations, register for wake up and wait
// smoltcp issues wake-ups when octets are dequeued from the send buffer
if s.send_queue() > 0 {
s.register_send_waker(cx.waker());
Poll::Pending
// No outstanding sends, socket is flushed
} else {
Poll::Ready(Ok(()))
}
})
})
.await
}
@ -271,8 +282,6 @@ impl<'d> TcpIo<'d> {
#[cfg(feature = "nightly")]
mod embedded_io_impls {
use core::future::Future;
use super::*;
impl embedded_io::Error for ConnectError {
@ -292,30 +301,18 @@ mod embedded_io_impls {
}
impl<'d> embedded_io::asynch::Read for TcpSocket<'d> {
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>> + 'a
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.io.read(buf)
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> {
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>> + 'a
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.io.write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.io.write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.io.flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.io.flush().await
}
}
@ -324,12 +321,8 @@ mod embedded_io_impls {
}
impl<'d> embedded_io::asynch::Read for TcpReader<'d> {
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>> + 'a
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.io.read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.io.read(buf).await
}
}
@ -338,27 +331,19 @@ mod embedded_io_impls {
}
impl<'d> embedded_io::asynch::Write for TcpWriter<'d> {
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>> + 'a
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.io.write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.io.write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.io.flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.io.flush().await
}
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
pub mod client {
use core::future::Future;
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
@ -368,45 +353,46 @@ pub mod client {
use super::*;
/// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ.
pub struct TcpClient<'d, D: Device, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
stack: &'d Stack<D>,
state: &'d TcpClientState<N, TX_SZ, RX_SZ>,
}
impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> {
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> {
/// Create a new TcpClient
pub fn new(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self {
Self { stack, state }
}
}
impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
for TcpClient<'d, D, N, TX_SZ, RX_SZ>
{
type Error = Error;
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
type ConnectFuture<'m> = impl Future<Output = Result<Self::Connection<'m>, Self::Error>> + 'm
where
Self: 'm;
fn connect<'m>(&'m self, remote: embedded_nal_async::SocketAddr) -> Self::ConnectFuture<'m> {
async move {
let addr: crate::IpAddress = match remote.ip() {
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
#[cfg(feature = "proto-ipv6")]
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
#[cfg(not(feature = "proto-ipv6"))]
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};
let remote_endpoint = (addr, remote.port());
let mut socket = TcpConnection::new(&self.stack, self.state)?;
socket
.socket
.connect(remote_endpoint)
.await
.map_err(|_| Error::ConnectionReset)?;
Ok(socket)
}
async fn connect<'a>(
&'a self,
remote: embedded_nal_async::SocketAddr,
) -> Result<Self::Connection<'a>, Self::Error>
where
Self: 'a,
{
let addr: crate::IpAddress = match remote.ip() {
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
#[cfg(feature = "proto-ipv6")]
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
#[cfg(not(feature = "proto-ipv6"))]
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};
let remote_endpoint = (addr, remote.port());
let mut socket = TcpConnection::new(&self.stack, self.state)?;
socket
.socket
.connect(remote_endpoint)
.await
.map_err(|_| Error::ConnectionReset)?;
Ok(socket)
}
}
@ -417,10 +403,10 @@ pub mod client {
}
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> {
fn new<D: Device>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
fn new<D: Driver>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?;
Ok(Self {
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().0, &mut bufs.as_mut().1) },
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) },
state,
bufs,
})
@ -445,32 +431,20 @@ pub mod client {
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read
for TcpConnection<'d, N, TX_SZ, RX_SZ>
{
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>> + 'a
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.socket.read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.socket.read(buf).await
}
}
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write
for TcpConnection<'d, N, TX_SZ, RX_SZ>
{
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>> + 'a
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.socket.write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.socket.write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.socket.flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.socket.flush().await
}
}

View File

@ -1,14 +1,14 @@
use core::cell::UnsafeCell;
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use embassy_net_driver::Driver;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::udp::{self, PacketMetadata};
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
use super::stack::SocketStack;
use crate::{Device, Stack};
use crate::{SocketStack, Stack};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -27,20 +27,19 @@ pub enum Error {
}
pub struct UdpSocket<'a> {
stack: &'a UnsafeCell<SocketStack>,
stack: &'a RefCell<SocketStack>,
handle: SocketHandle,
}
impl<'a> UdpSocket<'a> {
pub fn new<D: Device>(
pub fn new<D: Driver>(
stack: &'a Stack<D>,
rx_meta: &'a mut [PacketMetadata],
rx_buffer: &'a mut [u8],
tx_meta: &'a mut [PacketMetadata],
tx_buffer: &'a mut [u8],
) -> Self {
// safety: not accessed reentrantly.
let s = unsafe { &mut *stack.socket.get() };
let s = &mut *stack.socket.borrow_mut();
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
@ -63,30 +62,26 @@ impl<'a> UdpSocket<'a> {
{
let mut endpoint = endpoint.into();
// safety: not accessed reentrantly.
if endpoint.port == 0 {
// If user didn't specify port allocate a dynamic port.
endpoint.port = unsafe { &mut *self.stack.get() }.get_local_port();
endpoint.port = self.stack.borrow_mut().get_local_port();
}
// safety: not accessed reentrantly.
match unsafe { self.with_mut(|s, _| s.bind(endpoint)) } {
match self.with_mut(|s, _| s.bind(endpoint)) {
Ok(()) => Ok(()),
Err(udp::BindError::InvalidState) => Err(BindError::InvalidState),
Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute),
}
}
/// SAFETY: must not call reentrantly.
unsafe fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.get();
fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.borrow();
let socket = s.sockets.get::<udp::Socket>(self.handle);
f(socket, &s.iface)
}
/// SAFETY: must not call reentrantly.
unsafe fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.get();
fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<udp::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
@ -94,13 +89,12 @@ impl<'a> UdpSocket<'a> {
}
pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> {
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.recv_slice(buf) {
Ok(x) => Poll::Ready(Ok(x)),
// No data ready
Err(udp::RecvError::Exhausted) => {
//s.register_recv_waker(cx.waker());
cx.waker().wake_by_ref();
s.register_recv_waker(cx.waker());
Poll::Pending
}
})
@ -113,7 +107,7 @@ impl<'a> UdpSocket<'a> {
T: Into<IpEndpoint>,
{
let remote_endpoint = remote_endpoint.into();
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
// Entire datagram has been sent
Ok(()) => Poll::Ready(Ok(())),
@ -128,30 +122,28 @@ impl<'a> UdpSocket<'a> {
}
pub fn endpoint(&self) -> IpListenEndpoint {
unsafe { self.with(|s, _| s.endpoint()) }
self.with(|s, _| s.endpoint())
}
pub fn is_open(&self) -> bool {
unsafe { self.with(|s, _| s.is_open()) }
self.with(|s, _| s.is_open())
}
pub fn close(&mut self) {
unsafe { self.with_mut(|s, _| s.close()) }
self.with_mut(|s, _| s.close())
}
pub fn may_send(&self) -> bool {
unsafe { self.with(|s, _| s.can_send()) }
self.with(|s, _| s.can_send())
}
pub fn may_recv(&self) -> bool {
unsafe { self.with(|s, _| s.can_recv()) }
self.with(|s, _| s.can_recv())
}
}
impl Drop for UdpSocket<'_> {
fn drop(&mut self) {
// safety: not accessed reentrantly.
let s = unsafe { &mut *self.stack.get() };
s.sockets.remove(self.handle);
self.stack.borrow_mut().sockets.remove(self.handle);
}
}

View File

@ -34,22 +34,30 @@ unstable-pac = []
# Implement embedded-hal-async traits if `nightly` is set as well.
unstable-traits = ["embedded-hal-1"]
nrf52805 = ["nrf52805-pac", "_ppi"]
nrf52810 = ["nrf52810-pac", "_ppi"]
nrf52811 = ["nrf52811-pac", "_ppi"]
nrf52820 = ["nrf52820-pac", "_ppi"]
nrf52832 = ["nrf52832-pac", "_ppi"]
nrf52833 = ["nrf52833-pac", "_ppi", "_gpio-p1"]
nrf52840 = ["nrf52840-pac", "_ppi", "_gpio-p1"]
nrf5340-app-s = ["_nrf5340-app"]
nrf5340-app-ns = ["_nrf5340-app"]
nrf52805 = ["nrf52805-pac", "_nrf52"]
nrf52810 = ["nrf52810-pac", "_nrf52"]
nrf52811 = ["nrf52811-pac", "_nrf52"]
nrf52820 = ["nrf52820-pac", "_nrf52"]
nrf52832 = ["nrf52832-pac", "_nrf52"]
nrf52833 = ["nrf52833-pac", "_nrf52", "_gpio-p1"]
nrf52840 = ["nrf52840-pac", "_nrf52", "_gpio-p1"]
nrf5340-app-s = ["_nrf5340-app", "_s"]
nrf5340-app-ns = ["_nrf5340-app", "_ns"]
nrf5340-net = ["_nrf5340-net"]
nrf9160-s = ["_nrf9160"]
nrf9160-ns = ["_nrf9160"]
nrf9160-s = ["_nrf9160", "_s"]
nrf9160-ns = ["_nrf9160", "_ns"]
gpiote = []
time-driver-rtc1 = ["_time-driver"]
# Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53)
nfc-pins-as-gpio = []
# Allow using the RST pin as a regular GPIO pin.
# nrf52805, nrf52810, nrf52811, nrf52832: P0_21
# nrf52820, nrf52833, nrf52840: P0_18
reset-pin-as-gpio = []
# Features starting with `_` are for internal use only. They're not intended
# to be enabled by other crates, and are not covered by semver guarantees.
@ -57,26 +65,31 @@ _nrf5340-app = ["_nrf5340", "nrf5340-app-pac"]
_nrf5340-net = ["_nrf5340", "nrf5340-net-pac"]
_nrf5340 = ["_gpio-p1", "_dppi"]
_nrf9160 = ["nrf9160-pac", "_dppi"]
_nrf52 = ["_ppi"]
_time-driver = ["dep:embassy-time", "embassy-time?/tick-hz-32_768"]
# trustzone state.
_s = []
_ns = []
_ppi = []
_dppi = []
_gpio-p1 = []
[dependencies]
embassy-executor = { version = "0.1.0", path = "../embassy-executor", optional = true }
embassy-executor = { version = "0.2.0", path = "../embassy-executor", optional = true }
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-3"]}
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
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.9", optional = true}
embedded-hal-async = { version = "=0.1.0-alpha.3", optional = true}
embedded-io = { version = "0.3.1", features = ["async"], optional = true }
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 }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
@ -87,7 +100,7 @@ critical-section = "1.1"
rand_core = "0.6.3"
fixed = "1.10.0"
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.3.0", optional = true }
embedded-storage-async = { version = "0.4.0", optional = true }
cfg-if = "1.0.0"
nrf52805-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }

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