Compare commits

...

201 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
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
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
6b2aaacf83 Update embassy
Merge commit '9dd3719f09835f646e3a8f3abaa33726a1e3f9ca'
2023-03-30 14:37:51 +02: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
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
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
249 changed files with 11345 additions and 13857 deletions

View File

@ -61,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
@ -84,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

@ -71,7 +71,7 @@ jobs:
- name: Test boot
working-directory: ./embassy-boot/boot
run: cargo test && cargo test --features "ed25519-dalek" && cargo test --features "ed25519-salty"
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

View File

@ -16,10 +16,11 @@
// "embassy-executor/Cargo.toml",
// "embassy-sync/Cargo.toml",
"examples/nrf52840/Cargo.toml",
//"examples/nrf5340/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",
@ -28,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",
@ -35,9 +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",
],
}

View File

@ -35,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.
@ -106,10 +106,10 @@ 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:

9
ci.sh
View File

@ -22,6 +22,7 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
--- build --release --manifest-path embassy-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 \
@ -68,10 +69,10 @@ cargo batch \
--- 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-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 \
--- 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-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 \

View File

@ -9,10 +9,15 @@ 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,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 \

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", "arch-cortex-m", "executor-thread"] }
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

@ -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", "arch-cortex-m", "executor-thread"] }
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

@ -29,7 +29,7 @@ log = { version = "0.4", optional = true }
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.4.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 }
@ -48,5 +48,7 @@ features = ["rand", "std", "u32_backend"]
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

@ -1,5 +1,6 @@
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};
@ -78,6 +79,7 @@ impl FirmwareUpdater {
/// 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,
@ -108,7 +110,7 @@ impl FirmwareUpdater {
///
/// 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")]
#[cfg(all(feature = "_verify", feature = "nightly"))]
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
&mut self,
_state_and_dfu_flash: &mut F,
@ -172,6 +174,7 @@ impl FirmwareUpdater {
}
/// 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,
@ -194,7 +197,7 @@ impl FirmwareUpdater {
/// # 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"))]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
pub async fn mark_updated<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
@ -209,6 +212,7 @@ impl FirmwareUpdater {
/// # 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,
@ -218,6 +222,7 @@ impl FirmwareUpdater {
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
}
#[cfg(feature = "nightly")]
async fn set_magic<F: AsyncNorFlash>(
&mut self,
aligned: &mut [u8],
@ -258,6 +263,7 @@ impl FirmwareUpdater {
/// # 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,
@ -280,6 +286,7 @@ impl FirmwareUpdater {
///
/// 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,
@ -513,6 +520,7 @@ mod tests {
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);

View File

@ -1,4 +1,4 @@
#![feature(async_fn_in_trait)]
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
#![allow(incomplete_features)]
#![no_std]
#![warn(missing_docs)]
@ -83,7 +83,7 @@ mod tests {
}
#[test]
#[cfg(not(feature = "_verify"))]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_swap_state() {
const STATE: Partition = Partition::new(0, 4096);
const ACTIVE: Partition = Partition::new(4096, 61440);
@ -136,7 +136,7 @@ mod tests {
}
#[test]
#[cfg(not(feature = "_verify"))]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_separate_flash_active_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
@ -173,7 +173,7 @@ mod tests {
}
#[test]
#[cfg(not(feature = "_verify"))]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_separate_flash_dfu_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
@ -212,7 +212,7 @@ mod tests {
}
#[test]
#[cfg(feature = "_verify")]
#[cfg(all(feature = "nightly", feature = "_verify"))]
fn test_verify() {
// The following key setup is based on:
// https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example

View File

@ -3,6 +3,7 @@
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> {
@ -134,6 +135,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla
}
}
#[cfg(feature = "nightly")]
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
@ -148,6 +150,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncR
}
}
#[cfg(feature = "nightly")]
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{

View File

@ -1,4 +1,5 @@
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.
@ -23,6 +24,7 @@ impl Partition {
}
/// Read from the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn read<F: AsyncReadNorFlash>(
&self,
flash: &mut F,
@ -34,6 +36,7 @@ impl Partition {
}
/// 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?;
@ -42,6 +45,7 @@ impl Partition {
}
/// 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;
@ -51,6 +55,7 @@ impl Partition {
}
/// 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;

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.4.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

@ -13,7 +13,7 @@ An adaptation of `embassy-boot` for nRF.
## Minimum supported Rust version (MSRV)
`embassy-boot-nrf` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
`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

View File

@ -1,5 +1,4 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod fmt;

View File

@ -18,14 +18,14 @@ 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, features = ["nightly"] }
embassy-rp = { path = "../../embassy-rp", default-features = false }
embassy-boot = { path = "../boot", default-features = false }
embassy-time = { path = "../../embassy-time", features = ["nightly"] }
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 = "0.4.0"
embedded-storage-async = { version = "0.4.0", optional = true }
cfg-if = "1.0.0"
[features]
@ -40,6 +40,12 @@ 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

View File

@ -13,7 +13,7 @@ NOTE: The applications using this bootloader should not link with the `link-rp.x
## Minimum supported Rust version (MSRV)
`embassy-boot-rp` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
`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

View File

@ -1,5 +1,4 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod fmt;

View File

@ -19,12 +19,12 @@ 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.4.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

@ -11,7 +11,7 @@ An adaptation of `embassy-boot` for STM32.
## Minimum supported Rust version (MSRV)
`embassy-boot-stm32` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
`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

View File

@ -1,5 +1,4 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod fmt;

View File

@ -37,8 +37,8 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-executor = { version = "0.1.0", path = "../embassy-executor"}
embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
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

@ -131,48 +131,6 @@ 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(&mut self) -> Result<(), Self::Error> {
async move { self.wrapped.bflush() }
}
}
/// NOR flash wrapper
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};

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"
@ -61,7 +61,7 @@ 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"

View File

@ -4,15 +4,16 @@ use core::ptr::NonNull;
use atomic_polyfill::{AtomicPtr, Ordering};
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),
}
}
}
@ -51,7 +52,12 @@ impl RunQueue {
self.head
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
was_empty = prev.is_null();
task.header().run_queue_item.next.store(prev, Ordering::Relaxed);
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();
@ -64,18 +70,19 @@ impl RunQueue {
/// 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(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) {
let task = unsafe { TaskRef::from_ptr(task.as_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 = task.header().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,5 +1,5 @@
#[macro_export]
macro_rules! peripherals {
macro_rules! peripherals_definition {
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
/// Types for the peripheral singletons.
pub mod peripherals {
@ -26,7 +26,12 @@ 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`].
@ -76,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),*) => {

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"] },
{ 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 = ["dep:embassy-stm32"]
time = []
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
defmt = ["dep:defmt", "lorawan-device/defmt"]
[dependencies]
@ -38,5 +30,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
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 {
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
1003
}
}
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 {
-50
}
fn get_rx_window_duration_ms(&self) -> u32 {
1050
}
}
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 {
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
1003
}
}
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

@ -27,12 +27,10 @@ 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::iface::SocketHandle;
use smoltcp::iface::{Interface, SocketSet, SocketStorage};
use smoltcp::socket::dhcpv4::{self, RetryConfig};
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4;
use smoltcp::socket::dhcpv4::RetryConfig;
use smoltcp::time::Duration;
// smoltcp reexports
pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant};
@ -76,6 +74,7 @@ pub struct StaticConfig {
pub dns_servers: Vec<Ipv4Address, 3>,
}
#[cfg(feature = "dhcpv4")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DhcpConfig {
pub max_lease_duration: Option<Duration>,
@ -88,6 +87,7 @@ pub struct DhcpConfig {
pub client_port: u16,
}
#[cfg(feature = "dhcpv4")]
impl Default for DhcpConfig {
fn default() -> Self {
Self {
@ -384,6 +384,7 @@ impl<D: Driver + 'static> Inner<D> {
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);

View File

@ -78,7 +78,7 @@ _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.2.0", path = "../embassy-sync" }
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-3"]}

View File

@ -184,6 +184,34 @@ pub mod config {
NotConfigured,
}
/// Settings for enabling the built in DCDC converters.
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
pub struct DcdcConfig {
/// Config for the first stage DCDC (VDDH -> VDD), if disabled LDO will be used.
#[cfg(feature = "nrf52840")]
pub reg0: bool,
/// Config for the second stage DCDC (VDD -> DEC4), if disabled LDO will be used.
pub reg1: bool,
}
/// Settings for enabling the built in DCDC converters.
#[cfg(feature = "_nrf5340-app")]
pub struct DcdcConfig {
/// Config for the high voltage stage, if disabled LDO will be used.
pub regh: bool,
/// Config for the main rail, if disabled LDO will be used.
pub regmain: bool,
/// Config for the radio rail, if disabled LDO will be used.
pub regradio: bool,
}
/// Settings for enabling the built in DCDC converter.
#[cfg(feature = "_nrf9160")]
pub struct DcdcConfig {
/// Config for the main rail, if disabled LDO will be used.
pub regmain: bool,
}
/// Configuration for peripherals. Default configuration should work on any nRF chip.
#[non_exhaustive]
pub struct Config {
@ -191,6 +219,9 @@ pub mod config {
pub hfclk_source: HfclkSource,
/// Low frequency clock source.
pub lfclk_source: LfclkSource,
#[cfg(not(feature = "_nrf5340-net"))]
/// DCDC configuration.
pub dcdc: DcdcConfig,
/// GPIOTE interrupt priority. Should be lower priority than softdevice if used.
#[cfg(feature = "gpiote")]
pub gpiote_interrupt_priority: crate::interrupt::Priority,
@ -209,6 +240,20 @@ pub mod config {
// xtals if they know they have them.
hfclk_source: HfclkSource::Internal,
lfclk_source: LfclkSource::InternalRC,
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
dcdc: DcdcConfig {
#[cfg(feature = "nrf52840")]
reg0: false,
reg1: false,
},
#[cfg(feature = "_nrf5340-app")]
dcdc: DcdcConfig {
regh: false,
regmain: false,
regradio: false,
},
#[cfg(feature = "_nrf9160")]
dcdc: DcdcConfig { regmain: false },
#[cfg(feature = "gpiote")]
gpiote_interrupt_priority: crate::interrupt::Priority::P0,
#[cfg(feature = "_time-driver")]
@ -454,6 +499,41 @@ pub fn init(config: config::Config) -> Peripherals {
r.tasks_lfclkstart.write(|w| unsafe { w.bits(1) });
while r.events_lfclkstarted.read().bits() == 0 {}
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
{
// Setup DCDCs.
let pwr = unsafe { &*pac::POWER::ptr() };
#[cfg(feature = "nrf52840")]
if config.dcdc.reg0 {
pwr.dcdcen0.write(|w| w.dcdcen().set_bit());
}
if config.dcdc.reg1 {
pwr.dcdcen.write(|w| w.dcdcen().set_bit());
}
}
#[cfg(feature = "_nrf9160")]
{
// Setup DCDC.
let reg = unsafe { &*pac::REGULATORS::ptr() };
if config.dcdc.regmain {
reg.dcdcen.write(|w| w.dcdcen().set_bit());
}
}
#[cfg(feature = "_nrf5340-app")]
{
// Setup DCDC.
let reg = unsafe { &*pac::REGULATORS::ptr() };
if config.dcdc.regh {
reg.vregh.dcdcen.write(|w| w.dcdcen().set_bit());
}
if config.dcdc.regmain {
reg.vregmain.dcdcen.write(|w| w.dcdcen().set_bit());
}
if config.dcdc.regradio {
reg.vregradio.dcdcen.write(|w| w.dcdcen().set_bit());
}
}
// Init GPIOTE
#[cfg(feature = "gpiote")]
gpiote::init(config.gpiote_interrupt_priority);

View File

@ -992,80 +992,3 @@ mod eh1 {
type Error = Error;
}
}
#[cfg(all(
feature = "unstable-traits",
feature = "nightly",
feature = "_todo_embedded_hal_serial"
))]
mod eha {
use core::future::Future;
use super::*;
impl<'d, T: Instance> embedded_hal_async::serial::Read for Uarte<'d, T> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.read(buffer)
}
}
impl<'d, T: Instance> embedded_hal_async::serial::Write for Uarte<'d, T> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> {
self.write(buffer)
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn flush(&mut self) -> Result<(), Self::Error> {
async move { Ok(()) }
}
}
impl<'d, T: Instance> embedded_hal_async::serial::Write for UarteTx<'d, T> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> {
self.write(buffer)
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn flush(&mut self) -> Result<(), Self::Error> {
async move { Ok(()) }
}
}
impl<'d, T: Instance> embedded_hal_async::serial::Read for UarteRx<'d, T> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.read(buffer)
}
}
impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Read for UarteWithIdle<'d, U, T> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.read(buffer)
}
}
impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Write for UarteWithIdle<'d, U, T> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> {
self.write(buffer)
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn flush(&mut self) -> Result<(), Self::Error> {
async move { Ok(()) }
}
}
}

View File

@ -29,7 +29,6 @@ time-driver = []
rom-func-cache = []
intrinsics = []
rom-v2-intrinsics = []
pio = ["dep:pio", "dep:pio-proc"]
# Enable nightly-only features
nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly", "dep:embassy-usb-driver", "dep:embedded-io"]
@ -40,7 +39,7 @@ unstable-traits = ["embedded-hal-1", "embedded-hal-nb"]
[dependencies]
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-executor = { version = "0.1.0", path = "../embassy-executor" }
embassy-executor = { version = "0.2.0", path = "../embassy-executor" }
embassy-time = { version = "0.1.0", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-2"]}
@ -60,9 +59,9 @@ chrono = { version = "0.4", default-features = false, optional = true }
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
embedded-storage = { version = "0.3" }
rand_core = "0.6.4"
fixed = "1.23.1"
rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] }
#rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] }
rp-pac = { version = "2", features = ["rt"] }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
@ -70,5 +69,5 @@ embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
embedded-hal-nb = { version = "=1.0.0-alpha.2", optional = true}
paste = "1.0"
pio-proc = {version= "0.2", optional = true}
pio = {version= "0.2.1", optional = true}
pio-proc = {version= "0.2" }
pio = {version= "0.2.1" }

View File

@ -155,8 +155,8 @@ unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1:
let cs = p.cs().read();
let prim = p.prim().read();
if cs.lock()
&& cs.refdiv() == refdiv as _
&& p.fbdiv_int().read().fbdiv_int() == fbdiv as _
&& cs.refdiv() == refdiv as u8
&& p.fbdiv_int().read().fbdiv_int() == fbdiv as u16
&& prim.postdiv1() == post_div1
&& prim.postdiv2() == post_div2
{

View File

@ -162,8 +162,6 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
/// - interrupts must be disabled
/// - DMA must not access flash memory
unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> {
let dma_status = &mut [false; crate::dma::CHANNEL_COUNT];
// Make sure we're running on CORE0
let core_id: u32 = unsafe { pac::SIO.cpuid().read() };
if core_id != 0 {
@ -174,25 +172,15 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
crate::multicore::pause_core1();
critical_section::with(|_| {
// Pause all DMA channels for the duration of the ram operation
for (number, status) in dma_status.iter_mut().enumerate() {
let ch = crate::pac::DMA.ch(number as _);
*status = ch.ctrl_trig().read().en();
if *status {
ch.ctrl_trig().modify(|w| w.set_en(false));
}
// Wait for all DMA channels in flash to finish before ram operation
const SRAM_LOWER: u32 = 0x2000_0000;
for n in 0..crate::dma::CHANNEL_COUNT {
let ch = crate::pac::DMA.ch(n);
while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {}
}
// Run our flash operation in RAM
operation();
// Re-enable previously enabled DMA channels
for (number, status) in dma_status.iter().enumerate() {
let ch = crate::pac::DMA.ch(number as _);
if *status {
ch.ctrl_trig().modify(|w| w.set_en(true));
}
}
});
// Resume CORE1 execution

View File

@ -0,0 +1,92 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/add_sub.rs
use super::{Float, Int};
use crate::rom_data;
trait ROMAdd {
fn rom_add(self, b: Self) -> Self;
}
impl ROMAdd for f32 {
fn rom_add(self, b: Self) -> Self {
rom_data::float_funcs::fadd(self, b)
}
}
impl ROMAdd for f64 {
fn rom_add(self, b: Self) -> Self {
rom_data::double_funcs::dadd(self, b)
}
}
fn add<F: Float + ROMAdd>(a: F, b: F) -> F {
if a.is_not_finite() {
if b.is_not_finite() {
let class_a = a.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK);
let class_b = b.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK);
if class_a == F::Int::ZERO && class_b == F::Int::ZERO {
// inf + inf = inf
return a;
}
if class_a == F::SIGN_MASK && class_b == F::SIGN_MASK {
// -inf + (-inf) = -inf
return a;
}
// Sign mismatch, or either is NaN already
return F::NAN;
}
// [-]inf/NaN + X = [-]inf/NaN
return a;
}
if b.is_not_finite() {
// X + [-]inf/NaN = [-]inf/NaN
return b;
}
a.rom_add(b)
}
intrinsics! {
#[alias = __addsf3vfp]
#[aeabi = __aeabi_fadd]
extern "C" fn __addsf3(a: f32, b: f32) -> f32 {
add(a, b)
}
#[bootrom_v2]
#[alias = __adddf3vfp]
#[aeabi = __aeabi_dadd]
extern "C" fn __adddf3(a: f64, b: f64) -> f64 {
add(a, b)
}
// The ROM just implements subtraction the same way, so just do it here
// and save the work of implementing more complicated NaN/inf handling.
#[alias = __subsf3vfp]
#[aeabi = __aeabi_fsub]
extern "C" fn __subsf3(a: f32, b: f32) -> f32 {
add(a, -b)
}
#[bootrom_v2]
#[alias = __subdf3vfp]
#[aeabi = __aeabi_dsub]
extern "C" fn __subdf3(a: f64, b: f64) -> f64 {
add(a, -b)
}
extern "aapcs" fn __aeabi_frsub(a: f32, b: f32) -> f32 {
add(b, -a)
}
#[bootrom_v2]
extern "aapcs" fn __aeabi_drsub(a: f64, b: f64) -> f64 {
add(b, -a)
}
}

201
embassy-rp/src/float/cmp.rs Normal file
View File

@ -0,0 +1,201 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/cmp.rs
use super::Float;
use crate::rom_data;
trait ROMCmp {
fn rom_cmp(self, b: Self) -> i32;
}
impl ROMCmp for f32 {
fn rom_cmp(self, b: Self) -> i32 {
rom_data::float_funcs::fcmp(self, b)
}
}
impl ROMCmp for f64 {
fn rom_cmp(self, b: Self) -> i32 {
rom_data::double_funcs::dcmp(self, b)
}
}
fn le_abi<F: Float + ROMCmp>(a: F, b: F) -> i32 {
if a.is_nan() || b.is_nan() {
1
} else {
a.rom_cmp(b)
}
}
fn ge_abi<F: Float + ROMCmp>(a: F, b: F) -> i32 {
if a.is_nan() || b.is_nan() {
-1
} else {
a.rom_cmp(b)
}
}
intrinsics! {
#[slower_than_default]
#[bootrom_v2]
#[alias = __eqsf2, __ltsf2, __nesf2]
extern "C" fn __lesf2(a: f32, b: f32) -> i32 {
le_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
#[alias = __eqdf2, __ltdf2, __nedf2]
extern "C" fn __ledf2(a: f64, b: f64) -> i32 {
le_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
#[alias = __gtsf2]
extern "C" fn __gesf2(a: f32, b: f32) -> i32 {
ge_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
#[alias = __gtdf2]
extern "C" fn __gedf2(a: f64, b: f64) -> i32 {
ge_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmple(a: f32, b: f32) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmpge(a: f32, b: f32) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmpeq(a: f32, b: f32) -> i32 {
(le_abi(a, b) == 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmplt(a: f32, b: f32) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmpgt(a: f32, b: f32) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmple(a: f64, b: f64) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmpge(a: f64, b: f64) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmpeq(a: f64, b: f64) -> i32 {
(le_abi(a, b) == 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmplt(a: f64, b: f64) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmpgt(a: f64, b: f64) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gesf2vfp(a: f32, b: f32) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gedf2vfp(a: f64, b: f64) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gtsf2vfp(a: f32, b: f32) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gtdf2vfp(a: f64, b: f64) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __ltsf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __ltdf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __lesf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __ledf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __nesf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) != 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __nedf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) != 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __eqsf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) == 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __eqdf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) == 0) as i32
}
}

View File

@ -0,0 +1,157 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs
use super::Float;
use crate::rom_data;
// Some of these are also not connected in the Pico SDK. This is probably
// because the ROM version actually does a fixed point conversion, just with
// the fractional width set to zero.
intrinsics! {
// Not connected in the Pico SDK
#[slower_than_default]
#[aeabi = __aeabi_i2f]
extern "C" fn __floatsisf(i: i32) -> f32 {
rom_data::float_funcs::int_to_float(i)
}
// Not connected in the Pico SDK
#[slower_than_default]
#[aeabi = __aeabi_i2d]
extern "C" fn __floatsidf(i: i32) -> f64 {
rom_data::double_funcs::int_to_double(i)
}
// Questionable gain
#[aeabi = __aeabi_l2f]
extern "C" fn __floatdisf(i: i64) -> f32 {
rom_data::float_funcs::int64_to_float(i)
}
#[bootrom_v2]
#[aeabi = __aeabi_l2d]
extern "C" fn __floatdidf(i: i64) -> f64 {
rom_data::double_funcs::int64_to_double(i)
}
// Not connected in the Pico SDK
#[slower_than_default]
#[aeabi = __aeabi_ui2f]
extern "C" fn __floatunsisf(i: u32) -> f32 {
rom_data::float_funcs::uint_to_float(i)
}
// Questionable gain
#[bootrom_v2]
#[aeabi = __aeabi_ui2d]
extern "C" fn __floatunsidf(i: u32) -> f64 {
rom_data::double_funcs::uint_to_double(i)
}
// Questionable gain
#[bootrom_v2]
#[aeabi = __aeabi_ul2f]
extern "C" fn __floatundisf(i: u64) -> f32 {
rom_data::float_funcs::uint64_to_float(i)
}
#[bootrom_v2]
#[aeabi = __aeabi_ul2d]
extern "C" fn __floatundidf(i: u64) -> f64 {
rom_data::double_funcs::uint64_to_double(i)
}
// The Pico SDK does some optimization here (e.x. fast paths for zero and
// one), but we can just directly connect it.
#[aeabi = __aeabi_f2iz]
extern "C" fn __fixsfsi(f: f32) -> i32 {
rom_data::float_funcs::float_to_int(f)
}
#[bootrom_v2]
#[aeabi = __aeabi_f2lz]
extern "C" fn __fixsfdi(f: f32) -> i64 {
rom_data::float_funcs::float_to_int64(f)
}
// Not connected in the Pico SDK
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_d2iz]
extern "C" fn __fixdfsi(f: f64) -> i32 {
rom_data::double_funcs::double_to_int(f)
}
// Like with the 32 bit version, there's optimization that we just
// skip.
#[bootrom_v2]
#[aeabi = __aeabi_d2lz]
extern "C" fn __fixdfdi(f: f64) -> i64 {
rom_data::double_funcs::double_to_int64(f)
}
#[slower_than_default]
#[aeabi = __aeabi_f2uiz]
extern "C" fn __fixunssfsi(f: f32) -> u32 {
rom_data::float_funcs::float_to_uint(f)
}
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_f2ulz]
extern "C" fn __fixunssfdi(f: f32) -> u64 {
rom_data::float_funcs::float_to_uint64(f)
}
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_d2uiz]
extern "C" fn __fixunsdfsi(f: f64) -> u32 {
rom_data::double_funcs::double_to_uint(f)
}
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_d2ulz]
extern "C" fn __fixunsdfdi(f: f64) -> u64 {
rom_data::double_funcs::double_to_uint64(f)
}
#[bootrom_v2]
#[alias = __extendsfdf2vfp]
#[aeabi = __aeabi_f2d]
extern "C" fn __extendsfdf2(f: f32) -> f64 {
if f.is_not_finite() {
return f64::from_repr(
// Not finite
f64::EXPONENT_MASK |
// Preserve NaN or inf
((f.repr() & f32::SIGNIFICAND_MASK) as u64) |
// Preserve sign
((f.repr() & f32::SIGN_MASK) as u64) << (f64::BITS-f32::BITS)
);
}
rom_data::float_funcs::float_to_double(f)
}
#[bootrom_v2]
#[alias = __truncdfsf2vfp]
#[aeabi = __aeabi_d2f]
extern "C" fn __truncdfsf2(f: f64) -> f32 {
if f.is_not_finite() {
let mut repr: u32 =
// Not finite
f32::EXPONENT_MASK |
// Preserve sign
((f.repr() & f64::SIGN_MASK) >> (f64::BITS-f32::BITS)) as u32;
// Set NaN
if (f.repr() & f64::SIGNIFICAND_MASK) != 0 {
repr |= 1;
}
return f32::from_repr(repr);
}
rom_data::double_funcs::double_to_float(f)
}
}

141
embassy-rp/src/float/div.rs Normal file
View File

@ -0,0 +1,141 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs
use super::Float;
use crate::rom_data;
// Make sure this stays as a separate call, because when it's inlined the
// compiler will move the save of the registers used to contain the divider
// state into the function prologue. That save and restore (push/pop) takes
// longer than the actual division, so doing it in the common case where
// they are not required wastes a lot of time.
#[inline(never)]
#[cold]
fn save_divider_and_call<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let sio = rp_pac::SIO;
unsafe {
// Since we can't save the signed-ness of the calculation, we have to make
// sure that there's at least an 8 cycle delay before we read the result.
// The Pico SDK ensures this by using a 6 cycle push and two 1 cycle reads.
// Since we can't be sure the Rust implementation will optimize to the same,
// just use an explicit wait.
while !sio.div().csr().read().ready() {}
// Read the quotient last, since that's what clears the dirty flag
let dividend = sio.div().udividend().read();
let divisor = sio.div().udivisor().read();
let remainder = sio.div().remainder().read();
let quotient = sio.div().quotient().read();
// If we get interrupted here (before a write sets the DIRTY flag) its fine, since
// we have the full state, so the interruptor doesn't have to restore it. Once the
// write happens and the DIRTY flag is set, the interruptor becomes responsible for
// restoring our state.
let result = f();
// If we are interrupted here, then the interruptor will start an incorrect calculation
// using a wrong divisor, but we'll restore the divisor and result ourselves correctly.
// This sets DIRTY, so any interruptor will save the state.
sio.div().udividend().write_value(dividend);
// If we are interrupted here, the the interruptor may start the calculation using
// incorrectly signed inputs, but we'll restore the result ourselves.
// This sets DIRTY, so any interruptor will save the state.
sio.div().udivisor().write_value(divisor);
// If we are interrupted here, the interruptor will have restored everything but the
// quotient may be wrongly signed. If the calculation started by the above writes is
// still ongoing it is stopped, so it won't replace the result we're restoring.
// DIRTY and READY set, but only DIRTY matters to make the interruptor save the state.
sio.div().remainder().write_value(remainder);
// State fully restored after the quotient write. This sets both DIRTY and READY, so
// whatever we may have interrupted can read the result.
sio.div().quotient().write_value(quotient);
result
}
}
fn save_divider<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let sio = rp_pac::SIO;
if unsafe { !sio.div().csr().read().dirty() } {
// Not dirty, so nothing is waiting for the calculation. So we can just
// issue it directly without a save/restore.
f()
} else {
save_divider_and_call(f)
}
}
trait ROMDiv {
fn rom_div(self, b: Self) -> Self;
}
impl ROMDiv for f32 {
fn rom_div(self, b: Self) -> Self {
// ROM implementation uses the hardware divider, so we have to save it
save_divider(|| rom_data::float_funcs::fdiv(self, b))
}
}
impl ROMDiv for f64 {
fn rom_div(self, b: Self) -> Self {
// ROM implementation uses the hardware divider, so we have to save it
save_divider(|| rom_data::double_funcs::ddiv(self, b))
}
}
fn div<F: Float + ROMDiv>(a: F, b: F) -> F {
if a.is_not_finite() {
if b.is_not_finite() {
// inf/NaN / inf/NaN = NaN
return F::NAN;
}
if b.is_zero() {
// inf/NaN / 0 = NaN
return F::NAN;
}
return if b.is_sign_negative() {
// [+/-]inf/NaN / (-X) = [-/+]inf/NaN
a.negate()
} else {
// [-]inf/NaN / X = [-]inf/NaN
a
};
}
if b.is_nan() {
// X / NaN = NaN
return b;
}
// ROM handles X / 0 = [-]inf and X / [-]inf = [-]0, so we only
// need to catch 0 / 0
if b.is_zero() && a.is_zero() {
return F::NAN;
}
a.rom_div(b)
}
intrinsics! {
#[alias = __divsf3vfp]
#[aeabi = __aeabi_fdiv]
extern "C" fn __divsf3(a: f32, b: f32) -> f32 {
div(a, b)
}
#[bootrom_v2]
#[alias = __divdf3vfp]
#[aeabi = __aeabi_ddiv]
extern "C" fn __divdf3(a: f64, b: f64) -> f64 {
div(a, b)
}
}

View File

@ -0,0 +1,239 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/functions.rs
use crate::float::{Float, Int};
use crate::rom_data;
trait ROMFunctions {
fn sqrt(self) -> Self;
fn ln(self) -> Self;
fn exp(self) -> Self;
fn sin(self) -> Self;
fn cos(self) -> Self;
fn tan(self) -> Self;
fn atan2(self, y: Self) -> Self;
fn to_trig_range(self) -> Self;
}
impl ROMFunctions for f32 {
fn sqrt(self) -> Self {
rom_data::float_funcs::fsqrt(self)
}
fn ln(self) -> Self {
rom_data::float_funcs::fln(self)
}
fn exp(self) -> Self {
rom_data::float_funcs::fexp(self)
}
fn sin(self) -> Self {
rom_data::float_funcs::fsin(self)
}
fn cos(self) -> Self {
rom_data::float_funcs::fcos(self)
}
fn tan(self) -> Self {
rom_data::float_funcs::ftan(self)
}
fn atan2(self, y: Self) -> Self {
rom_data::float_funcs::fatan2(self, y)
}
fn to_trig_range(self) -> Self {
// -128 < X < 128, logic from the Pico SDK
let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS;
if exponent < 134 {
self
} else {
self % (core::f32::consts::PI * 2.0)
}
}
}
impl ROMFunctions for f64 {
fn sqrt(self) -> Self {
rom_data::double_funcs::dsqrt(self)
}
fn ln(self) -> Self {
rom_data::double_funcs::dln(self)
}
fn exp(self) -> Self {
rom_data::double_funcs::dexp(self)
}
fn sin(self) -> Self {
rom_data::double_funcs::dsin(self)
}
fn cos(self) -> Self {
rom_data::double_funcs::dcos(self)
}
fn tan(self) -> Self {
rom_data::double_funcs::dtan(self)
}
fn atan2(self, y: Self) -> Self {
rom_data::double_funcs::datan2(self, y)
}
fn to_trig_range(self) -> Self {
// -1024 < X < 1024, logic from the Pico SDK
let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS;
if exponent < 1033 {
self
} else {
self % (core::f64::consts::PI * 2.0)
}
}
}
fn is_negative_nonzero_or_nan<F: Float>(f: F) -> bool {
let repr = f.repr();
if (repr & F::SIGN_MASK) != F::Int::ZERO {
// Negative, so anything other than exactly zero
return (repr & (!F::SIGN_MASK)) != F::Int::ZERO;
}
// NaN
(repr & (F::EXPONENT_MASK | F::SIGNIFICAND_MASK)) > F::EXPONENT_MASK
}
fn sqrt<F: Float + ROMFunctions>(f: F) -> F {
if is_negative_nonzero_or_nan(f) {
F::NAN
} else {
f.sqrt()
}
}
fn ln<F: Float + ROMFunctions>(f: F) -> F {
if is_negative_nonzero_or_nan(f) {
F::NAN
} else {
f.ln()
}
}
fn exp<F: Float + ROMFunctions>(f: F) -> F {
if f.is_nan() {
F::NAN
} else {
f.exp()
}
}
fn sin<F: Float + ROMFunctions>(f: F) -> F {
if f.is_not_finite() {
F::NAN
} else {
f.to_trig_range().sin()
}
}
fn cos<F: Float + ROMFunctions>(f: F) -> F {
if f.is_not_finite() {
F::NAN
} else {
f.to_trig_range().cos()
}
}
fn tan<F: Float + ROMFunctions>(f: F) -> F {
if f.is_not_finite() {
F::NAN
} else {
f.to_trig_range().tan()
}
}
fn atan2<F: Float + ROMFunctions>(x: F, y: F) -> F {
if x.is_nan() || y.is_nan() {
F::NAN
} else {
x.to_trig_range().atan2(y)
}
}
// Name collisions
mod intrinsics {
intrinsics! {
extern "C" fn sqrtf(f: f32) -> f32 {
super::sqrt(f)
}
#[bootrom_v2]
extern "C" fn sqrt(f: f64) -> f64 {
super::sqrt(f)
}
extern "C" fn logf(f: f32) -> f32 {
super::ln(f)
}
#[bootrom_v2]
extern "C" fn log(f: f64) -> f64 {
super::ln(f)
}
extern "C" fn expf(f: f32) -> f32 {
super::exp(f)
}
#[bootrom_v2]
extern "C" fn exp(f: f64) -> f64 {
super::exp(f)
}
#[slower_than_default]
extern "C" fn sinf(f: f32) -> f32 {
super::sin(f)
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn sin(f: f64) -> f64 {
super::sin(f)
}
#[slower_than_default]
extern "C" fn cosf(f: f32) -> f32 {
super::cos(f)
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn cos(f: f64) -> f64 {
super::cos(f)
}
#[slower_than_default]
extern "C" fn tanf(f: f32) -> f32 {
super::tan(f)
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn tan(f: f64) -> f64 {
super::tan(f)
}
// Questionable gain
#[bootrom_v2]
extern "C" fn atan2f(a: f32, b: f32) -> f32 {
super::atan2(a, b)
}
// Questionable gain
#[bootrom_v2]
extern "C" fn atan2(a: f64, b: f64) -> f64 {
super::atan2(a, b)
}
}
}

149
embassy-rp/src/float/mod.rs Normal file
View File

@ -0,0 +1,149 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mod.rs
use core::ops;
// Borrowed and simplified from compiler-builtins so we can use bit ops
// on floating point without macro soup.
pub(crate) trait Int:
Copy
+ core::fmt::Debug
+ PartialEq
+ PartialOrd
+ ops::AddAssign
+ ops::SubAssign
+ ops::BitAndAssign
+ ops::BitOrAssign
+ ops::BitXorAssign
+ ops::ShlAssign<i32>
+ ops::ShrAssign<u32>
+ ops::Add<Output = Self>
+ ops::Sub<Output = Self>
+ ops::Div<Output = Self>
+ ops::Shl<u32, Output = Self>
+ ops::Shr<u32, Output = Self>
+ ops::BitOr<Output = Self>
+ ops::BitXor<Output = Self>
+ ops::BitAnd<Output = Self>
+ ops::Not<Output = Self>
{
const ZERO: Self;
}
macro_rules! int_impl {
($ty:ty) => {
impl Int for $ty {
const ZERO: Self = 0;
}
};
}
int_impl!(u32);
int_impl!(u64);
pub(crate) trait Float:
Copy
+ core::fmt::Debug
+ PartialEq
+ PartialOrd
+ ops::AddAssign
+ ops::MulAssign
+ ops::Add<Output = Self>
+ ops::Sub<Output = Self>
+ ops::Div<Output = Self>
+ ops::Rem<Output = Self>
{
/// A uint of the same with as the float
type Int: Int;
/// NaN representation for the float
const NAN: Self;
/// The bitwidth of the float type
const BITS: u32;
/// The bitwidth of the significand
const SIGNIFICAND_BITS: u32;
/// A mask for the sign bit
const SIGN_MASK: Self::Int;
/// A mask for the significand
const SIGNIFICAND_MASK: Self::Int;
/// A mask for the exponent
const EXPONENT_MASK: Self::Int;
/// Returns `self` transmuted to `Self::Int`
fn repr(self) -> Self::Int;
/// Returns a `Self::Int` transmuted back to `Self`
fn from_repr(a: Self::Int) -> Self;
/// Return a sign swapped `self`
fn negate(self) -> Self;
/// Returns true if `self` is either NaN or infinity
fn is_not_finite(self) -> bool {
(self.repr() & Self::EXPONENT_MASK) == Self::EXPONENT_MASK
}
/// Returns true if `self` is infinity
fn is_infinity(self) -> bool {
(self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) == Self::EXPONENT_MASK
}
/// Returns true if `self is NaN
fn is_nan(self) -> bool {
(self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) > Self::EXPONENT_MASK
}
/// Returns true if `self` is negative
fn is_sign_negative(self) -> bool {
(self.repr() & Self::SIGN_MASK) != Self::Int::ZERO
}
/// Returns true if `self` is zero (either sign)
fn is_zero(self) -> bool {
(self.repr() & (Self::SIGNIFICAND_MASK | Self::EXPONENT_MASK)) == Self::Int::ZERO
}
}
macro_rules! float_impl {
($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => {
impl Float for $ty {
type Int = $ity;
const NAN: Self = <$ty>::NAN;
const BITS: u32 = $bits;
const SIGNIFICAND_BITS: u32 = $significand_bits;
const SIGN_MASK: Self::Int = 1 << (Self::BITS - 1);
const SIGNIFICAND_MASK: Self::Int = (1 << Self::SIGNIFICAND_BITS) - 1;
const EXPONENT_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIGNIFICAND_MASK);
fn repr(self) -> Self::Int {
self.to_bits()
}
fn from_repr(a: Self::Int) -> Self {
Self::from_bits(a)
}
fn negate(self) -> Self {
-self
}
}
};
}
float_impl!(f32, u32, 32, 23);
float_impl!(f64, u64, 64, 52);
mod add_sub;
mod cmp;
mod conv;
mod div;
mod functions;
mod mul;

View File

@ -0,0 +1,70 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mul.rs
use super::Float;
use crate::rom_data;
trait ROMMul {
fn rom_mul(self, b: Self) -> Self;
}
impl ROMMul for f32 {
fn rom_mul(self, b: Self) -> Self {
rom_data::float_funcs::fmul(self, b)
}
}
impl ROMMul for f64 {
fn rom_mul(self, b: Self) -> Self {
rom_data::double_funcs::dmul(self, b)
}
}
fn mul<F: Float + ROMMul>(a: F, b: F) -> F {
if a.is_not_finite() {
if b.is_zero() {
// [-]inf/NaN * 0 = NaN
return F::NAN;
}
return if b.is_sign_negative() {
// [+/-]inf/NaN * (-X) = [-/+]inf/NaN
a.negate()
} else {
// [-]inf/NaN * X = [-]inf/NaN
a
};
}
if b.is_not_finite() {
if a.is_zero() {
// 0 * [-]inf/NaN = NaN
return F::NAN;
}
return if b.is_sign_negative() {
// (-X) * [+/-]inf/NaN = [-/+]inf/NaN
b.negate()
} else {
// X * [-]inf/NaN = [-]inf/NaN
b
};
}
a.rom_mul(b)
}
intrinsics! {
#[alias = __mulsf3vfp]
#[aeabi = __aeabi_fmul]
extern "C" fn __mulsf3(a: f32, b: f32) -> f32 {
mul(a, b)
}
#[bootrom_v2]
#[alias = __muldf3vfp]
#[aeabi = __aeabi_dmul]
extern "C" fn __muldf3(a: f64, b: f64) -> f64 {
mul(a, b)
}
}

View File

@ -9,7 +9,7 @@ use embassy_sync::waitqueue::AtomicWaker;
use crate::pac::common::{Reg, RW};
use crate::pac::SIO;
use crate::{interrupt, pac, peripherals, Peripheral};
use crate::{interrupt, pac, peripherals, Peripheral, RegExt};
const PIN_COUNT: usize = 30;
const NEW_AW: AtomicWaker = AtomicWaker::new();
@ -136,16 +136,11 @@ pub enum InterruptTrigger {
AnyEdge,
}
impl InterruptTrigger {
fn from_u32(value: u32) -> Option<InterruptTrigger> {
match value {
1 => Some(InterruptTrigger::LevelLow),
2 => Some(InterruptTrigger::LevelHigh),
3 => Some(InterruptTrigger::EdgeLow),
4 => Some(InterruptTrigger::EdgeHigh),
_ => None,
}
}
pub(crate) unsafe fn init() {
let irq = interrupt::IO_IRQ_BANK0::steal();
irq.disable();
irq.set_priority(interrupt::Priority::P3);
irq.enable();
}
#[interrupt]
@ -166,27 +161,15 @@ unsafe fn IO_IRQ_BANK0() {
let pin_group = (pin % 8) as usize;
let event = (intsx.read().0 >> pin_group * 4) & 0xf as u32;
if let Some(trigger) = InterruptTrigger::from_u32(event) {
critical_section::with(|_| {
proc_intx.inte(pin / 8).modify(|w| match trigger {
InterruptTrigger::AnyEdge => {
w.set_edge_high(pin_group, false);
w.set_edge_low(pin_group, false);
}
InterruptTrigger::LevelHigh => {
trace!("IO_IRQ_BANK0 pin {} LevelHigh triggered", pin);
w.set_level_high(pin_group, false);
}
InterruptTrigger::LevelLow => {
w.set_level_low(pin_group, false);
}
InterruptTrigger::EdgeHigh => {
w.set_edge_high(pin_group, false);
}
InterruptTrigger::EdgeLow => {
w.set_edge_low(pin_group, false);
}
});
// no more than one event can be awaited per pin at any given time, so
// we can just clear all interrupt enables for that pin without having
// to check which event was signalled.
if event != 0 {
proc_intx.inte(pin / 8).write_clear(|w| {
w.set_edge_high(pin_group, true);
w.set_edge_low(pin_group, true);
w.set_level_high(pin_group, true);
w.set_level_low(pin_group, true);
});
INTERRUPT_WAKERS[pin as usize].wake();
}
@ -203,16 +186,26 @@ impl<'d, T: Pin> InputFuture<'d, T> {
pub fn new(pin: impl Peripheral<P = T> + 'd, level: InterruptTrigger) -> Self {
into_ref!(pin);
unsafe {
let irq = interrupt::IO_IRQ_BANK0::steal();
irq.disable();
irq.set_priority(interrupt::Priority::P3);
let pin_group = (pin.pin() % 8) as usize;
// first, clear the INTR register bits. without this INTR will still
// contain reports of previous edges, causing the IRQ to fire early
// on stale state. clearing these means that we can only detect edges
// that occur *after* the clear happened, but since both this and the
// alternative are fundamentally racy it's probably fine.
// (the alternative being checking the current level and waiting for
// its inverse, but that requires reading the current level and thus
// missing anything that happened before the level was read.)
pac::IO_BANK0.intr(pin.pin() as usize / 8).write(|w| {
w.set_edge_high(pin_group, true);
w.set_edge_low(pin_group, true);
});
// Each INTR register is divided into 8 groups, one group for each
// pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW,
// and EGDE_HIGH.
let pin_group = (pin.pin() % 8) as usize;
critical_section::with(|_| {
pin.int_proc().inte((pin.pin() / 8) as usize).modify(|w| match level {
pin.int_proc()
.inte((pin.pin() / 8) as usize)
.write_set(|w| match level {
InterruptTrigger::LevelHigh => {
trace!("InputFuture::new enable LevelHigh for pin {}", pin.pin());
w.set_level_high(pin_group, true);
@ -227,12 +220,10 @@ impl<'d, T: Pin> InputFuture<'d, T> {
w.set_edge_low(pin_group, true);
}
InterruptTrigger::AnyEdge => {
// noop
w.set_edge_high(pin_group, true);
w.set_edge_low(pin_group, true);
}
});
});
irq.enable();
}
Self { pin, level }
@ -257,47 +248,21 @@ impl<'d, T: Pin> Future for InputFuture<'d, T> {
// LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin.
let pin_group = (self.pin.pin() % 8) as usize;
// This should check the the level of the interrupt trigger level of
// the pin and if it has been disabled that means it was done by the
// interrupt service routine, so we then know that the event/trigger
// happened and Poll::Ready will be returned.
trace!("{:?} for pin {}", self.level, self.pin.pin());
match self.level {
InterruptTrigger::AnyEdge => {
if !inte.edge_high(pin_group) && !inte.edge_low(pin_group) {
#[rustfmt::skip]
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
return Poll::Ready(());
}
}
InterruptTrigger::LevelHigh => {
if !inte.level_high(pin_group) {
#[rustfmt::skip]
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
return Poll::Ready(());
}
}
InterruptTrigger::LevelLow => {
if !inte.level_low(pin_group) {
#[rustfmt::skip]
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
return Poll::Ready(());
}
}
InterruptTrigger::EdgeHigh => {
if !inte.edge_high(pin_group) {
#[rustfmt::skip]
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
return Poll::Ready(());
}
}
InterruptTrigger::EdgeLow => {
if !inte.edge_low(pin_group) {
#[rustfmt::skip]
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
return Poll::Ready(());
}
}
// since the interrupt handler clears all INTE flags we'll check that
// all have been cleared and unconditionally return Ready(()) if so.
// we don't need further handshaking since only a single event wait
// is possible for any given pin at any given time.
if !inte.edge_high(pin_group)
&& !inte.edge_low(pin_group)
&& !inte.level_high(pin_group)
&& !inte.level_low(pin_group)
{
trace!(
"{:?} for pin {} was cleared, return Poll::Ready",
self.level,
self.pin.pin()
);
return Poll::Ready(());
}
trace!("InputFuture::poll return Poll::Pending");
Poll::Pending
@ -644,23 +609,17 @@ impl<'d, T: Pin> Flex<'d, T> {
#[inline]
pub async fn wait_for_rising_edge(&mut self) {
self.wait_for_low().await;
self.wait_for_high().await;
InputFuture::new(&mut self.pin, InterruptTrigger::EdgeHigh).await;
}
#[inline]
pub async fn wait_for_falling_edge(&mut self) {
self.wait_for_high().await;
self.wait_for_low().await;
InputFuture::new(&mut self.pin, InterruptTrigger::EdgeLow).await;
}
#[inline]
pub async fn wait_for_any_edge(&mut self) {
if self.is_high() {
self.wait_for_low().await;
} else {
self.wait_for_high().await;
}
InputFuture::new(&mut self.pin, InterruptTrigger::AnyEdge).await;
}
}

View File

@ -617,6 +617,28 @@ mod eh02 {
self.blocking_write_read(address, bytes, buffer)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> {
type Error = Error;
fn exec(
&mut self,
address: u8,
operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
Self::setup(address.into())?;
for i in 0..operations.len() {
let last = i == operations.len() - 1;
match &mut operations[i] {
embedded_hal_02::blocking::i2c::Operation::Read(buf) => {
self.read_blocking_internal(buf, false, last)?
}
embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
}
}
Ok(())
}
}
}
#[cfg(feature = "unstable-traits")]

View File

@ -274,3 +274,203 @@ macro_rules! intrinsics {
intrinsics!($($rest)*);
};
}
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/sio.rs
// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers
// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put
// the remainder in the high order 32 bits of a 64 bit result. We can also
// alias the division operators to these for a similar reason r0 is the
// result either way and r1 a scratch register, so the caller can't assume it
// retains the argument value.
#[cfg(target_arch = "arm")]
core::arch::global_asm!(
".macro hwdivider_head",
"ldr r2, =(0xd0000000)", // SIO_BASE
// Check the DIRTY state of the divider by shifting it into the C
// status bit.
"ldr r3, [r2, #0x078]", // DIV_CSR
"lsrs r3, #2", // DIRTY = 1, so shift 2 down
// We only need to save the state when DIRTY, otherwise we can just do the
// division directly.
"bcs 2f",
"1:",
// Do the actual division now, we're either not DIRTY, or we've saved the
// state and branched back here so it's safe now.
".endm",
".macro hwdivider_tail",
// 8 cycle delay to wait for the result. Each branch takes two cycles
// and fits into a 2-byte Thumb instruction, so this is smaller than
// 8 NOPs.
"b 3f",
"3: b 3f",
"3: b 3f",
"3: b 3f",
"3:",
// Read the quotient last, since that's what clears the dirty flag.
"ldr r1, [r2, #0x074]", // DIV_REMAINDER
"ldr r0, [r2, #0x070]", // DIV_QUOTIENT
// Either return to the caller or back to the state restore.
"bx lr",
"2:",
// Since we can't save the signed-ness of the calculation, we have to make
// sure that there's at least an 8 cycle delay before we read the result.
// The push takes 5 cycles, and we've already spent at least 7 checking
// the DIRTY state to get here.
"push {{r4-r6, lr}}",
// Read the quotient last, since that's what clears the dirty flag.
"ldr r3, [r2, #0x060]", // DIV_UDIVIDEND
"ldr r4, [r2, #0x064]", // DIV_UDIVISOR
"ldr r5, [r2, #0x074]", // DIV_REMAINDER
"ldr r6, [r2, #0x070]", // DIV_QUOTIENT
// If we get interrupted here (before a write sets the DIRTY flag) it's
// fine, since we have the full state, so the interruptor doesn't have to
// restore it. Once the write happens and the DIRTY flag is set, the
// interruptor becomes responsible for restoring our state.
"bl 1b",
// If we are interrupted here, then the interruptor will start an incorrect
// calculation using a wrong divisor, but we'll restore the divisor and
// result ourselves correctly. This sets DIRTY, so any interruptor will
// save the state.
"str r3, [r2, #0x060]", // DIV_UDIVIDEND
// If we are interrupted here, the the interruptor may start the
// calculation using incorrectly signed inputs, but we'll restore the
// result ourselves. This sets DIRTY, so any interruptor will save the
// state.
"str r4, [r2, #0x064]", // DIV_UDIVISOR
// If we are interrupted here, the interruptor will have restored
// everything but the quotient may be wrongly signed. If the calculation
// started by the above writes is still ongoing it is stopped, so it won't
// replace the result we're restoring. DIRTY and READY set, but only
// DIRTY matters to make the interruptor save the state.
"str r5, [r2, #0x074]", // DIV_REMAINDER
// State fully restored after the quotient write. This sets both DIRTY
// and READY, so whatever we may have interrupted can read the result.
"str r6, [r2, #0x070]", // DIV_QUOTIENT
"pop {{r4-r6, pc}}",
".endm",
);
macro_rules! division_function {
(
$name:ident $($intrinsic:ident)* ( $argty:ty ) {
$($begin:literal),+
}
) => {
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
core::arch::global_asm!(
// Mangle the name slightly, since this is a global symbol.
concat!(".section .text._erphal_", stringify!($name)),
concat!(".global _erphal_", stringify!($name)),
concat!(".type _erphal_", stringify!($name), ", %function"),
".align 2",
concat!("_erphal_", stringify!($name), ":"),
$(
concat!(".global ", stringify!($intrinsic)),
concat!(".type ", stringify!($intrinsic), ", %function"),
concat!(stringify!($intrinsic), ":"),
)*
"hwdivider_head",
$($begin),+ ,
"hwdivider_tail",
);
#[cfg(all(target_arch = "arm", not(feature = "intrinsics")))]
core::arch::global_asm!(
// Mangle the name slightly, since this is a global symbol.
concat!(".section .text._erphal_", stringify!($name)),
concat!(".global _erphal_", stringify!($name)),
concat!(".type _erphal_", stringify!($name), ", %function"),
".align 2",
concat!("_erphal_", stringify!($name), ":"),
"hwdivider_head",
$($begin),+ ,
"hwdivider_tail",
);
#[cfg(target_arch = "arm")]
extern "aapcs" {
// Connect a local name to global symbol above through FFI.
#[link_name = concat!("_erphal_", stringify!($name)) ]
fn $name(n: $argty, d: $argty) -> u64;
}
#[cfg(not(target_arch = "arm"))]
#[allow(unused_variables)]
unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 }
};
}
division_function! {
unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) {
"str r0, [r2, #0x060]", // DIV_UDIVIDEND
"str r1, [r2, #0x064]" // DIV_UDIVISOR
}
}
division_function! {
signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) {
"str r0, [r2, #0x068]", // DIV_SDIVIDEND
"str r1, [r2, #0x06c]" // DIV_SDIVISOR
}
}
fn divider_unsigned(n: u32, d: u32) -> DivResult<u32> {
let packed = unsafe { unsigned_divmod(n, d) };
DivResult {
quotient: packed as u32,
remainder: (packed >> 32) as u32,
}
}
fn divider_signed(n: i32, d: i32) -> DivResult<i32> {
let packed = unsafe { signed_divmod(n, d) };
// Double casts to avoid sign extension
DivResult {
quotient: packed as u32 as i32,
remainder: (packed >> 32) as u32 as i32,
}
}
/// Result of divide/modulo operation
struct DivResult<T> {
/// The quotient of divide/modulo operation
pub quotient: T,
/// The remainder of divide/modulo operation
pub remainder: T,
}
intrinsics! {
extern "C" fn __udivsi3(n: u32, d: u32) -> u32 {
divider_unsigned(n, d).quotient
}
extern "C" fn __umodsi3(n: u32, d: u32) -> u32 {
divider_unsigned(n, d).remainder
}
extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 {
let quo_rem = divider_unsigned(n, d);
if let Some(rem) = rem {
*rem = quo_rem.remainder;
}
quo_rem.quotient
}
extern "C" fn __divsi3(n: i32, d: i32) -> i32 {
divider_signed(n, d).quotient
}
extern "C" fn __modsi3(n: i32, d: i32) -> i32 {
divider_signed(n, d).remainder
}
extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 {
let quo_rem = divider_signed(n, d);
*rem = quo_rem.remainder;
quo_rem.quotient
}
}

View File

@ -11,18 +11,16 @@ mod critical_section_impl;
mod intrinsics;
pub mod adc;
pub mod clocks;
pub mod dma;
pub mod flash;
mod float;
pub mod gpio;
pub mod i2c;
pub mod interrupt;
#[cfg(feature = "pio")]
pub mod pio;
#[cfg(feature = "pio")]
pub mod pio_instr_util;
#[cfg(feature = "pio")]
pub mod relocate;
pub mod multicore;
pub mod pwm;
mod reset;
pub mod rom_data;
pub mod rtc;
pub mod spi;
@ -31,22 +29,22 @@ pub mod timer;
pub mod uart;
#[cfg(feature = "nightly")]
pub mod usb;
pub mod clocks;
pub mod flash;
pub mod multicore;
mod reset;
pub mod watchdog;
// Reexports
// PIO
// TODO: move `pio_instr_util` and `relocate` to inside `pio`
pub mod pio;
pub mod pio_instr_util;
pub mod relocate;
// Reexports
pub use embassy_cortex_m::executor;
pub use embassy_cortex_m::interrupt::_export::interrupt;
pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
#[cfg(feature = "unstable-pac")]
pub use rp2040_pac2 as pac;
pub use rp_pac as pac;
#[cfg(not(feature = "unstable-pac"))]
pub(crate) use rp2040_pac2 as pac;
pub(crate) use rp_pac as pac;
embassy_hal_common::peripherals! {
PIN_0,
@ -108,6 +106,15 @@ embassy_hal_common::peripherals! {
DMA_CH10,
DMA_CH11,
PWM_CH0,
PWM_CH1,
PWM_CH2,
PWM_CH3,
PWM_CH4,
PWM_CH5,
PWM_CH6,
PWM_CH7,
USB,
RTC,
@ -149,6 +156,8 @@ pub fn init(_config: config::Config) -> Peripherals {
#[cfg(feature = "time-driver")]
timer::init();
dma::init();
pio::init();
gpio::init();
}
peripherals

View File

@ -33,7 +33,7 @@ use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
use crate::interrupt::{Interrupt, InterruptExt};
use crate::peripherals::CORE1;
use crate::{interrupt, pac};
use crate::{gpio, interrupt, pac};
const PAUSE_TOKEN: u32 = 0xDEADBEEF;
const RESUME_TOKEN: u32 = !0xDEADBEEF;
@ -68,6 +68,9 @@ fn install_stack_guard(stack_bottom: *mut usize) {
#[inline(always)]
fn core1_setup(stack_bottom: *mut usize) {
install_stack_guard(stack_bottom);
unsafe {
gpio::init();
}
}
/// Data type for a properly aligned stack of N bytes

File diff suppressed because it is too large Load Diff

338
embassy-rp/src/pwm.rs Normal file
View File

@ -0,0 +1,338 @@
//! Pulse Width Modulation (PWM)
use embassy_embedded_hal::SetConfig;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use fixed::traits::ToFixed;
use fixed::FixedU16;
use pac::pwm::regs::{ChDiv, Intr};
use pac::pwm::vals::Divmode;
use crate::gpio::sealed::Pin as _;
use crate::gpio::{AnyPin, Pin as GpioPin};
use crate::{pac, peripherals, RegExt};
#[non_exhaustive]
#[derive(Clone)]
pub struct Config {
pub invert_a: bool,
pub invert_b: bool,
pub phase_correct: bool,
pub enable: bool,
pub divider: fixed::FixedU16<fixed::types::extra::U4>,
pub compare_a: u16,
pub compare_b: u16,
pub top: u16,
}
impl Default for Config {
fn default() -> Self {
Self {
invert_a: false,
invert_b: false,
phase_correct: false,
enable: true, // differs from reset value
divider: 1.to_fixed(),
compare_a: 0,
compare_b: 0,
top: 0xffff,
}
}
}
pub enum InputMode {
Level,
RisingEdge,
FallingEdge,
}
impl From<InputMode> for Divmode {
fn from(value: InputMode) -> Self {
match value {
InputMode::Level => Divmode::LEVEL,
InputMode::RisingEdge => Divmode::RISE,
InputMode::FallingEdge => Divmode::FALL,
}
}
}
pub struct Pwm<'d, T: Channel> {
inner: PeripheralRef<'d, T>,
pin_a: Option<PeripheralRef<'d, AnyPin>>,
pin_b: Option<PeripheralRef<'d, AnyPin>>,
}
impl<'d, T: Channel> Pwm<'d, T> {
fn new_inner(
inner: impl Peripheral<P = T> + 'd,
a: Option<PeripheralRef<'d, AnyPin>>,
b: Option<PeripheralRef<'d, AnyPin>>,
config: Config,
divmode: Divmode,
) -> Self {
into_ref!(inner);
let p = inner.regs();
unsafe {
p.csr().modify(|w| {
w.set_divmode(divmode);
w.set_en(false);
});
p.ctr().write(|w| w.0 = 0);
Self::configure(p, &config);
if let Some(pin) = &a {
pin.io().ctrl().write(|w| w.set_funcsel(4));
}
if let Some(pin) = &b {
pin.io().ctrl().write(|w| w.set_funcsel(4));
}
}
Self {
inner,
pin_a: a.into(),
pin_b: b.into(),
}
}
#[inline]
pub fn new_free(inner: impl Peripheral<P = T> + 'd, config: Config) -> Self {
Self::new_inner(inner, None, None, config, Divmode::DIV)
}
#[inline]
pub fn new_output_a(
inner: impl Peripheral<P = T> + 'd,
a: impl Peripheral<P = impl PwmPinA<T>> + 'd,
config: Config,
) -> Self {
into_ref!(a);
Self::new_inner(inner, Some(a.map_into()), None, config, Divmode::DIV)
}
#[inline]
pub fn new_output_b(
inner: impl Peripheral<P = T> + 'd,
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
config: Config,
) -> Self {
into_ref!(b);
Self::new_inner(inner, None, Some(b.map_into()), config, Divmode::DIV)
}
#[inline]
pub fn new_output_ab(
inner: impl Peripheral<P = T> + 'd,
a: impl Peripheral<P = impl PwmPinA<T>> + 'd,
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
config: Config,
) -> Self {
into_ref!(a, b);
Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, Divmode::DIV)
}
#[inline]
pub fn new_input(
inner: impl Peripheral<P = T> + 'd,
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
mode: InputMode,
config: Config,
) -> Self {
into_ref!(b);
Self::new_inner(inner, None, Some(b.map_into()), config, mode.into())
}
#[inline]
pub fn new_output_input(
inner: impl Peripheral<P = T> + 'd,
a: impl Peripheral<P = impl PwmPinA<T>> + 'd,
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
mode: InputMode,
config: Config,
) -> Self {
into_ref!(a, b);
Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, mode.into())
}
fn configure(p: pac::pwm::Channel, config: &Config) {
if config.divider > FixedU16::<fixed::types::extra::U4>::from_bits(0xFF_F) {
panic!("Requested divider is too large");
}
unsafe {
p.div().write_value(ChDiv(config.divider.to_bits() as u32));
p.cc().write(|w| {
w.set_a(config.compare_a);
w.set_b(config.compare_b);
});
p.top().write(|w| w.set_top(config.top));
p.csr().modify(|w| {
w.set_a_inv(config.invert_a);
w.set_b_inv(config.invert_b);
w.set_ph_correct(config.phase_correct);
w.set_en(config.enable);
});
}
}
#[inline]
pub unsafe fn phase_advance(&mut self) {
let p = self.inner.regs();
p.csr().write_set(|w| w.set_ph_adv(true));
while p.csr().read().ph_adv() {}
}
#[inline]
pub unsafe fn phase_retard(&mut self) {
let p = self.inner.regs();
p.csr().write_set(|w| w.set_ph_ret(true));
while p.csr().read().ph_ret() {}
}
#[inline]
pub fn counter(&self) -> u16 {
unsafe { self.inner.regs().ctr().read().ctr() }
}
#[inline]
pub fn set_counter(&self, ctr: u16) {
unsafe { self.inner.regs().ctr().write(|w| w.set_ctr(ctr)) }
}
#[inline]
pub fn wait_for_wrap(&mut self) {
while !self.wrapped() {}
self.clear_wrapped();
}
#[inline]
pub fn wrapped(&mut self) -> bool {
unsafe { pac::PWM.intr().read().0 & self.bit() != 0 }
}
#[inline]
pub fn clear_wrapped(&mut self) {
unsafe {
pac::PWM.intr().write_value(Intr(self.bit() as _));
}
}
#[inline]
fn bit(&self) -> u32 {
1 << self.inner.number() as usize
}
}
pub struct PwmBatch(u32);
impl PwmBatch {
#[inline]
pub fn enable(&mut self, pwm: &Pwm<'_, impl Channel>) {
self.0 |= pwm.bit();
}
#[inline]
pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) {
let mut en = PwmBatch(0);
batch(&mut en);
unsafe {
if enabled {
pac::PWM.en().write_set(|w| w.0 = en.0);
} else {
pac::PWM.en().write_clear(|w| w.0 = en.0);
}
}
}
}
impl<'d, T: Channel> Drop for Pwm<'d, T> {
fn drop(&mut self) {
unsafe {
self.inner.regs().csr().write_clear(|w| w.set_en(false));
if let Some(pin) = &self.pin_a {
pin.io().ctrl().write(|w| w.set_funcsel(31));
}
if let Some(pin) = &self.pin_b {
pin.io().ctrl().write(|w| w.set_funcsel(31));
}
}
}
}
mod sealed {
pub trait Channel {}
}
pub trait Channel: Peripheral<P = Self> + sealed::Channel + Sized + 'static {
fn number(&self) -> u8;
fn regs(&self) -> pac::pwm::Channel {
pac::PWM.ch(self.number() as _)
}
}
macro_rules! channel {
($name:ident, $num:expr) => {
impl sealed::Channel for peripherals::$name {}
impl Channel for peripherals::$name {
fn number(&self) -> u8 {
$num
}
}
};
}
channel!(PWM_CH0, 0);
channel!(PWM_CH1, 1);
channel!(PWM_CH2, 2);
channel!(PWM_CH3, 3);
channel!(PWM_CH4, 4);
channel!(PWM_CH5, 5);
channel!(PWM_CH6, 6);
channel!(PWM_CH7, 7);
pub trait PwmPinA<T: Channel>: GpioPin {}
pub trait PwmPinB<T: Channel>: GpioPin {}
macro_rules! impl_pin {
($pin:ident, $channel:ident, $kind:ident) => {
impl $kind<peripherals::$channel> for peripherals::$pin {}
};
}
impl_pin!(PIN_0, PWM_CH0, PwmPinA);
impl_pin!(PIN_1, PWM_CH0, PwmPinB);
impl_pin!(PIN_2, PWM_CH1, PwmPinA);
impl_pin!(PIN_3, PWM_CH1, PwmPinB);
impl_pin!(PIN_4, PWM_CH2, PwmPinA);
impl_pin!(PIN_5, PWM_CH2, PwmPinB);
impl_pin!(PIN_6, PWM_CH3, PwmPinA);
impl_pin!(PIN_7, PWM_CH3, PwmPinB);
impl_pin!(PIN_8, PWM_CH4, PwmPinA);
impl_pin!(PIN_9, PWM_CH4, PwmPinB);
impl_pin!(PIN_10, PWM_CH5, PwmPinA);
impl_pin!(PIN_11, PWM_CH5, PwmPinB);
impl_pin!(PIN_12, PWM_CH6, PwmPinA);
impl_pin!(PIN_13, PWM_CH6, PwmPinB);
impl_pin!(PIN_14, PWM_CH7, PwmPinA);
impl_pin!(PIN_15, PWM_CH7, PwmPinB);
impl_pin!(PIN_16, PWM_CH0, PwmPinA);
impl_pin!(PIN_17, PWM_CH0, PwmPinB);
impl_pin!(PIN_18, PWM_CH1, PwmPinA);
impl_pin!(PIN_19, PWM_CH1, PwmPinB);
impl_pin!(PIN_20, PWM_CH2, PwmPinA);
impl_pin!(PIN_21, PWM_CH2, PwmPinB);
impl_pin!(PIN_22, PWM_CH3, PwmPinA);
impl_pin!(PIN_23, PWM_CH3, PwmPinB);
impl_pin!(PIN_24, PWM_CH4, PwmPinA);
impl_pin!(PIN_25, PWM_CH4, PwmPinB);
impl_pin!(PIN_26, PWM_CH5, PwmPinA);
impl_pin!(PIN_27, PWM_CH5, PwmPinB);
impl_pin!(PIN_28, PWM_CH6, PwmPinA);
impl_pin!(PIN_29, PWM_CH6, PwmPinB);
impl<'d, T: Channel> SetConfig for Pwm<'d, T> {
type Config = Config;
fn set_config(&mut self, config: &Self::Config) {
Self::configure(self.inner.regs(), config);
}
}

View File

@ -56,50 +56,11 @@ macro_rules! declare_rom_function {
fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
#[doc = r"` ROM function."]
pub mod $name {
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: *const u32 = $lookup;
unsafe {
let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{AtomicU16, Ordering};
// All pointers in the ROM fit in 16 bits, so we don't need a
// full width word to store the cached value.
static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
let p: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) {
0 => {
let raw: *const u32 = $lookup;
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
raw
},
val => val as *const u32,
};
unsafe {
let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
}
$(#[$outer])*
pub extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::ptr()($($argname),*)
declare_rom_function!{
__internal ,
$(#[$outer])*
fn $name( $($argname: $ty),* ) -> $ret
$lookup
}
};
@ -107,6 +68,21 @@ macro_rules! declare_rom_function {
$(#[$outer:meta])*
unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
declare_rom_function!{
__internal unsafe ,
$(#[$outer])*
fn $name( $($argname: $ty),* ) -> $ret
$lookup
}
};
(
__internal
$( $maybe_unsafe:ident )? ,
$(#[$outer:meta])*
fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
@ -114,43 +90,58 @@ macro_rules! declare_rom_function {
pub mod $name {
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: *const u32 = $lookup;
unsafe {
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
let func : $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret
= core::mem::transmute(p);
func
}
}
#[cfg(feature = "rom-func-cache")]
// unlike rp2040-hal we store a full word, containing the full function pointer.
// rp2040-hal saves two bytes by storing only the rom offset, at the cost of
// having to do an indirection and an atomic operation on every rom call.
static mut CACHE: $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret
= trampoline;
#[cfg(feature = "rom-func-cache")]
$( $maybe_unsafe )? extern "C" fn trampoline( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{compiler_fence, Ordering};
let p: *const u32 = $lookup;
#[allow(unused_unsafe)]
unsafe {
CACHE = core::mem::transmute(p);
compiler_fence(Ordering::Release);
CACHE($($argname),*)
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{AtomicU16, Ordering};
pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{compiler_fence, Ordering};
// All pointers in the ROM fit in 16 bits, so we don't need a
// full width word to store the cached value.
static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
let p: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) {
0 => {
let raw: *const u32 = $lookup;
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
raw
},
val => val as *const u32,
};
//
// We easily get away with using only compiler fences here
// because RP2040 SRAM is not cached. If it were we'd need
// to make sure updates propagate quickly, or just take the
// hit and let each core resolve every function once.
compiler_fence(Ordering::Acquire);
unsafe {
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
CACHE
}
}
}
$(#[$outer])*
pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret {
pub $( $maybe_unsafe )? extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::ptr()($($argname),*)
}
};
@ -369,6 +360,7 @@ pub fn fplib_start() -> *const u8 {
}
/// See Table 180 in the RP2040 datasheet for the contents of this table.
#[cfg_attr(feature = "rom-func-cache", inline(never))]
pub fn soft_float_table() -> *const usize {
rom_table_lookup(DATA_TABLE, *b"SF")
}
@ -379,6 +371,7 @@ pub fn fplib_end() -> *const u8 {
}
/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table.
#[cfg_attr(feature = "rom-func-cache", inline(never))]
pub fn soft_double_table() -> *const usize {
if rom_version_number() < 2 {
panic!(

View File

@ -2,11 +2,14 @@ use core::future::{poll_fn, Future};
use core::slice;
use core::task::Poll;
use atomic_polyfill::{AtomicU8, Ordering};
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
use embassy_hal_common::atomic_ring_buffer::RingBuffer;
use embassy_sync::waitqueue::AtomicWaker;
use embassy_time::{Duration, Timer};
use super::*;
use crate::clocks::clk_peri_freq;
use crate::RegExt;
pub struct State {
@ -14,8 +17,15 @@ pub struct State {
tx_buf: RingBuffer,
rx_waker: AtomicWaker,
rx_buf: RingBuffer,
rx_error: AtomicU8,
}
// these must match bits 8..11 in UARTDR
const RXE_OVERRUN: u8 = 8;
const RXE_BREAK: u8 = 4;
const RXE_PARITY: u8 = 2;
const RXE_FRAMING: u8 = 1;
impl State {
pub const fn new() -> Self {
Self {
@ -23,6 +33,7 @@ impl State {
tx_buf: RingBuffer::new(),
rx_waker: AtomicWaker::new(),
tx_waker: AtomicWaker::new(),
rx_error: AtomicU8::new(0),
}
}
}
@ -45,7 +56,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>(
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
) {
let state = T::state();
let state = T::buffered_state();
let len = tx_buffer.len();
unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
let len = rx_buffer.len();
@ -63,7 +74,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>(
// to pend the ISR when we want data transmission to start.
let regs = T::regs();
unsafe {
regs.uartimsc().write_set(|w| {
regs.uartimsc().write(|w| {
w.set_rxim(true);
w.set_rtim(true);
w.set_txim(true);
@ -136,6 +147,14 @@ impl<'d, T: Instance> BufferedUart<'d, T> {
self.rx.blocking_read(buffer)
}
pub fn busy(&self) -> bool {
self.tx.busy()
}
pub async fn send_break(&mut self, bits: u32) {
self.tx.send_break(bits).await
}
pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) {
(self.rx, self.tx)
}
@ -173,90 +192,113 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> {
Self { phantom: PhantomData }
}
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a {
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a
where
T: 'd,
{
poll_fn(move |cx| {
if buf.is_empty() {
return Poll::Ready(Ok(0));
if let Poll::Ready(r) = Self::try_read(buf) {
return Poll::Ready(r);
}
let state = T::state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
let n = rx_reader.pop(|data| {
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
n
});
if n == 0 {
state.rx_waker.register(cx.waker());
return Poll::Pending;
}
// (Re-)Enable the interrupt to receive more data in case it was
// disabled because the buffer was full.
let regs = T::regs();
unsafe {
regs.uartimsc().write_set(|w| {
w.set_rxim(true);
w.set_rtim(true);
});
}
Poll::Ready(Ok(n))
T::buffered_state().rx_waker.register(cx.waker());
Poll::Pending
})
}
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
fn get_rx_error() -> Option<Error> {
let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed);
if errs & RXE_OVERRUN != 0 {
Some(Error::Overrun)
} else if errs & RXE_BREAK != 0 {
Some(Error::Break)
} else if errs & RXE_PARITY != 0 {
Some(Error::Parity)
} else if errs & RXE_FRAMING != 0 {
Some(Error::Framing)
} else {
None
}
}
fn try_read(buf: &mut [u8]) -> Poll<Result<usize, Error>>
where
T: 'd,
{
if buf.is_empty() {
return Ok(0);
return Poll::Ready(Ok(0));
}
loop {
let state = T::state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
let n = rx_reader.pop(|data| {
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
n
let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
let n = rx_reader.pop(|data| {
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
n
});
let result = if n == 0 {
match Self::get_rx_error() {
None => return Poll::Pending,
Some(e) => Err(e),
}
} else {
Ok(n)
};
// (Re-)Enable the interrupt to receive more data in case it was
// disabled because the buffer was full or errors were detected.
let regs = T::regs();
unsafe {
regs.uartimsc().write_set(|w| {
w.set_rxim(true);
w.set_rtim(true);
});
}
if n > 0 {
// (Re-)Enable the interrupt to receive more data in case it was
// disabled because the buffer was full.
let regs = T::regs();
unsafe {
regs.uartimsc().write_set(|w| {
w.set_rxim(true);
w.set_rtim(true);
});
}
Poll::Ready(result)
}
return Ok(n);
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
loop {
match Self::try_read(buf) {
Poll::Ready(res) => return res,
Poll::Pending => continue,
}
}
}
fn fill_buf<'a>() -> impl Future<Output = Result<&'a [u8], Error>> {
fn fill_buf<'a>() -> impl Future<Output = Result<&'a [u8], Error>>
where
T: 'd,
{
poll_fn(move |cx| {
let state = T::state();
let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
let (p, n) = rx_reader.pop_buf();
if n == 0 {
state.rx_waker.register(cx.waker());
return Poll::Pending;
}
let result = if n == 0 {
match Self::get_rx_error() {
None => {
state.rx_waker.register(cx.waker());
return Poll::Pending;
}
Some(e) => Err(e),
}
} else {
let buf = unsafe { slice::from_raw_parts(p, n) };
Ok(buf)
};
let buf = unsafe { slice::from_raw_parts(p, n) };
Poll::Ready(Ok(buf))
Poll::Ready(result)
})
}
fn consume(amt: usize) {
let state = T::state();
let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
rx_reader.pop_done(amt);
// (Re-)Enable the interrupt to receive more data in case it was
// disabled because the buffer was full.
// disabled because the buffer was full or errors were detected.
let regs = T::regs();
unsafe {
regs.uartimsc().write_set(|w| {
@ -305,7 +347,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
return Poll::Ready(Ok(0));
}
let state = T::state();
let state = T::buffered_state();
let mut tx_writer = unsafe { state.tx_buf.writer() };
let n = tx_writer.push(|data| {
let n = data.len().min(buf.len());
@ -328,7 +370,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
fn flush() -> impl Future<Output = Result<(), Error>> {
poll_fn(move |cx| {
let state = T::state();
let state = T::buffered_state();
if !state.tx_buf.is_empty() {
state.tx_waker.register(cx.waker());
return Poll::Pending;
@ -344,7 +386,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
}
loop {
let state = T::state();
let state = T::buffered_state();
let mut tx_writer = unsafe { state.tx_buf.writer() };
let n = tx_writer.push(|data| {
let n = data.len().min(buf.len());
@ -365,17 +407,54 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
pub fn blocking_flush(&mut self) -> Result<(), Error> {
loop {
let state = T::state();
let state = T::buffered_state();
if state.tx_buf.is_empty() {
return Ok(());
}
}
}
pub fn busy(&self) -> bool {
unsafe { T::regs().uartfr().read().busy() }
}
/// Assert a break condition after waiting for the transmit buffers to empty,
/// for the specified number of bit times. This condition must be asserted
/// for at least two frame times to be effective, `bits` will adjusted
/// according to frame size, parity, and stop bit settings to ensure this.
///
/// This method may block for a long amount of time since it has to wait
/// for the transmit fifo to empty, which may take a while on slow links.
pub async fn send_break(&mut self, bits: u32) {
let regs = T::regs();
let bits = bits.max(unsafe {
let lcr = regs.uartlcr_h().read();
let width = lcr.wlen() as u32 + 5;
let parity = lcr.pen() as u32;
let stops = 1 + lcr.stp2() as u32;
2 * (1 + width + parity + stops)
});
let divx64 = unsafe {
((regs.uartibrd().read().baud_divint() as u32) << 6) + regs.uartfbrd().read().baud_divfrac() as u32
} as u64;
let div_clk = clk_peri_freq() as u64 * 64;
let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk;
Self::flush().await.unwrap();
while self.busy() {}
unsafe {
regs.uartlcr_h().write_set(|w| w.set_brk(true));
}
Timer::after(Duration::from_micros(wait_usecs)).await;
unsafe {
regs.uartlcr_h().write_clear(|w| w.set_brk(true));
}
}
}
impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
fn drop(&mut self) {
let state = T::state();
let state = T::buffered_state();
unsafe {
state.rx_buf.deinit();
@ -390,7 +469,7 @@ impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
fn drop(&mut self) {
let state = T::state();
let state = T::buffered_state();
unsafe {
state.tx_buf.deinit();
@ -405,7 +484,7 @@ impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
let r = T::regs();
let s = T::state();
let s = T::buffered_state();
unsafe {
// Clear TX and error interrupt flags
@ -439,19 +518,37 @@ pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
let mut rx_writer = s.rx_buf.writer();
let rx_buf = rx_writer.push_slice();
let mut n_read = 0;
let mut error = false;
for rx_byte in rx_buf {
if r.uartfr().read().rxfe() {
break;
}
*rx_byte = r.uartdr().read().data();
let dr = r.uartdr().read();
if (dr.0 >> 8) != 0 {
s.rx_error.fetch_or((dr.0 >> 8) as u8, Ordering::Relaxed);
error = true;
// only fill the buffer with valid characters. the current character is fine
// if the error is an overrun, but if we add it to the buffer we'll report
// the overrun one character too late. drop it instead and pretend we were
// a bit slower at draining the rx fifo than we actually were.
// this is consistent with blocking uart error reporting.
break;
}
*rx_byte = dr.data();
n_read += 1;
}
if n_read > 0 {
rx_writer.push_done(n_read);
s.rx_waker.wake();
} else if error {
s.rx_waker.wake();
}
// Disable any further RX interrupts when the buffer becomes full.
if s.rx_buf.is_full() {
// Disable any further RX interrupts when the buffer becomes full or
// errors have occured. this lets us buffer additional errors in the
// fifo without needing more error storage locations, and most applications
// will want to do a full reset of their uart state anyway once an error
// has happened.
if s.rx_buf.is_full() || error {
r.uartimsc().write_clear(|w| {
w.set_rxim(true);
w.set_rtim(true);
@ -726,58 +823,3 @@ mod eh1 {
}
}
}
#[cfg(all(
feature = "unstable-traits",
feature = "nightly",
feature = "_todo_embedded_hal_serial"
))]
mod eha {
use core::future::Future;
use super::*;
impl<'d, T: Instance> embedded_hal_async::serial::Write for BufferedUartTx<'d, T> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
Self::write(buf)
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
Self::flush()
}
}
impl<'d, T: Instance> embedded_hal_async::serial::Read for BufferedUartRx<'d, T> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
Self::read(buf)
}
}
impl<'d, T: Instance> embedded_hal_async::serial::Write for BufferedUart<'d, T> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
BufferedUartTx::<'d, T>::write(buf)
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
BufferedUartTx::<'d, T>::flush()
}
}
impl<'d, T: Instance> embedded_hal_async::serial::Read for BufferedUart<'d, T> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
BufferedUartRx::<'d, T>::read(buf)
}
}
}

View File

@ -1,11 +1,21 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::Poll;
use atomic_polyfill::{AtomicU16, Ordering};
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
use embassy_futures::select::{select, Either};
use embassy_hal_common::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use embassy_time::{Duration, Timer};
use pac::uart::regs::Uartris;
use crate::clocks::clk_peri_freq;
use crate::dma::{AnyChannel, Channel};
use crate::gpio::sealed::Pin;
use crate::gpio::AnyPin;
use crate::{pac, peripherals, Peripheral};
use crate::pac::io::vals::{Inover, Outover};
use crate::{pac, peripherals, Peripheral, RegExt};
#[cfg(feature = "nightly")]
mod buffered;
@ -53,6 +63,14 @@ pub struct Config {
pub data_bits: DataBits,
pub stop_bits: StopBits,
pub parity: Parity,
/// Invert the tx pin output
pub invert_tx: bool,
/// Invert the rx pin input
pub invert_rx: bool,
// Invert the rts pin
pub invert_rts: bool,
// Invert the cts pin
pub invert_cts: bool,
}
impl Default for Config {
@ -62,6 +80,10 @@ impl Default for Config {
data_bits: DataBits::DataBits8,
stop_bits: StopBits::STOP1,
parity: Parity::ParityNone,
invert_rx: false,
invert_tx: false,
invert_rts: false,
invert_cts: false,
}
}
}
@ -82,6 +104,11 @@ pub enum Error {
Framing,
}
pub struct DmaState {
rx_err_waker: AtomicWaker,
rx_errs: AtomicU16,
}
pub struct Uart<'d, T: Instance, M: Mode> {
tx: UartTx<'d, T, M>,
rx: UartRx<'d, T, M>,
@ -133,6 +160,43 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> {
unsafe { while !r.uartfr().read().txfe() {} }
Ok(())
}
pub fn busy(&self) -> bool {
unsafe { T::regs().uartfr().read().busy() }
}
/// Assert a break condition after waiting for the transmit buffers to empty,
/// for the specified number of bit times. This condition must be asserted
/// for at least two frame times to be effective, `bits` will adjusted
/// according to frame size, parity, and stop bit settings to ensure this.
///
/// This method may block for a long amount of time since it has to wait
/// for the transmit fifo to empty, which may take a while on slow links.
pub async fn send_break(&mut self, bits: u32) {
let regs = T::regs();
let bits = bits.max(unsafe {
let lcr = regs.uartlcr_h().read();
let width = lcr.wlen() as u32 + 5;
let parity = lcr.pen() as u32;
let stops = 1 + lcr.stp2() as u32;
2 * (1 + width + parity + stops)
});
let divx64 = unsafe {
((regs.uartibrd().read().baud_divint() as u32) << 6) + regs.uartfbrd().read().baud_divfrac() as u32
} as u64;
let div_clk = clk_peri_freq() as u64 * 64;
let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk;
self.blocking_flush().unwrap();
while self.busy() {}
unsafe {
regs.uartlcr_h().write_set(|w| w.set_brk(true));
}
Timer::after(Duration::from_micros(wait_usecs)).await;
unsafe {
regs.uartlcr_h().write_clear(|w| w.set_brk(true));
}
}
}
impl<'d, T: Instance> UartTx<'d, T, Blocking> {
@ -154,7 +218,7 @@ impl<'d, T: Instance> UartTx<'d, T, Async> {
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
let ch = self.tx_dma.as_mut().unwrap();
let transfer = unsafe {
T::regs().uartdmacr().modify(|reg| {
T::regs().uartdmacr().write_set(|reg| {
reg.set_txdmae(true);
});
// If we don't assign future to a variable, the data register pointer
@ -167,55 +231,90 @@ impl<'d, T: Instance> UartTx<'d, T, Async> {
}
impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> {
/// Create a new DMA-enabled UART which can only send data
/// Create a new DMA-enabled UART which can only recieve data
pub fn new(
_uart: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(rx, rx_dma);
Uart::<T, M>::init(Some(rx.map_into()), None, None, None, config);
Self::new_inner(Some(rx_dma.map_into()))
into_ref!(rx, irq, rx_dma);
Uart::<T, M>::init(None, Some(rx.map_into()), None, None, config);
Self::new_inner(Some(irq), Some(rx_dma.map_into()))
}
fn new_inner(rx_dma: Option<PeripheralRef<'d, AnyChannel>>) -> Self {
fn new_inner(irq: Option<PeripheralRef<'d, T::Interrupt>>, rx_dma: Option<PeripheralRef<'d, AnyChannel>>) -> Self {
debug_assert_eq!(irq.is_some(), rx_dma.is_some());
if let Some(irq) = irq {
unsafe {
// disable all error interrupts initially
T::regs().uartimsc().write(|w| w.0 = 0);
}
irq.set_handler(on_interrupt::<T>);
irq.unpend();
irq.enable();
}
Self {
rx_dma,
phantom: PhantomData,
}
}
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
let r = T::regs();
unsafe {
for b in buffer {
*b = loop {
if r.uartfr().read().rxfe() {
continue;
}
let dr = r.uartdr().read();
if dr.oe() {
return Err(Error::Overrun);
} else if dr.be() {
return Err(Error::Break);
} else if dr.pe() {
return Err(Error::Parity);
} else if dr.fe() {
return Err(Error::Framing);
} else {
break dr.data();
}
};
}
pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> {
while buffer.len() > 0 {
let received = self.drain_fifo(buffer)?;
buffer = &mut buffer[received..];
}
Ok(())
}
fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
let r = T::regs();
for (i, b) in buffer.iter_mut().enumerate() {
if unsafe { r.uartfr().read().rxfe() } {
return Ok(i);
}
let dr = unsafe { r.uartdr().read() };
if dr.oe() {
return Err(Error::Overrun);
} else if dr.be() {
return Err(Error::Break);
} else if dr.pe() {
return Err(Error::Parity);
} else if dr.fe() {
return Err(Error::Framing);
} else {
*b = dr.data();
}
}
Ok(buffer.len())
}
}
impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> {
fn drop(&mut self) {
if let Some(_) = self.rx_dma {
unsafe {
T::Interrupt::steal().disable();
}
}
}
}
impl<'d, T: Instance> UartRx<'d, T, Blocking> {
pub fn new_blocking(
_uart: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(rx);
Uart::<T, Blocking>::init(None, Some(rx.map_into()), None, None, config);
Self::new_inner(None, None)
}
#[cfg(feature = "nightly")]
pub fn into_buffered(
self,
@ -230,19 +329,93 @@ impl<'d, T: Instance> UartRx<'d, T, Blocking> {
}
}
unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
let uart = T::regs();
let state = T::dma_state();
let errs = uart.uartris().read();
state.rx_errs.store(errs.0 as u16, Ordering::Relaxed);
state.rx_err_waker.wake();
// disable the error interrupts instead of clearing the flags. clearing the
// flags would allow the dma transfer to continue, potentially signaling
// completion before we can check for errors that happened *during* the transfer.
uart.uartimsc().write_clear(|w| w.0 = errs.0);
}
impl<'d, T: Instance> UartRx<'d, T, Async> {
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
// clear error flags before we drain the fifo. errors that have accumulated
// in the flags will also be present in the fifo.
T::dma_state().rx_errs.store(0, Ordering::Relaxed);
unsafe {
T::regs().uarticr().write(|w| {
w.set_oeic(true);
w.set_beic(true);
w.set_peic(true);
w.set_feic(true);
});
}
// then drain the fifo. we need to read at most 32 bytes. errors that apply
// to fifo bytes will be reported directly.
let buffer = match {
let limit = buffer.len().min(32);
self.drain_fifo(&mut buffer[0..limit])
} {
Ok(len) if len < buffer.len() => &mut buffer[len..],
Ok(_) => return Ok(()),
Err(e) => return Err(e),
};
// start a dma transfer. if errors have happened in the interim some error
// interrupt flags will have been raised, and those will be picked up immediately
// by the interrupt handler.
let ch = self.rx_dma.as_mut().unwrap();
let transfer = unsafe {
T::regs().uartdmacr().modify(|reg| {
T::regs().uartimsc().write_set(|w| {
w.set_oeim(true);
w.set_beim(true);
w.set_peim(true);
w.set_feim(true);
});
T::regs().uartdmacr().write_set(|reg| {
reg.set_rxdmae(true);
reg.set_dmaonerr(true);
});
// If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send.
crate::dma::read(ch, T::regs().uartdr().ptr() as *const _, buffer, T::RX_DREQ)
};
transfer.await;
Ok(())
// wait for either the transfer to complete or an error to happen.
let transfer_result = select(
transfer,
poll_fn(|cx| {
T::dma_state().rx_err_waker.register(cx.waker());
match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) {
0 => Poll::Pending,
e => Poll::Ready(Uartris(e as u32)),
}
}),
)
.await;
let errors = match transfer_result {
Either::First(()) => return Ok(()),
Either::Second(e) => e,
};
if errors.0 == 0 {
return Ok(());
} else if errors.oeris() {
return Err(Error::Overrun);
} else if errors.beris() {
return Err(Error::Break);
} else if errors.peris() {
return Err(Error::Parity);
} else if errors.feris() {
return Err(Error::Framing);
}
unreachable!("unrecognized rx error");
}
}
@ -255,7 +428,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
config: Config,
) -> Self {
into_ref!(tx, rx);
Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, config)
Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, None, config)
}
/// Create a new UART with hardware flow control (RTS/CTS)
@ -276,6 +449,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
Some(cts.map_into()),
None,
None,
None,
config,
)
}
@ -304,17 +478,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
uart: impl Peripheral<P = T> + 'd,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(tx, rx, tx_dma, rx_dma);
into_ref!(tx, rx, irq, tx_dma, rx_dma);
Self::new_inner(
uart,
tx.map_into(),
rx.map_into(),
None,
None,
Some(irq),
Some(tx_dma.map_into()),
Some(rx_dma.map_into()),
config,
@ -328,17 +504,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
cts: impl Peripheral<P = impl CtsPin<T>> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(tx, rx, cts, rts, tx_dma, rx_dma);
into_ref!(tx, rx, cts, rts, irq, tx_dma, rx_dma);
Self::new_inner(
uart,
tx.map_into(),
rx.map_into(),
Some(rts.map_into()),
Some(cts.map_into()),
Some(irq),
Some(tx_dma.map_into()),
Some(rx_dma.map_into()),
config,
@ -353,6 +531,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
mut rx: PeripheralRef<'d, AnyPin>,
mut rts: Option<PeripheralRef<'d, AnyPin>>,
mut cts: Option<PeripheralRef<'d, AnyPin>>,
irq: Option<PeripheralRef<'d, T::Interrupt>>,
tx_dma: Option<PeripheralRef<'d, AnyChannel>>,
rx_dma: Option<PeripheralRef<'d, AnyChannel>>,
config: Config,
@ -367,7 +546,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
Self {
tx: UartTx::new_inner(tx_dma),
rx: UartRx::new_inner(rx_dma),
rx: UartRx::new_inner(irq, rx_dma),
}
}
@ -381,19 +560,47 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
let r = T::regs();
unsafe {
if let Some(pin) = &tx {
pin.io().ctrl().write(|w| w.set_funcsel(2));
pin.io().ctrl().write(|w| {
w.set_funcsel(2);
w.set_outover(if config.invert_tx {
Outover::INVERT
} else {
Outover::NORMAL
});
});
pin.pad_ctrl().write(|w| w.set_ie(true));
}
if let Some(pin) = &rx {
pin.io().ctrl().write(|w| w.set_funcsel(2));
pin.io().ctrl().write(|w| {
w.set_funcsel(2);
w.set_inover(if config.invert_rx {
Inover::INVERT
} else {
Inover::NORMAL
});
});
pin.pad_ctrl().write(|w| w.set_ie(true));
}
if let Some(pin) = &cts {
pin.io().ctrl().write(|w| w.set_funcsel(2));
pin.io().ctrl().write(|w| {
w.set_funcsel(2);
w.set_inover(if config.invert_cts {
Inover::INVERT
} else {
Inover::NORMAL
});
});
pin.pad_ctrl().write(|w| w.set_ie(true));
}
if let Some(pin) = &rts {
pin.io().ctrl().write(|w| w.set_funcsel(2));
pin.io().ctrl().write(|w| {
w.set_funcsel(2);
w.set_outover(if config.invert_rts {
Outover::INVERT
} else {
Outover::NORMAL
});
});
pin.pad_ctrl().write(|w| w.set_ie(true));
}
@ -475,6 +682,14 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> {
self.rx.blocking_read(buffer)
}
pub fn busy(&self) -> bool {
self.tx.busy()
}
pub async fn send_break(&mut self, bits: u32) {
self.tx.send_break(bits).await
}
/// Split the Uart into a transmitter and receiver, which is particuarly
/// useful when having two tasks correlating to transmitting and receiving.
pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) {
@ -651,61 +866,6 @@ mod eh1 {
}
}
#[cfg(all(
feature = "unstable-traits",
feature = "nightly",
feature = "_todo_embedded_hal_serial"
))]
mod eha {
use core::future::Future;
use super::*;
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Write for UartTx<'d, T, M> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.write(buf)
}
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(()) }
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Read for UartRx<'d, T, M> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.read(buf)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Write for Uart<'d, T, M> {
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.write(buf)
}
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(()) }
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Read for Uart<'d, T, M> {
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.read(buf)
}
}
}
mod sealed {
use super::*;
@ -720,7 +880,9 @@ mod sealed {
fn regs() -> pac::uart::Uart;
#[cfg(feature = "nightly")]
fn state() -> &'static buffered::State;
fn buffered_state() -> &'static buffered::State;
fn dma_state() -> &'static DmaState;
}
pub trait TxPin<T: Instance> {}
pub trait RxPin<T: Instance> {}
@ -758,10 +920,18 @@ macro_rules! impl_instance {
}
#[cfg(feature = "nightly")]
fn state() -> &'static buffered::State {
fn buffered_state() -> &'static buffered::State {
static STATE: buffered::State = buffered::State::new();
&STATE
}
fn dma_state() -> &'static DmaState {
static STATE: DmaState = DmaState {
rx_err_waker: AtomicWaker::new(),
rx_errs: AtomicU16::new(0),
};
&STATE
}
}
impl Instance for peripherals::$inst {}
};

View File

@ -231,7 +231,7 @@ impl<'d, T: Instance> Driver<'d, T> {
let len = (max_packet_size + 63) / 64 * 64;
let addr = self.ep_mem_free;
if addr + len > EP_MEMORY_SIZE as _ {
if addr + len > EP_MEMORY_SIZE as u16 {
warn!("Endpoint memory full");
return Err(EndpointAllocError);
}

View File

@ -32,7 +32,7 @@ flavors = [
[dependencies]
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-executor = { version = "0.1.0", path = "../embassy-executor" }
embassy-executor = { version = "0.2.0", path = "../embassy-executor" }
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-4"]}
@ -55,10 +55,10 @@ cortex-m = "0.7.6"
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
rand_core = "0.6.3"
sdio-host = "0.5.0"
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "46d1b1c2ff13e31e282ec1e352421721694f126a", optional = true }
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
critical-section = "1.1"
atomic-polyfill = "1.0.1"
stm32-metapac = "6"
stm32-metapac = "7"
vcell = "0.1.3"
bxcan = "0.7.0"
nb = "1.0.0"
@ -66,6 +66,7 @@ stm32-fmc = "0.2.4"
seq-macro = "0.3.0"
cfg-if = "1.0.0"
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
chrono = { version = "^0.4", default-features = false, optional = true}
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
@ -73,7 +74,7 @@ critical-section = { version = "1.1", features = ["std"] }
[build-dependencies]
proc-macro2 = "1.0.36"
quote = "1.0.15"
stm32-metapac = { version = "6", default-features = false, features = ["metadata"]}
stm32-metapac = { version = "7", default-features = false, features = ["metadata"]}
[features]
default = ["stm32-metapac/rt"]

View File

@ -81,11 +81,74 @@ fn main() {
singletons.push(c.name.to_string());
}
// ========
// Handle time-driver-XXXX features.
let time_driver = match env::vars()
.map(|(a, _)| a)
.filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_"))
.get_one()
{
Ok(x) => Some(
x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_")
.unwrap()
.to_ascii_lowercase(),
),
Err(GetOneError::None) => None,
Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"),
};
let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) {
None => "",
Some("tim2") => "TIM2",
Some("tim3") => "TIM3",
Some("tim4") => "TIM4",
Some("tim5") => "TIM5",
Some("tim12") => "TIM12",
Some("tim15") => "TIM15",
Some("any") => {
if singletons.contains(&"TIM2".to_string()) {
"TIM2"
} else if singletons.contains(&"TIM3".to_string()) {
"TIM3"
} else if singletons.contains(&"TIM4".to_string()) {
"TIM4"
} else if singletons.contains(&"TIM5".to_string()) {
"TIM5"
} else if singletons.contains(&"TIM12".to_string()) {
"TIM12"
} else if singletons.contains(&"TIM15".to_string()) {
"TIM15"
} else {
panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.")
}
}
_ => panic!("unknown time_driver {:?}", time_driver),
};
if time_driver_singleton != "" {
println!("cargo:rustc-cfg=time_driver_{}", time_driver_singleton.to_lowercase());
}
// ========
// Write singletons
let mut g = TokenStream::new();
let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect();
g.extend(quote! {
embassy_hal_common::peripherals!(#(#singleton_tokens),*);
embassy_hal_common::peripherals_definition!(#(#singleton_tokens),*);
});
let singleton_tokens: Vec<_> = singletons
.iter()
.filter(|s| *s != &time_driver_singleton.to_string())
.map(|s| format_ident!("{}", s))
.collect();
g.extend(quote! {
embassy_hal_common::peripherals_struct!(#(#singleton_tokens),*);
});
// ========
@ -192,12 +255,20 @@ fn main() {
];
});
let max_erase_size = flash_memory_regions
.iter()
.map(|region| region.settings.as_ref().unwrap().erase_size)
.max()
.unwrap();
g.extend(quote! { pub const MAX_ERASE_SIZE: usize = #max_erase_size as usize; });
g.extend(quote! { pub mod flash_regions { #flash_regions } });
// ========
// Generate DMA IRQs.
let mut dma_irqs: HashMap<&str, Vec<(&str, &str)>> = HashMap::new();
let mut dma_irqs: HashMap<&str, Vec<(&str, &str, &str)>> = HashMap::new();
for p in METADATA.peripherals {
if let Some(r) = &p.registers {
@ -207,7 +278,10 @@ fn main() {
continue;
}
for irq in p.interrupts {
dma_irqs.entry(irq.interrupt).or_default().push((p.name, irq.signal));
dma_irqs
.entry(irq.interrupt)
.or_default()
.push((r.kind, p.name, irq.signal));
}
}
}
@ -216,13 +290,14 @@ fn main() {
for (irq, channels) in dma_irqs {
let irq = format_ident!("{}", irq);
let channels = channels.iter().map(|(dma, ch)| format_ident!("{}_{}", dma, ch));
let xdma = format_ident!("{}", channels[0].0);
let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch));
g.extend(quote! {
#[crate::interrupt]
unsafe fn #irq () {
#(
<crate::peripherals::#channels as crate::dma::sealed::Channel>::on_irq();
<crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq();
)*
}
});
@ -838,51 +913,6 @@ fn main() {
println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x
println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9
// ========
// Handle time-driver-XXXX features.
let time_driver = match env::vars()
.map(|(a, _)| a)
.filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_"))
.get_one()
{
Ok(x) => Some(
x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_")
.unwrap()
.to_ascii_lowercase(),
),
Err(GetOneError::None) => None,
Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"),
};
match time_driver.as_ref().map(|x| x.as_ref()) {
None => {}
Some("tim2") => println!("cargo:rustc-cfg=time_driver_tim2"),
Some("tim3") => println!("cargo:rustc-cfg=time_driver_tim3"),
Some("tim4") => println!("cargo:rustc-cfg=time_driver_tim4"),
Some("tim5") => println!("cargo:rustc-cfg=time_driver_tim5"),
Some("tim12") => println!("cargo:rustc-cfg=time_driver_tim12"),
Some("tim15") => println!("cargo:rustc-cfg=time_driver_tim15"),
Some("any") => {
if singletons.contains(&"TIM2".to_string()) {
println!("cargo:rustc-cfg=time_driver_tim2");
} else if singletons.contains(&"TIM3".to_string()) {
println!("cargo:rustc-cfg=time_driver_tim3");
} else if singletons.contains(&"TIM4".to_string()) {
println!("cargo:rustc-cfg=time_driver_tim4");
} else if singletons.contains(&"TIM5".to_string()) {
println!("cargo:rustc-cfg=time_driver_tim5");
} else if singletons.contains(&"TIM12".to_string()) {
println!("cargo:rustc-cfg=time_driver_tim12");
} else if singletons.contains(&"TIM15".to_string()) {
println!("cargo:rustc-cfg=time_driver_tim15");
} else {
panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.")
}
}
_ => panic!("unknown time_driver {:?}", time_driver),
}
// Handle time-driver-XXXX features.
if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {}
println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]);

View File

@ -4,6 +4,7 @@ use core::task::Poll;
use embassy_hal_common::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use crate::dma::Transfer;
use crate::gpio::sealed::AFType;
use crate::gpio::Speed;
use crate::interrupt::{Interrupt, InterruptExt};
@ -385,14 +386,11 @@ where
return self.capture_giant(buffer).await;
}
}
async fn capture_small(&mut self, buffer: &mut [u32]) -> Result<(), Error> {
let channel = &mut self.dma;
let request = channel.request();
let r = self.inner.regs();
let src = r.dr().ptr() as *mut u32;
let dma_read = crate::dma::read(channel, request, src, buffer);
let request = self.dma.request();
let dma_read = unsafe { Transfer::new_read(&mut self.dma, request, src, buffer, Default::default()) };
Self::clear_interrupt_flags();
Self::enable_irqs();
@ -436,6 +434,12 @@ where
result
}
#[cfg(not(dma))]
async fn capture_giant(&mut self, _buffer: &mut [u32]) -> Result<(), Error> {
panic!("capturing to buffers larger than 0xffff is only supported on DMA for now, not on BDMA or GPDMA.");
}
#[cfg(dma)]
async fn capture_giant(&mut self, buffer: &mut [u32]) -> Result<(), Error> {
use crate::dma::TransferOptions;
@ -460,16 +464,24 @@ where
let r = self.inner.regs();
let src = r.dr().ptr() as *mut u32;
unsafe {
channel.start_double_buffered_read(request, src, m0ar, m1ar, chunk_size, TransferOptions::default());
}
let mut transfer = unsafe {
crate::dma::DoubleBuffered::new_read(
&mut self.dma,
request,
src,
m0ar,
m1ar,
chunk_size,
TransferOptions::default(),
)
};
let mut last_chunk_set_for_transfer = false;
let mut buffer0_last_accessible = false;
let dma_result = poll_fn(|cx| {
channel.set_waker(cx.waker());
transfer.set_waker(cx.waker());
let buffer0_currently_accessible = unsafe { channel.is_buffer0_accessible() };
let buffer0_currently_accessible = transfer.is_buffer0_accessible();
// check if the accessible buffer changed since last poll
if buffer0_last_accessible == buffer0_currently_accessible {
@ -480,21 +492,21 @@ where
if remaining_chunks != 0 {
if remaining_chunks % 2 == 0 && buffer0_currently_accessible {
m0ar = unsafe { m0ar.add(2 * chunk_size) };
unsafe { channel.set_buffer0(m0ar) }
unsafe { transfer.set_buffer0(m0ar) }
remaining_chunks -= 1;
} else if !buffer0_currently_accessible {
m1ar = unsafe { m1ar.add(2 * chunk_size) };
unsafe { channel.set_buffer1(m1ar) };
unsafe { transfer.set_buffer1(m1ar) };
remaining_chunks -= 1;
}
} else {
if buffer0_currently_accessible {
unsafe { channel.set_buffer0(buffer.as_mut_ptr()) }
unsafe { transfer.set_buffer0(buffer.as_mut_ptr()) }
} else {
unsafe { channel.set_buffer1(buffer.as_mut_ptr()) }
unsafe { transfer.set_buffer1(buffer.as_mut_ptr()) }
}
if last_chunk_set_for_transfer {
channel.request_stop();
transfer.request_stop();
return Poll::Ready(());
}
last_chunk_set_for_transfer = true;

View File

@ -1,17 +1,33 @@
#![macro_use]
use core::future::Future;
use core::pin::Pin;
use core::sync::atomic::{fence, Ordering};
use core::task::Waker;
use core::task::{Context, Poll, Waker};
use atomic_polyfill::AtomicUsize;
use embassy_cortex_m::interrupt::Priority;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use super::{TransferOptions, Word, WordSize};
use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError};
use super::word::{Word, WordSize};
use super::Dir;
use crate::_generated::BDMA_CHANNEL_COUNT;
use crate::dma::Request;
use crate::interrupt::{Interrupt, InterruptExt};
use crate::pac;
use crate::pac::bdma::vals;
use crate::pac::bdma::{regs, vals};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TransferOptions {}
impl Default for TransferOptions {
fn default() -> Self {
Self {}
}
}
impl From<WordSize> for vals::Size {
fn from(raw: WordSize) -> Self {
@ -23,15 +39,27 @@ impl From<WordSize> for vals::Size {
}
}
impl From<Dir> for vals::Dir {
fn from(raw: Dir) -> Self {
match raw {
Dir::MemoryToPeripheral => Self::FROMMEMORY,
Dir::PeripheralToMemory => Self::FROMPERIPHERAL,
}
}
}
struct State {
ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT],
complete_count: [AtomicUsize; BDMA_CHANNEL_COUNT],
}
impl State {
const fn new() -> Self {
const ZERO: AtomicUsize = AtomicUsize::new(0);
const AW: AtomicWaker = AtomicWaker::new();
Self {
ch_wakers: [AW; BDMA_CHANNEL_COUNT],
complete_count: [ZERO; BDMA_CHANNEL_COUNT],
}
}
}
@ -55,228 +83,443 @@ foreach_dma_channel! {
// BDMA1 in H7 doesn't use DMAMUX, which breaks
};
($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
unsafe fn start_write<W: Word>(&mut self, _request: Request, buf: *const[W], reg_addr: *mut W, options: TransferOptions) {
let (ptr, len) = super::slice_ptr_parts(buf);
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
#[cfg(any(bdma_v2, dmamux))]
_request,
vals::Dir::FROMMEMORY,
reg_addr as *const u32,
ptr as *mut u32,
len,
true,
vals::Size::from(W::bits()),
options,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
);
impl sealed::Channel for crate::peripherals::$channel_peri {
fn regs(&self) -> pac::bdma::Dma {
pac::$dma_peri
}
unsafe fn start_write_repeated<W: Word>(&mut self, _request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
#[cfg(any(bdma_v2, dmamux))]
_request,
vals::Dir::FROMMEMORY,
reg_addr as *const u32,
repeated as *mut u32,
count,
false,
vals::Size::from(W::bits()),
options,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
)
fn num(&self) -> usize {
$channel_num
}
unsafe fn start_read<W: Word>(&mut self, _request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
let (ptr, len) = super::slice_ptr_parts_mut(buf);
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
#[cfg(any(bdma_v2, dmamux))]
_request,
vals::Dir::FROMPERIPHERAL,
reg_addr as *const u32,
ptr as *mut u32,
len,
true,
vals::Size::from(W::bits()),
options,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
);
fn index(&self) -> usize {
$index
}
unsafe fn start_double_buffered_read<W: super::Word>(
&mut self,
_request: Request,
_reg_addr: *const W,
_buffer0: *mut W,
_buffer1: *mut W,
_buffer_len: usize,
_options: TransferOptions,
) {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
unsafe fn set_buffer0<W: super::Word>(&mut self, _buffer: *mut W) {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
unsafe fn set_buffer1<W: super::Word>(&mut self, _buffer: *mut W) {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
unsafe fn is_buffer0_accessible(&mut self) -> bool {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
fn request_stop(&mut self){
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
}
fn is_running(&self) -> bool {
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
}
fn remaining_transfers(&mut self) -> u16 {
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
}
fn set_waker(&mut self, waker: &Waker) {
unsafe { low_level_api::set_waker($index, waker) }
}
fn on_irq() {
unsafe {
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
}
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
}
}
impl crate::dma::Channel for crate::peripherals::$channel_peri {}
impl Channel for crate::peripherals::$channel_peri {}
};
}
mod low_level_api {
/// Safety: Must be called with a matching set of parameters for a valid dma channel
pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index: usize) {
let isr = dma.isr().read();
let cr = dma.ch(channel_num).cr();
if isr.teif(channel_num) {
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
}
let mut wake = false;
if isr.htif(channel_num) && cr.read().htie() {
// Acknowledge half transfer complete interrupt
dma.ifcr().write(|w| w.set_htif(channel_num, true));
wake = true;
}
if isr.tcif(channel_num) && cr.read().tcie() {
// Acknowledge transfer complete interrupt
dma.ifcr().write(|w| w.set_tcif(channel_num, true));
STATE.complete_count[index].fetch_add(1, Ordering::Release);
wake = true;
}
if wake {
STATE.ch_wakers[index].wake();
}
}
#[cfg(any(bdma_v2, dmamux))]
pub type Request = u8;
#[cfg(not(any(bdma_v2, dmamux)))]
pub type Request = ();
#[cfg(dmamux)]
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
#[cfg(not(dmamux))]
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
pub(crate) mod sealed {
use super::*;
pub unsafe fn start_transfer(
dma: pac::bdma::Dma,
channel_number: u8,
#[cfg(any(bdma_v2, dmamux))] request: Request,
dir: vals::Dir,
pub trait Channel {
fn regs(&self) -> pac::bdma::Dma;
fn num(&self) -> usize;
fn index(&self) -> usize;
fn on_irq();
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>,
}
impl<'a, C: Channel> Transfer<'a, C> {
pub unsafe fn new_read<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
peri_addr: *mut W,
buf: &'a mut [W],
options: TransferOptions,
) -> Self {
Self::new_read_raw(channel, request, peri_addr, buf, options)
}
pub unsafe fn new_read_raw<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
peri_addr: *mut W,
buf: *mut [W],
options: TransferOptions,
) -> Self {
into_ref!(channel);
let (ptr, len) = super::slice_ptr_parts_mut(buf);
assert!(len > 0 && len <= 0xFFFF);
Self::new_inner(
channel,
request,
Dir::PeripheralToMemory,
peri_addr as *const u32,
ptr as *mut u32,
len,
true,
W::size(),
options,
)
}
pub unsafe fn new_write<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
buf: &'a [W],
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
Self::new_write_raw(channel, request, buf, peri_addr, options)
}
pub unsafe fn new_write_raw<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
buf: *const [W],
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
into_ref!(channel);
let (ptr, len) = super::slice_ptr_parts(buf);
assert!(len > 0 && len <= 0xFFFF);
Self::new_inner(
channel,
request,
Dir::MemoryToPeripheral,
peri_addr as *const u32,
ptr as *mut u32,
len,
true,
W::size(),
options,
)
}
pub unsafe fn new_write_repeated<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
repeated: &'a W,
count: usize,
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
into_ref!(channel);
Self::new_inner(
channel,
request,
Dir::MemoryToPeripheral,
peri_addr as *const u32,
repeated as *const W as *mut u32,
count,
false,
W::size(),
options,
)
}
unsafe fn new_inner(
channel: PeripheralRef<'a, C>,
_request: Request,
dir: Dir,
peri_addr: *const u32,
mem_addr: *mut u32,
mem_len: usize,
incr_mem: bool,
data_size: vals::Size,
options: TransferOptions,
#[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux,
#[cfg(dmamux)] dmamux_ch_num: u8,
) {
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(
options.flow_ctrl == crate::dma::FlowControl::Dma,
"Peripheral flow control not supported"
);
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
let ch = dma.ch(channel_number as _);
reset_status(dma, channel_number);
#[cfg(dmamux)]
super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request);
#[cfg(bdma_v2)]
critical_section::with(|_| dma.cselr().modify(|w| w.set_cs(channel_number as _, request)));
data_size: WordSize,
_options: TransferOptions,
) -> Self {
let ch = channel.regs().ch(channel.num());
// "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::SeqCst);
#[cfg(bdma_v2)]
critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request)));
let mut this = Self { channel };
this.clear_irqs();
STATE.complete_count[this.channel.index()].store(0, Ordering::Release);
#[cfg(dmamux)]
super::dmamux::configure_dmamux(&mut *this.channel, _request);
ch.par().write_value(peri_addr as u32);
ch.mar().write_value(mem_addr as u32);
ch.ndtr().write(|w| w.set_ndt(mem_len as u16));
ch.cr().write(|w| {
w.set_psize(data_size);
w.set_msize(data_size);
w.set_psize(data_size.into());
w.set_msize(data_size.into());
if incr_mem {
w.set_minc(vals::Inc::ENABLED);
} else {
w.set_minc(vals::Inc::DISABLED);
}
w.set_dir(dir);
w.set_dir(dir.into());
w.set_teie(true);
w.set_tcie(true);
w.set_en(true);
});
this
}
pub unsafe fn request_stop(dma: pac::bdma::Dma, channel_number: u8) {
reset_status(dma, channel_number);
let ch = dma.ch(channel_number as _);
// Disable the channel and interrupts with the default value.
ch.cr().write(|_| ());
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
fn clear_irqs(&mut self) {
unsafe {
self.channel.regs().ifcr().write(|w| {
w.set_tcif(self.channel.num(), true);
w.set_teif(self.channel.num(), true);
})
}
}
pub unsafe fn is_running(dma: pac::bdma::Dma, ch: u8) -> bool {
let ch = dma.ch(ch as _);
ch.cr().read().en()
pub fn request_stop(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
// Disable the channel. Keep the IEs enabled so the irqs still fire.
unsafe {
ch.cr().write(|w| {
w.set_teie(true);
w.set_tcie(true);
})
}
}
pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num());
let en = unsafe { ch.cr().read() }.en();
let tcif = STATE.complete_count[self.channel.index()].load(Ordering::Acquire) != 0;
en && !tcif
}
/// Gets the total remaining transfers for the channel
/// Note: this will be zero for transfers that completed without cancellation.
pub unsafe fn get_remaining_transfers(dma: pac::bdma::Dma, ch: u8) -> u16 {
// get a handle on the channel itself
let ch = dma.ch(ch as _);
// read the remaining transfer count. If this is zero, the transfer completed fully.
ch.ndtr().read().ndt() as u16
pub fn get_remaining_transfers(&self) -> u16 {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.ndtr().read() }.ndt()
}
/// Sets the waker for the specified DMA channel
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
STATE.ch_wakers[state_number].register(waker);
pub fn blocking_wait(mut self) {
while self.is_running() {}
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
core::mem::forget(self);
}
}
pub unsafe fn reset_status(dma: pac::bdma::Dma, channel_number: u8) {
dma.ifcr().write(|w| {
w.set_tcif(channel_number as _, true);
w.set_teif(channel_number as _, true);
});
impl<'a, C: Channel> Drop for Transfer<'a, C> {
fn drop(&mut self) {
self.request_stop();
while self.is_running() {}
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
}
}
/// Safety: Must be called with a matching set of parameters for a valid dma channel
pub unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: u8, index: u8) {
let channel_num = channel_num as usize;
let index = index as usize;
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
impl<'a, C: Channel> Future for Transfer<'a, C> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
STATE.ch_wakers[self.channel.index()].register(cx.waker());
let isr = dma.isr().read();
let cr = dma.ch(channel_num).cr();
if isr.teif(channel_num) {
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
}
if isr.tcif(channel_num) && cr.read().tcie() {
cr.write(|_| ()); // Disable channel interrupts with the default value.
STATE.ch_wakers[index].wake();
if self.is_running() {
Poll::Pending
} else {
Poll::Ready(())
}
}
}
// ==============================
struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>);
impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> {
fn ndtr(&self) -> usize {
let ch = self.0.regs().ch(self.0.num());
unsafe { ch.ndtr().read() }.ndt() as usize
}
fn get_complete_count(&self) -> usize {
STATE.complete_count[self.0.index()].load(Ordering::Acquire)
}
fn reset_complete_count(&mut self) -> usize {
STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel)
}
}
pub struct RingBuffer<'a, C: Channel, W: Word> {
cr: regs::Cr,
channel: PeripheralRef<'a, C>,
ringbuf: DmaRingBuffer<'a, W>,
}
impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
pub unsafe fn new_read(
channel: impl Peripheral<P = C> + 'a,
_request: Request,
peri_addr: *mut W,
buffer: &'a mut [W],
_options: TransferOptions,
) -> Self {
into_ref!(channel);
let len = buffer.len();
assert!(len > 0 && len <= 0xFFFF);
let dir = Dir::PeripheralToMemory;
let data_size = W::size();
let channel_number = channel.num();
let dma = channel.regs();
// "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::SeqCst);
#[cfg(bdma_v2)]
critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request)));
let mut w = regs::Cr(0);
w.set_psize(data_size.into());
w.set_msize(data_size.into());
w.set_minc(vals::Inc::ENABLED);
w.set_dir(dir.into());
w.set_teie(true);
w.set_htie(true);
w.set_tcie(true);
w.set_circ(vals::Circ::ENABLED);
w.set_pl(vals::Pl::VERYHIGH);
w.set_en(true);
let buffer_ptr = buffer.as_mut_ptr();
let mut this = Self {
channel,
cr: w,
ringbuf: DmaRingBuffer::new(buffer),
};
this.clear_irqs();
#[cfg(dmamux)]
super::dmamux::configure_dmamux(&mut *this.channel, _request);
let ch = dma.ch(channel_number);
ch.par().write_value(peri_addr as u32);
ch.mar().write_value(buffer_ptr as u32);
ch.ndtr().write(|w| w.set_ndt(len as u16));
this
}
pub fn start(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.cr().write_value(self.cr) }
}
pub fn clear(&mut self) {
self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow()));
}
/// Read bytes from the ring buffer
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, buf: &mut [W]) -> Result<usize, OverrunError> {
self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf)
}
pub fn is_empty(&self) -> bool {
self.ringbuf.is_empty()
}
pub fn len(&self) -> usize {
self.ringbuf.len()
}
pub fn capacity(&self) -> usize {
self.ringbuf.dma_buf.len()
}
pub fn set_waker(&mut self, waker: &Waker) {
STATE.ch_wakers[self.channel.index()].register(waker);
}
fn clear_irqs(&mut self) {
let dma = self.channel.regs();
unsafe {
dma.ifcr().write(|w| {
w.set_htif(self.channel.num(), true);
w.set_tcif(self.channel.num(), true);
w.set_teif(self.channel.num(), true);
})
}
}
pub fn request_stop(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
// Disable the channel. Keep the IEs enabled so the irqs still fire.
unsafe {
ch.cr().write(|w| {
w.set_teie(true);
w.set_htie(true);
w.set_tcie(true);
})
}
}
pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.cr().read() }.en()
}
/// Synchronize the position of the ring buffer to the actual DMA controller position
pub fn reload_position(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
self.ringbuf.ndtr = unsafe { ch.ndtr().read() }.ndt() as usize;
}
}
impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> {
fn drop(&mut self) {
self.request_stop();
while self.is_running() {}
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,8 @@
use crate::{pac, peripherals};
pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_ch_num: u8, request: u8) {
let ch_mux_regs = dmamux_regs.ccr(dmamux_ch_num as _);
pub(crate) unsafe fn configure_dmamux<M: MuxChannel>(channel: &mut M, request: u8) {
let ch_mux_regs = channel.mux_regs().ccr(channel.mux_num());
ch_mux_regs.write(|reg| {
reg.set_nbreq(0);
reg.set_dmareq_id(request);
@ -14,11 +14,11 @@ pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_c
});
}
pub(crate) mod sealed {
pub(crate) mod dmamux_sealed {
use super::*;
pub trait MuxChannel {
const DMAMUX_CH_NUM: u8;
const DMAMUX_REGS: pac::dmamux::Dmamux;
fn mux_regs(&self) -> pac::dmamux::Dmamux;
fn mux_num(&self) -> usize;
}
}
@ -26,15 +26,19 @@ pub struct DMAMUX1;
#[cfg(stm32h7)]
pub struct DMAMUX2;
pub trait MuxChannel: sealed::MuxChannel + super::Channel {
pub trait MuxChannel: dmamux_sealed::MuxChannel {
type Mux;
}
foreach_dma_channel! {
($channel_peri:ident, $dma_peri:ident, $version:ident, $channel_num:expr, $index:expr, {dmamux: $dmamux:ident, dmamux_channel: $dmamux_channel:expr}) => {
impl sealed::MuxChannel for peripherals::$channel_peri {
const DMAMUX_CH_NUM: u8 = $dmamux_channel;
const DMAMUX_REGS: pac::dmamux::Dmamux = pac::$dmamux;
impl dmamux_sealed::MuxChannel for peripherals::$channel_peri {
fn mux_regs(&self) -> pac::dmamux::Dmamux {
pac::$dmamux
}
fn mux_num(&self) -> usize {
$dmamux_channel
}
}
impl MuxChannel for peripherals::$channel_peri {
type Mux = $dmamux;

View File

@ -1,13 +1,31 @@
use core::sync::atomic::{fence, Ordering};
use core::task::Waker;
#![macro_use]
use core::future::Future;
use core::pin::Pin;
use core::sync::atomic::{fence, Ordering};
use core::task::{Context, Poll};
use embassy_cortex_m::interrupt::Priority;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use super::{Request, TransferOptions, Word, WordSize};
use super::word::{Word, WordSize};
use super::Dir;
use crate::_generated::GPDMA_CHANNEL_COUNT;
use crate::interrupt::{Interrupt, InterruptExt};
use crate::pac::gpdma::{vals, Gpdma};
use crate::{interrupt, pac};
use crate::pac;
use crate::pac::gpdma::vals;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TransferOptions {}
impl Default for TransferOptions {
fn default() -> Self {
Self {}
}
}
impl From<WordSize> for vals::ChTr1Dw {
fn from(raw: WordSize) -> Self {
@ -19,27 +37,15 @@ impl From<WordSize> for vals::ChTr1Dw {
}
}
struct ChannelState {
waker: AtomicWaker,
}
impl ChannelState {
const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
}
struct State {
channels: [ChannelState; GPDMA_CHANNEL_COUNT],
ch_wakers: [AtomicWaker; GPDMA_CHANNEL_COUNT],
}
impl State {
const fn new() -> Self {
const CH: ChannelState = ChannelState::new();
const AW: AtomicWaker = AtomicWaker::new();
Self {
channels: [CH; GPDMA_CHANNEL_COUNT],
ch_wakers: [AW; GPDMA_CHANNEL_COUNT],
}
}
}
@ -47,10 +53,12 @@ impl State {
static STATE: State = State::new();
/// safety: must be called only once
pub(crate) unsafe fn init() {
pub(crate) unsafe fn init(irq_priority: Priority) {
foreach_interrupt! {
($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => {
interrupt::$irq::steal().enable();
let irq = crate::interrupt::$irq::steal();
irq.set_priority(irq_priority);
irq.enable();
};
}
crate::_generated::init_gpdma();
@ -58,117 +66,171 @@ pub(crate) unsafe fn init() {
foreach_dma_channel! {
($channel_peri:ident, $dma_peri:ident, gpdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
unsafe fn start_write<W: Word>(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) {
let (ptr, len) = super::slice_ptr_parts(buf);
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
request,
low_level_api::Dir::MemoryToPeripheral,
reg_addr as *const u32,
ptr as *mut u32,
len,
true,
W::bits(),
options,
)
impl sealed::Channel for crate::peripherals::$channel_peri {
fn regs(&self) -> pac::gpdma::Gpdma {
pac::$dma_peri
}
unsafe fn start_write_repeated<W: Word>(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
request,
low_level_api::Dir::MemoryToPeripheral,
reg_addr as *const u32,
repeated as *mut u32,
count,
false,
W::bits(),
options,
)
fn num(&self) -> usize {
$channel_num
}
unsafe fn start_read<W: Word>(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
let (ptr, len) = super::slice_ptr_parts_mut(buf);
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
request,
low_level_api::Dir::PeripheralToMemory,
reg_addr as *const u32,
ptr as *mut u32,
len,
true,
W::bits(),
options,
);
fn index(&self) -> usize {
$index
}
unsafe fn start_double_buffered_read<W: Word>(
&mut self,
_request: Request,
_reg_addr: *const W,
_buffer0: *mut W,
_buffer1: *mut W,
_buffer_len: usize,
_options: TransferOptions,
) {
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
}
unsafe fn set_buffer0<W: Word>(&mut self, _buffer: *mut W) {
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
}
unsafe fn set_buffer1<W: Word>(&mut self, _buffer: *mut W) {
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
}
unsafe fn is_buffer0_accessible(&mut self) -> bool {
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
}
fn request_stop(&mut self) {
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
}
fn is_running(&self) -> bool {
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
}
fn remaining_transfers(&mut self) -> u16 {
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
}
fn set_waker(&mut self, waker: &Waker) {
unsafe {low_level_api::set_waker($index, waker )}
}
fn on_irq() {
unsafe {
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
}
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
}
}
impl crate::dma::Channel for crate::peripherals::$channel_peri { }
impl Channel for crate::peripherals::$channel_peri {}
};
}
mod low_level_api {
use super::*;
/// Safety: Must be called with a matching set of parameters for a valid dma channel
pub(crate) unsafe fn on_irq_inner(dma: pac::gpdma::Gpdma, channel_num: usize, index: usize) {
let ch = dma.ch(channel_num);
let sr = ch.sr().read();
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Dir {
MemoryToPeripheral,
PeripheralToMemory,
if sr.dtef() {
panic!(
"DMA: data transfer error on DMA@{:08x} channel {}",
dma.0 as u32, channel_num
);
}
if sr.usef() {
panic!(
"DMA: user settings error on DMA@{:08x} channel {}",
dma.0 as u32, channel_num
);
}
pub unsafe fn start_transfer(
dma: Gpdma,
channel_number: u8,
if sr.suspf() || sr.tcf() {
// disable all xxIEs to prevent the irq from firing again.
ch.cr().write(|_| {});
// Wake the future. It'll look at tcf and see it's set.
STATE.ch_wakers[index].wake();
}
}
pub type Request = u8;
#[cfg(dmamux)]
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
#[cfg(not(dmamux))]
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
pub(crate) mod sealed {
use super::*;
pub trait Channel {
fn regs(&self) -> pac::gpdma::Gpdma;
fn num(&self) -> usize;
fn index(&self) -> usize;
fn on_irq();
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>,
}
impl<'a, C: Channel> Transfer<'a, C> {
pub unsafe fn new_read<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
peri_addr: *mut W,
buf: &'a mut [W],
options: TransferOptions,
) -> Self {
Self::new_read_raw(channel, request, peri_addr, buf, options)
}
pub unsafe fn new_read_raw<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
peri_addr: *mut W,
buf: *mut [W],
options: TransferOptions,
) -> Self {
into_ref!(channel);
let (ptr, len) = super::slice_ptr_parts_mut(buf);
assert!(len > 0 && len <= 0xFFFF);
Self::new_inner(
channel,
request,
Dir::PeripheralToMemory,
peri_addr as *const u32,
ptr as *mut u32,
len,
true,
W::size(),
options,
)
}
pub unsafe fn new_write<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
buf: &'a [W],
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
Self::new_write_raw(channel, request, buf, peri_addr, options)
}
pub unsafe fn new_write_raw<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
buf: *const [W],
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
into_ref!(channel);
let (ptr, len) = super::slice_ptr_parts(buf);
assert!(len > 0 && len <= 0xFFFF);
Self::new_inner(
channel,
request,
Dir::MemoryToPeripheral,
peri_addr as *const u32,
ptr as *mut u32,
len,
true,
W::size(),
options,
)
}
pub unsafe fn new_write_repeated<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
repeated: &'a W,
count: usize,
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
into_ref!(channel);
Self::new_inner(
channel,
request,
Dir::MemoryToPeripheral,
peri_addr as *const u32,
repeated as *const W as *mut u32,
count,
false,
W::size(),
options,
)
}
unsafe fn new_inner(
channel: PeripheralRef<'a, C>,
request: Request,
dir: Dir,
peri_addr: *const u32,
@ -176,24 +238,19 @@ mod low_level_api {
mem_len: usize,
incr_mem: bool,
data_size: WordSize,
options: TransferOptions,
) {
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(
options.flow_ctrl == crate::dma::FlowControl::Dma,
"Peripheral flow control not supported"
);
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
_options: TransferOptions,
) -> Self {
let ch = channel.regs().ch(channel.num());
// "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::SeqCst);
let ch = dma.ch(channel_number as _);
let this = Self { channel };
#[cfg(dmamux)]
super::dmamux::configure_dmamux(&mut *this.channel, request);
// Reset ch
ch.cr().write(|w| w.set_reset(true));
ch.llr().write(|_| {}); // no linked list
ch.tr1().write(|w| {
w.set_sdw(data_size.into());
@ -234,72 +291,66 @@ mod low_level_api {
// Start it
w.set_en(true);
});
this
}
/// Stops the DMA channel.
pub unsafe fn request_stop(dma: Gpdma, channel_number: u8) {
// get a handle on the channel itself
let ch = dma.ch(channel_number as _);
pub fn request_stop(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
// Disable the channel. Keep the IEs enabled so the irqs still fire.
ch.cr().write(|w| {
w.set_tcie(true);
w.set_useie(true);
w.set_dteie(true);
w.set_suspie(true);
});
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
unsafe {
ch.cr().write(|w| {
w.set_tcie(true);
w.set_useie(true);
w.set_dteie(true);
w.set_suspie(true);
})
}
}
/// Gets the running status of the channel
pub unsafe fn is_running(dma: Gpdma, ch: u8) -> bool {
let ch = dma.ch(ch as _);
!ch.sr().read().tcf()
pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num());
!unsafe { ch.sr().read() }.tcf()
}
/// Gets the total remaining transfers for the channel
/// Note: this will be zero for transfers that completed without cancellation.
pub unsafe fn get_remaining_transfers(dma: Gpdma, ch: u8) -> u16 {
// get a handle on the channel itself
let ch = dma.ch(ch as _);
// read the remaining transfer count. If this is zero, the transfer completed fully.
ch.br1().read().bndt()
pub fn get_remaining_transfers(&self) -> u16 {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.br1().read() }.bndt()
}
/// Sets the waker for the specified DMA channel
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
STATE.channels[state_number].waker.register(waker);
pub fn blocking_wait(mut self) {
while self.is_running() {}
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
core::mem::forget(self);
}
}
/// Safety: Must be called with a matching set of parameters for a valid dma channel
pub unsafe fn on_irq_inner(dma: Gpdma, channel_num: u8, state_index: u8) {
let channel_num = channel_num as usize;
let state_index = state_index as usize;
impl<'a, C: Channel> Drop for Transfer<'a, C> {
fn drop(&mut self) {
self.request_stop();
while self.is_running() {}
let ch = dma.ch(channel_num);
let sr = ch.sr().read();
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
}
}
if sr.dtef() {
panic!(
"DMA: data transfer error on DMA@{:08x} channel {}",
dma.0 as u32, channel_num
);
}
if sr.usef() {
panic!(
"DMA: user settings error on DMA@{:08x} channel {}",
dma.0 as u32, channel_num
);
}
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
impl<'a, C: Channel> Future for Transfer<'a, C> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
STATE.ch_wakers[self.channel.index()].register(cx.waker());
if sr.suspf() || sr.tcf() {
// disable all xxIEs to prevent the irq from firing again.
ch.cr().write(|_| {});
// Wake the future. It'll look at tcf and see it's set.
STATE.channels[state_index].waker.wake();
if self.is_running() {
Poll::Pending
} else {
Poll::Ready(())
}
}
}

View File

@ -1,329 +1,48 @@
#[cfg(bdma)]
pub(crate) mod bdma;
#[cfg(dma)]
pub(crate) mod dma;
#[cfg(dma)]
pub use dma::*;
// stm32h7 has both dma and bdma. In that case, we export dma as "main" dma,
// and bdma as "secondary", under `embassy_stm32::dma::bdma`.
#[cfg(all(bdma, dma))]
pub mod bdma;
#[cfg(all(bdma, not(dma)))]
pub(crate) mod bdma;
#[cfg(all(bdma, not(dma)))]
pub use bdma::*;
#[cfg(gpdma)]
pub(crate) mod gpdma;
#[cfg(gpdma)]
pub use gpdma::*;
#[cfg(dmamux)]
mod dmamux;
#[cfg(gpdma)]
mod gpdma;
use core::future::Future;
pub(crate) mod ringbuffer;
pub mod word;
use core::mem;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
#[cfg(any(dma, bdma))]
use embassy_cortex_m::interrupt::Priority;
use embassy_hal_common::{impl_peripheral, into_ref};
use embassy_hal_common::impl_peripheral;
#[cfg(dmamux)]
pub use self::dmamux::*;
use crate::Peripheral;
#[cfg(feature = "unstable-pac")]
pub mod low_level {
pub use super::transfers::*;
}
pub(crate) use transfers::*;
#[cfg(any(bdma_v2, dma_v2, dmamux, gpdma))]
pub type Request = u8;
#[cfg(not(any(bdma_v2, dma_v2, dmamux, gpdma)))]
pub type Request = ();
pub(crate) mod sealed {
use super::*;
pub trait Word {}
pub trait Channel {
/// Starts this channel for writing a stream of words.
///
/// Safety:
/// - `buf` must point to a valid buffer for DMA reading.
/// - `buf` must be alive for the entire duration of the DMA transfer.
/// - `reg_addr` must be a valid peripheral register address to write to.
unsafe fn start_write<W: super::Word>(
&mut self,
request: Request,
buf: *const [W],
reg_addr: *mut W,
options: TransferOptions,
);
/// Starts this channel for writing a word repeatedly.
///
/// Safety:
/// - `reg_addr` must be a valid peripheral register address to write to.
unsafe fn start_write_repeated<W: super::Word>(
&mut self,
request: Request,
repeated: *const W,
count: usize,
reg_addr: *mut W,
options: TransferOptions,
);
/// Starts this channel for reading a stream of words.
///
/// Safety:
/// - `buf` must point to a valid buffer for DMA writing.
/// - `buf` must be alive for the entire duration of the DMA transfer.
/// - `reg_addr` must be a valid peripheral register address to read from.
unsafe fn start_read<W: super::Word>(
&mut self,
request: Request,
reg_addr: *const W,
buf: *mut [W],
options: TransferOptions,
);
/// DMA double-buffered mode is unsafe as UB can happen when the hardware writes to a buffer currently owned by the software
/// more information can be found here: https://github.com/embassy-rs/embassy/issues/702
/// This feature is now used solely for the purposes of implementing giant DMA transfers required for DCMI
unsafe fn start_double_buffered_read<W: super::Word>(
&mut self,
request: Request,
reg_addr: *const W,
buffer0: *mut W,
buffer1: *mut W,
buffer_len: usize,
options: TransferOptions,
);
unsafe fn set_buffer0<W: super::Word>(&mut self, buffer: *mut W);
unsafe fn set_buffer1<W: super::Word>(&mut self, buffer: *mut W);
unsafe fn is_buffer0_accessible(&mut self) -> bool;
/// Requests the channel to stop.
/// NOTE: The channel does not immediately stop, you have to wait
/// for `is_running() = false`.
fn request_stop(&mut self);
/// Returns whether this channel is running or stopped.
///
/// The channel stops running when it either completes or is manually stopped.
fn is_running(&self) -> bool;
/// Returns the total number of remaining transfers.
fn remaining_transfers(&mut self) -> u16;
/// Sets the waker that is called when this channel stops (either completed or manually stopped)
fn set_waker(&mut self, waker: &Waker);
/// This is called when this channel triggers an interrupt.
/// Note: Because some channels share an interrupt, this function might be
/// called for a channel that didn't trigger an interrupt.
fn on_irq();
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WordSize {
OneByte,
TwoBytes,
FourBytes,
enum Dir {
MemoryToPeripheral,
PeripheralToMemory,
}
impl WordSize {
pub fn bytes(&self) -> usize {
match self {
Self::OneByte => 1,
Self::TwoBytes => 2,
Self::FourBytes => 4,
}
}
}
pub trait Word: sealed::Word {
fn bits() -> WordSize;
}
impl sealed::Word for u8 {}
impl Word for u8 {
fn bits() -> WordSize {
WordSize::OneByte
}
}
impl sealed::Word for u16 {}
impl Word for u16 {
fn bits() -> WordSize {
WordSize::TwoBytes
}
}
impl sealed::Word for u32 {}
impl Word for u32 {
fn bits() -> WordSize {
WordSize::FourBytes
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Burst {
/// Single transfer
Single,
/// Incremental burst of 4 beats
Incr4,
/// Incremental burst of 8 beats
Incr8,
/// Incremental burst of 16 beats
Incr16,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FlowControl {
/// Flow control by DMA
Dma,
/// Flow control by peripheral
Peripheral,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FifoThreshold {
/// 1/4 full FIFO
Quarter,
/// 1/2 full FIFO
Half,
/// 3/4 full FIFO
ThreeQuarters,
/// Full FIFO
Full,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TransferOptions {
/// Peripheral burst transfer configuration
pub pburst: Burst,
/// Memory burst transfer configuration
pub mburst: Burst,
/// Flow control configuration
pub flow_ctrl: FlowControl,
/// FIFO threshold for DMA FIFO mode. If none, direct mode is used.
pub fifo_threshold: Option<FifoThreshold>,
}
impl Default for TransferOptions {
fn default() -> Self {
Self {
pburst: Burst::Single,
mburst: Burst::Single,
flow_ctrl: FlowControl::Dma,
fifo_threshold: None,
}
}
}
mod transfers {
use embassy_hal_common::PeripheralRef;
use super::*;
#[allow(unused)]
pub fn read<'a, W: Word>(
channel: impl Peripheral<P = impl Channel> + 'a,
request: Request,
reg_addr: *mut W,
buf: &'a mut [W],
) -> impl Future<Output = ()> + 'a {
assert!(buf.len() > 0 && buf.len() <= 0xFFFF);
into_ref!(channel);
unsafe { channel.start_read::<W>(request, reg_addr, buf, Default::default()) };
Transfer::new(channel)
}
#[allow(unused)]
pub fn write<'a, W: Word>(
channel: impl Peripheral<P = impl Channel> + 'a,
request: Request,
buf: &'a [W],
reg_addr: *mut W,
) -> impl Future<Output = ()> + 'a {
assert!(buf.len() > 0 && buf.len() <= 0xFFFF);
into_ref!(channel);
unsafe { channel.start_write::<W>(request, buf, reg_addr, Default::default()) };
Transfer::new(channel)
}
#[allow(unused)]
pub fn write_repeated<'a, W: Word>(
channel: impl Peripheral<P = impl Channel> + 'a,
request: Request,
repeated: *const W,
count: usize,
reg_addr: *mut W,
) -> impl Future<Output = ()> + 'a {
into_ref!(channel);
unsafe { channel.start_write_repeated::<W>(request, repeated, count, reg_addr, Default::default()) };
Transfer::new(channel)
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>,
}
impl<'a, C: Channel> Transfer<'a, C> {
pub(crate) fn new(channel: impl Peripheral<P = C> + 'a) -> Self {
into_ref!(channel);
Self { channel }
}
}
impl<'a, C: Channel> Drop for Transfer<'a, C> {
fn drop(&mut self) {
self.channel.request_stop();
while self.channel.is_running() {}
}
}
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
impl<'a, C: Channel> Future for Transfer<'a, C> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.channel.set_waker(cx.waker());
if self.channel.is_running() {
Poll::Pending
} else {
Poll::Ready(())
}
}
}
}
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
pub struct NoDma;
impl_peripheral!(NoDma);
// safety: must be called only once at startup
pub(crate) unsafe fn init(#[cfg(bdma)] bdma_priority: Priority, #[cfg(dma)] dma_priority: Priority) {
#[cfg(bdma)]
bdma::init(bdma_priority);
#[cfg(dma)]
dma::init(dma_priority);
#[cfg(dmamux)]
dmamux::init();
#[cfg(gpdma)]
gpdma::init();
}
// TODO: replace transmutes with core::ptr::metadata once it's stable
#[allow(unused)]
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (usize, usize) {
@ -334,3 +53,19 @@ pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (usize, usize) {
pub(crate) fn slice_ptr_parts_mut<T>(slice: *mut [T]) -> (usize, usize) {
unsafe { mem::transmute(slice) }
}
// safety: must be called only once at startup
pub(crate) unsafe fn init(
#[cfg(bdma)] bdma_priority: Priority,
#[cfg(dma)] dma_priority: Priority,
#[cfg(gpdma)] gpdma_priority: Priority,
) {
#[cfg(bdma)]
bdma::init(bdma_priority);
#[cfg(dma)]
dma::init(dma_priority);
#[cfg(gpdma)]
gpdma::init(gpdma_priority);
#[cfg(dmamux)]
dmamux::init();
}

View File

@ -0,0 +1,420 @@
#![cfg_attr(gpdma, allow(unused))]
use core::ops::Range;
use core::sync::atomic::{compiler_fence, Ordering};
use super::word::Word;
/// A "read-only" ring-buffer to be used together with the DMA controller which
/// writes in a circular way, "uncontrolled" to the buffer.
///
/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field
/// to the current register value. `ndtr` describes the current position of the DMA
/// write.
///
/// # Buffer layout
///
/// ```text
/// Without wraparound: With wraparound:
///
/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+
/// | | | | | |
/// v v v v v v
/// +-----------------------------------------+ +-----------------------------------------+
/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX|
/// +-----------------------------------------+ +-----------------------------------------+
/// ^ ^ ^ ^ ^ ^
/// | | | | | |
/// +- first --+ | +- end ------+ |
/// | | | |
/// +- end --------------------+ +- first ----------------+
/// ```
pub struct DmaRingBuffer<'a, W: Word> {
pub(crate) dma_buf: &'a mut [W],
first: usize,
pub ndtr: usize,
}
#[derive(Debug, PartialEq)]
pub struct OverrunError;
pub trait DmaCtrl {
/// Get the NDTR register value, i.e. the space left in the underlying
/// buffer until the dma writer wraps.
fn ndtr(&self) -> usize;
/// Get the transfer completed counter.
/// This counter is incremented by the dma controller when NDTR is reloaded,
/// i.e. when the writing wraps.
fn get_complete_count(&self) -> usize;
/// Reset the transfer completed counter to 0 and return the value just prior to the reset.
fn reset_complete_count(&mut self) -> usize;
}
impl<'a, W: Word> DmaRingBuffer<'a, W> {
pub fn new(dma_buf: &'a mut [W]) -> Self {
let ndtr = dma_buf.len();
Self {
dma_buf,
first: 0,
ndtr,
}
}
/// Reset the ring buffer to its initial state
pub fn clear(&mut self, mut dma: impl DmaCtrl) {
self.first = 0;
self.ndtr = self.dma_buf.len();
dma.reset_complete_count();
}
/// The buffer end position
fn end(&self) -> usize {
self.dma_buf.len() - self.ndtr
}
/// Returns whether the buffer is empty
pub fn is_empty(&self) -> bool {
self.first == self.end()
}
/// The current number of bytes in the buffer
/// This may change at any time if dma is currently active
pub fn len(&self) -> usize {
// Read out a stable end (the dma periheral can change it at anytime)
let end = self.end();
if self.first <= end {
// No wrap
end - self.first
} else {
self.dma_buf.len() - self.first + end
}
}
/// Read bytes from the ring buffer
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<usize, OverrunError> {
let end = self.end();
compiler_fence(Ordering::SeqCst);
if self.first == end {
// The buffer is currently empty
if dma.get_complete_count() > 0 {
// The DMA has written such that the ring buffer wraps at least once
self.ndtr = dma.ndtr();
if self.end() > self.first || dma.get_complete_count() > 1 {
return Err(OverrunError);
}
}
Ok(0)
} else if self.first < end {
// The available, unread portion in the ring buffer DOES NOT wrap
if dma.get_complete_count() > 1 {
return Err(OverrunError);
}
// Copy out the bytes from the dma buffer
let len = self.copy_to(buf, self.first..end);
compiler_fence(Ordering::SeqCst);
match dma.get_complete_count() {
0 => {
// The DMA writer has not wrapped before nor after the copy
}
1 => {
// The DMA writer has written such that the ring buffer now wraps
self.ndtr = dma.ndtr();
if self.end() > self.first || dma.get_complete_count() > 1 {
// The bytes that we have copied out have overflowed
// as the writer has now both wrapped and is currently writing
// within the region that we have just copied out
return Err(OverrunError);
}
}
_ => {
return Err(OverrunError);
}
}
self.first = (self.first + len) % self.dma_buf.len();
Ok(len)
} else {
// The available, unread portion in the ring buffer DOES wrap
// The DMA writer has wrapped since we last read and is currently
// writing (or the next byte added will be) in the beginning of the ring buffer.
let complete_count = dma.get_complete_count();
if complete_count > 1 {
return Err(OverrunError);
}
// If the unread portion wraps then the writer must also have wrapped
assert!(complete_count == 1);
if self.first + buf.len() < self.dma_buf.len() {
// The provided read buffer is not large enough to include all bytes from the tail of the dma buffer.
// Copy out from the dma buffer
let len = self.copy_to(buf, self.first..self.dma_buf.len());
compiler_fence(Ordering::SeqCst);
// We have now copied out the data from dma_buf
// Make sure that the just read part was not overwritten during the copy
self.ndtr = dma.ndtr();
if self.end() > self.first || dma.get_complete_count() > 1 {
// The writer has entered the data that we have just read since we read out `end` in the beginning and until now.
return Err(OverrunError);
}
self.first = (self.first + len) % self.dma_buf.len();
Ok(len)
} else {
// The provided read buffer is large enough to include all bytes from the tail of the dma buffer,
// so the next read will not have any unread tail bytes in the ring buffer.
// Copy out from the dma buffer
let tail = self.copy_to(buf, self.first..self.dma_buf.len());
let head = self.copy_to(&mut buf[tail..], 0..end);
compiler_fence(Ordering::SeqCst);
// We have now copied out the data from dma_buf
// Reset complete counter and make sure that the just read part was not overwritten during the copy
self.ndtr = dma.ndtr();
let complete_count = dma.reset_complete_count();
if self.end() > self.first || complete_count > 1 {
return Err(OverrunError);
}
self.first = head;
Ok(tail + head)
}
}
}
/// Copy from the dma buffer at `data_range` into `buf`
fn copy_to(&mut self, buf: &mut [W], data_range: Range<usize>) -> usize {
// Limit the number of bytes that can be copied
let length = usize::min(data_range.len(), buf.len());
// Copy from dma buffer into read buffer
// We need to do it like this instead of a simple copy_from_slice() because
// reading from a part of memory that may be simultaneously written to is unsafe
unsafe {
let dma_buf = self.dma_buf.as_ptr();
for i in 0..length {
buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize));
}
}
length
}
}
#[cfg(test)]
mod tests {
use core::array;
use core::cell::RefCell;
use super::*;
struct TestCtrl {
next_ndtr: RefCell<Option<usize>>,
complete_count: usize,
}
impl TestCtrl {
pub const fn new() -> Self {
Self {
next_ndtr: RefCell::new(None),
complete_count: 0,
}
}
pub fn set_next_ndtr(&mut self, ndtr: usize) {
self.next_ndtr.borrow_mut().replace(ndtr);
}
}
impl DmaCtrl for &mut TestCtrl {
fn ndtr(&self) -> usize {
self.next_ndtr.borrow_mut().unwrap()
}
fn get_complete_count(&self) -> usize {
self.complete_count
}
fn reset_complete_count(&mut self) -> usize {
let old = self.complete_count;
self.complete_count = 0;
old
}
}
#[test]
fn empty() {
let mut dma_buf = [0u8; 16];
let ringbuf = DmaRingBuffer::new(&mut dma_buf);
assert!(ringbuf.is_empty());
assert_eq!(0, ringbuf.len());
}
#[test]
fn can_read() {
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.ndtr = 6;
assert!(!ringbuf.is_empty());
assert_eq!(10, ringbuf.len());
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([0, 1], buf);
assert_eq!(8, ringbuf.len());
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([2, 3], buf);
assert_eq!(6, ringbuf.len());
let mut buf = [0; 8];
assert_eq!(6, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]);
assert_eq!(0, ringbuf.len());
let mut buf = [0; 2];
assert_eq!(0, ringbuf.read(&mut ctrl, &mut buf).unwrap());
}
#[test]
fn can_read_with_wrap() {
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 12;
ringbuf.ndtr = 10;
// The dma controller has written 4 + 6 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(10);
assert!(!ringbuf.is_empty());
assert_eq!(6 + 4, ringbuf.len());
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([12, 13], buf);
assert_eq!(6 + 2, ringbuf.len());
let mut buf = [0; 4];
assert_eq!(4, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([14, 15, 0, 1], buf);
assert_eq!(4, ringbuf.len());
}
#[test]
fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() {
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 2;
ringbuf.ndtr = 6;
// The dma controller has written 6 + 2 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(14);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([2, 3], buf);
assert_eq!(1, ctrl.complete_count); // The interrupt flag IS NOT cleared
}
#[test]
fn can_read_when_dma_writer_is_wrapped_and_read_wraps() {
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 12;
ringbuf.ndtr = 10;
// The dma controller has written 6 + 2 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(14);
let mut buf = [0; 10];
assert_eq!(10, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([12, 13, 14, 15, 0, 1, 2, 3, 4, 5], buf);
assert_eq!(0, ctrl.complete_count); // The interrupt flag IS cleared
}
#[test]
fn cannot_read_when_dma_writer_wraps_with_same_ndtr() {
let mut dma_buf = [0u8; 16];
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 6;
ringbuf.ndtr = 10;
ctrl.set_next_ndtr(9);
assert!(ringbuf.is_empty()); // The ring buffer thinks that it is empty
// The dma controller has written exactly 16 bytes
ctrl.complete_count = 1;
let mut buf = [0; 2];
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
}
#[test]
fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() {
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 2;
ringbuf.ndtr = 6;
// The dma controller has written 6 + 3 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(13);
let mut buf = [0; 2];
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
}
#[test]
fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() {
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 12;
ringbuf.ndtr = 10;
// The dma controller has written 6 + 13 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(3);
let mut buf = [0; 2];
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
}
}

View File

@ -0,0 +1,79 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WordSize {
OneByte,
TwoBytes,
FourBytes,
}
impl WordSize {
pub fn bytes(&self) -> usize {
match self {
Self::OneByte => 1,
Self::TwoBytes => 2,
Self::FourBytes => 4,
}
}
}
mod sealed {
pub trait Word {}
}
pub trait Word: sealed::Word + Default + Copy + 'static {
fn size() -> WordSize;
fn bits() -> usize;
}
macro_rules! impl_word {
(_, $T:ident, $bits:literal, $size:ident) => {
impl sealed::Word for $T {}
impl Word for $T {
fn bits() -> usize {
$bits
}
fn size() -> WordSize {
WordSize::$size
}
}
};
($T:ident, $uX:ident, $bits:literal, $size:ident) => {
#[repr(transparent)]
#[derive(Copy, Clone, Default)]
pub struct $T(pub $uX);
impl_word!(_, $T, $bits, $size);
};
}
impl_word!(U1, u8, 1, OneByte);
impl_word!(U2, u8, 2, OneByte);
impl_word!(U3, u8, 3, OneByte);
impl_word!(U4, u8, 4, OneByte);
impl_word!(U5, u8, 5, OneByte);
impl_word!(U6, u8, 6, OneByte);
impl_word!(U7, u8, 7, OneByte);
impl_word!(_, u8, 8, OneByte);
impl_word!(U9, u16, 9, TwoBytes);
impl_word!(U10, u16, 10, TwoBytes);
impl_word!(U11, u16, 11, TwoBytes);
impl_word!(U12, u16, 12, TwoBytes);
impl_word!(U13, u16, 13, TwoBytes);
impl_word!(U14, u16, 14, TwoBytes);
impl_word!(U15, u16, 15, TwoBytes);
impl_word!(_, u16, 16, TwoBytes);
impl_word!(U17, u32, 17, FourBytes);
impl_word!(U18, u32, 18, FourBytes);
impl_word!(U19, u32, 19, FourBytes);
impl_word!(U20, u32, 20, FourBytes);
impl_word!(U21, u32, 21, FourBytes);
impl_word!(U22, u32, 22, FourBytes);
impl_word!(U23, u32, 23, FourBytes);
impl_word!(U24, u32, 24, FourBytes);
impl_word!(U25, u32, 25, FourBytes);
impl_word!(U26, u32, 26, FourBytes);
impl_word!(U27, u32, 27, FourBytes);
impl_word!(U28, u32, 28, FourBytes);
impl_word!(U29, u32, 29, FourBytes);
impl_word!(U30, u32, 30, FourBytes);
impl_word!(U31, u32, 31, FourBytes);
impl_word!(_, u32, 32, FourBytes);

View File

@ -2,7 +2,7 @@ use atomic_polyfill::{fence, Ordering};
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};
use super::{family, Error, FlashLayout, FlashRegion, FlashSector, FLASH_BASE, FLASH_SIZE, WRITE_SIZE};
use super::{family, Error, FlashLayout, FlashRegion, FlashSector, FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE};
use crate::flash::FlashBank;
use crate::Peripheral;
@ -162,6 +162,35 @@ impl FlashRegion {
}
}
impl embedded_storage::nor_flash::ErrorType for Flash<'_> {
type Error = Error;
}
impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_> {
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(offset, bytes)
}
fn capacity(&self) -> usize {
FLASH_SIZE
}
}
impl embedded_storage::nor_flash::NorFlash for Flash<'_> {
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = MAX_ERASE_SIZE;
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(offset, bytes)
}
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.blocking_erase(from, to)
}
}
foreach_flash_region! {
($type_name:ident, $write_size:literal, $erase_size:literal) => {
impl crate::_generated::flash_regions::$type_name<'_> {

View File

@ -7,6 +7,7 @@ mod common;
pub use common::*;
pub use crate::_generated::flash_regions::*;
pub use crate::_generated::MAX_ERASE_SIZE;
pub use crate::pac::{FLASH_BASE, FLASH_SIZE, WRITE_SIZE};
#[derive(Debug)]

View File

@ -1,6 +1,5 @@
use core::cmp;
use core::future::poll_fn;
use core::sync::atomic::{AtomicUsize, Ordering};
use core::task::Poll;
use embassy_embedded_hal::SetConfig;
@ -8,7 +7,7 @@ use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use crate::dma::NoDma;
use crate::dma::{NoDma, Transfer};
use crate::gpio::sealed::AFType;
use crate::gpio::Pull;
use crate::i2c::{Error, Instance, SclPin, SdaPin};
@ -35,14 +34,12 @@ impl Default for Config {
pub struct State {
waker: AtomicWaker,
chunks_transferred: AtomicUsize,
}
impl State {
pub(crate) const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
chunks_transferred: AtomicUsize::new(0),
}
}
}
@ -130,10 +127,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
let isr = regs.isr().read();
if isr.tcr() || isr.tc() {
let state = T::state();
let transferred = state.chunks_transferred.load(Ordering::Relaxed);
state.chunks_transferred.store(transferred + 1, Ordering::Relaxed);
state.waker.wake();
T::state().waker.wake();
}
// The flag can only be cleared by writting to nbytes, we won't do that here, so disable
// the interrupt
@ -457,12 +451,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
TXDMA: crate::i2c::TxDma<T>,
{
let total_len = write.len();
let completed_chunks = total_len / 255;
let total_chunks = if completed_chunks * 255 == total_len {
completed_chunks
} else {
completed_chunks + 1
};
let dma_transfer = unsafe {
let regs = T::regs();
@ -476,11 +464,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
let ch = &mut self.tx_dma;
let request = ch.request();
crate::dma::write(ch, request, write, dst)
Transfer::new_write(ch, request, write, dst, Default::default())
};
let state = T::state();
state.chunks_transferred.store(0, Ordering::Relaxed);
let mut remaining_len = total_len;
let on_drop = OnDrop::new(|| {
@ -495,33 +482,35 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
}
});
// NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
if first_slice {
unsafe {
Self::master_write(
address,
total_len.min(255),
Stop::Software,
(total_chunks != 1) || !last_slice,
&check_timeout,
)?;
}
} else {
unsafe {
Self::master_continue(total_len.min(255), (total_chunks != 1) || !last_slice, &check_timeout)?;
T::regs().cr1().modify(|w| w.set_tcie(true));
}
}
poll_fn(|cx| {
state.waker.register(cx.waker());
let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed);
if chunks_transferred == total_chunks {
let isr = unsafe { T::regs().isr().read() };
if remaining_len == total_len {
// NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
if first_slice {
unsafe {
Self::master_write(
address,
total_len.min(255),
Stop::Software,
(total_len > 255) || !last_slice,
&check_timeout,
)?;
}
} else {
unsafe {
Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, &check_timeout)?;
T::regs().cr1().modify(|w| w.set_tcie(true));
}
}
} else if !(isr.tcr() || isr.tc()) {
// poll_fn was woken without an interrupt present
return Poll::Pending;
} else if remaining_len == 0 {
return Poll::Ready(Ok(()));
} else if chunks_transferred != 0 {
remaining_len = remaining_len.saturating_sub(255);
let last_piece = (chunks_transferred + 1 == total_chunks) && last_slice;
} else {
let last_piece = (remaining_len <= 255) && last_slice;
// NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
unsafe {
@ -531,6 +520,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
T::regs().cr1().modify(|w| w.set_tcie(true));
}
}
remaining_len = remaining_len.saturating_sub(255);
Poll::Pending
})
.await?;
@ -559,12 +550,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
RXDMA: crate::i2c::RxDma<T>,
{
let total_len = buffer.len();
let completed_chunks = total_len / 255;
let total_chunks = if completed_chunks * 255 == total_len {
completed_chunks
} else {
completed_chunks + 1
};
let dma_transfer = unsafe {
let regs = T::regs();
@ -576,11 +561,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
let ch = &mut self.rx_dma;
let request = ch.request();
crate::dma::read(ch, request, src, buffer)
Transfer::new_read(ch, request, src, buffer, Default::default())
};
let state = T::state();
state.chunks_transferred.store(0, Ordering::Relaxed);
let mut remaining_len = total_len;
let on_drop = OnDrop::new(|| {
@ -593,27 +577,29 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
}
});
// NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
unsafe {
Self::master_read(
address,
total_len.min(255),
Stop::Software,
total_chunks != 1,
restart,
&check_timeout,
)?;
}
poll_fn(|cx| {
state.waker.register(cx.waker());
let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed);
if chunks_transferred == total_chunks {
let isr = unsafe { T::regs().isr().read() };
if remaining_len == total_len {
// NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
unsafe {
Self::master_read(
address,
total_len.min(255),
Stop::Software,
total_len > 255,
restart,
&check_timeout,
)?;
}
} else if !(isr.tcr() || isr.tc()) {
// poll_fn was woken without an interrupt present
return Poll::Pending;
} else if remaining_len == 0 {
return Poll::Ready(Ok(()));
} else if chunks_transferred != 0 {
remaining_len = remaining_len.saturating_sub(255);
let last_piece = chunks_transferred + 1 == total_chunks;
} else {
let last_piece = remaining_len <= 255;
// NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
unsafe {
@ -623,6 +609,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
T::regs().cr1().modify(|w| w.set_tcie(true));
}
}
remaining_len = remaining_len.saturating_sub(255);
Poll::Pending
})
.await?;

178
embassy-stm32/src/ipcc.rs Normal file
View File

@ -0,0 +1,178 @@
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use crate::ipcc::sealed::Instance;
use crate::peripherals::IPCC;
use crate::rcc::sealed::RccPeripheral;
#[non_exhaustive]
#[derive(Clone, Copy, Default)]
pub struct Config {
// TODO: add IPCC peripheral configuration, if any, here
// reserved for future use
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub enum IpccChannel {
Channel1 = 0,
Channel2 = 1,
Channel3 = 2,
Channel4 = 3,
Channel5 = 4,
Channel6 = 5,
}
pub(crate) mod sealed {
pub trait Instance: crate::rcc::RccPeripheral {
fn regs() -> crate::pac::ipcc::Ipcc;
fn set_cpu2(enabled: bool);
}
}
pub struct Ipcc<'d> {
_peri: PeripheralRef<'d, IPCC>,
}
impl<'d> Ipcc<'d> {
pub fn new(peri: impl Peripheral<P = IPCC> + 'd, _config: Config) -> Self {
into_ref!(peri);
Self { _peri: peri }
}
pub fn init(&mut self) {
IPCC::enable();
IPCC::reset();
IPCC::set_cpu2(true);
unsafe { _configure_pwr() };
let regs = IPCC::regs();
unsafe {
regs.cpu(0).cr().modify(|w| {
w.set_rxoie(true);
w.set_txfie(true);
})
}
}
pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
}
pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
}
pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
}
pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
}
pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
}
pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
}
pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
}
pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(1).mr().read().chfm(channel as usize) }
}
/// clears IPCC receive channel status for CPU1
pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
}
/// clears IPCC receive channel status for CPU2
pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
}
pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
}
pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
}
pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool {
let regs = IPCC::regs();
unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
}
pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool {
let regs = IPCC::regs();
unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
}
pub fn is_tx_pending(&self, channel: IpccChannel) -> bool {
!self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel)
}
pub fn is_rx_pending(&self, channel: IpccChannel) -> bool {
self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel)
}
}
impl sealed::Instance for crate::peripherals::IPCC {
fn regs() -> crate::pac::ipcc::Ipcc {
crate::pac::IPCC
}
fn set_cpu2(enabled: bool) {
unsafe { crate::pac::PWR.cr4().modify(|w| w.set_c2boot(enabled)) }
}
}
unsafe fn _configure_pwr() {
let rcc = crate::pac::RCC;
// set RF wake-up clock = LSE
rcc.csr().modify(|w| w.set_rfwkpsel(0b01));
}

View File

@ -44,17 +44,19 @@ pub mod i2c;
#[cfg(crc)]
pub mod crc;
pub mod flash;
#[cfg(stm32wb)]
pub mod ipcc;
pub mod pwm;
#[cfg(quadspi)]
pub mod qspi;
#[cfg(rng)]
pub mod rng;
#[cfg(all(rtc, not(rtc_v1)))]
pub mod rtc;
#[cfg(sdmmc)]
pub mod sdmmc;
#[cfg(spi)]
pub mod spi;
#[cfg(stm32wl)]
pub mod subghz;
#[cfg(usart)]
pub mod usart;
#[cfg(all(usb, feature = "time"))]
@ -76,7 +78,6 @@ pub(crate) mod _generated {
// Reexports
pub use _generated::{peripherals, Peripherals};
pub use embassy_cortex_m::executor;
#[cfg(any(dma, bdma))]
use embassy_cortex_m::interrupt::Priority;
pub use embassy_cortex_m::interrupt::_export::interrupt;
pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
@ -94,6 +95,8 @@ pub struct Config {
pub bdma_interrupt_priority: Priority,
#[cfg(dma)]
pub dma_interrupt_priority: Priority,
#[cfg(gpdma)]
pub gpdma_interrupt_priority: Priority,
}
impl Default for Config {
@ -106,6 +109,8 @@ impl Default for Config {
bdma_interrupt_priority: Priority::P0,
#[cfg(dma)]
dma_interrupt_priority: Priority::P0,
#[cfg(gpdma)]
gpdma_interrupt_priority: Priority::P0,
}
}
}
@ -149,6 +154,8 @@ pub fn init(config: Config) -> Peripherals {
config.bdma_interrupt_priority,
#[cfg(dma)]
config.dma_interrupt_priority,
#[cfg(gpdma)]
config.gpdma_interrupt_priority,
);
#[cfg(feature = "exti")]
exti::init();

View File

@ -5,7 +5,7 @@ pub mod enums;
use embassy_hal_common::{into_ref, PeripheralRef};
use enums::*;
use crate::dma::TransferOptions;
use crate::dma::Transfer;
use crate::gpio::sealed::AFType;
use crate::gpio::AnyPin;
use crate::pac::quadspi::Quadspi as Regs;
@ -230,9 +230,6 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
unsafe {
self.setup_transaction(QspiMode::IndirectWrite, &transaction);
let request = self.dma.request();
let options = TransferOptions::default();
T::REGS.ccr().modify(|v| {
v.set_fmode(QspiMode::IndirectRead.into());
});
@ -241,12 +238,18 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
v.set_address(current_ar);
});
self.dma
.start_read(request, T::REGS.dr().ptr() as *mut u8, buf, options);
let request = self.dma.request();
let transfer = Transfer::new_read(
&mut self.dma,
request,
T::REGS.dr().ptr() as *mut u8,
buf,
Default::default(),
);
T::REGS.cr().modify(|v| v.set_dmaen(true));
while self.dma.is_running() {}
transfer.blocking_wait();
}
}
@ -257,19 +260,22 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
unsafe {
self.setup_transaction(QspiMode::IndirectWrite, &transaction);
let request = self.dma.request();
let options = TransferOptions::default();
T::REGS.ccr().modify(|v| {
v.set_fmode(QspiMode::IndirectWrite.into());
});
self.dma
.start_write(request, buf, T::REGS.dr().ptr() as *mut u8, options);
let request = self.dma.request();
let transfer = Transfer::new_write(
&mut self.dma,
request,
buf,
T::REGS.dr().ptr() as *mut u8,
Default::default(),
);
T::REGS.cr().modify(|v| v.set_dmaen(true));
while self.dma.is_running() {}
transfer.blocking_wait();
}
}

View File

@ -29,10 +29,66 @@ pub struct Config {
pub pclk1: Option<Hertz>,
pub pclk2: Option<Hertz>,
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
pub plli2s: Option<Hertz>,
pub pll48: bool,
}
unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48clk: bool) -> PllResults {
#[cfg(stm32f410)]
unsafe fn setup_i2s_pll(_vco_in: u32, _plli2s: Option<u32>) -> Option<u32> {
None
}
// Not currently implemented, but will be in the future
#[cfg(any(stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))]
unsafe fn setup_i2s_pll(_vco_in: u32, _plli2s: Option<u32>) -> Option<u32> {
None
}
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
unsafe fn setup_i2s_pll(vco_in: u32, plli2s: Option<u32>) -> Option<u32> {
let min_div = 2;
let max_div = 7;
let target = match plli2s {
Some(target) => target,
None => return None,
};
// We loop through the possible divider values to find the best configuration. Looping
// through all possible "N" values would result in more iterations.
let (n, outdiv, output, _error) = (min_div..=max_div)
.filter_map(|outdiv| {
let target_vco_out = match target.checked_mul(outdiv) {
Some(x) => x,
None => return None,
};
let n = (target_vco_out + (vco_in >> 1)) / vco_in;
let vco_out = vco_in * n;
if !(100_000_000..=432_000_000).contains(&vco_out) {
return None;
}
let output = vco_out / outdiv;
let error = (output as i32 - target as i32).unsigned_abs();
Some((n, outdiv, output, error))
})
.min_by_key(|(_, _, _, error)| *error)?;
RCC.plli2scfgr().modify(|w| {
w.set_plli2sn(n as u16);
w.set_plli2sr(outdiv as u8);
});
Some(output)
}
unsafe fn setup_pll(
pllsrcclk: u32,
use_hse: bool,
pllsysclk: Option<u32>,
plli2s: Option<u32>,
pll48clk: bool,
) -> PllResults {
use crate::pac::rcc::vals::{Pllp, Pllsrc};
let sysclk = pllsysclk.unwrap_or(pllsrcclk);
@ -43,6 +99,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48
use_pll: false,
pllsysclk: None,
pll48clk: None,
plli2sclk: None,
};
}
// Input divisor from PLL source clock, must result to frequency in
@ -101,6 +158,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48
use_pll: true,
pllsysclk: Some(real_pllsysclk),
pll48clk: if pll48clk { Some(real_pll48clk) } else { None },
plli2sclk: setup_i2s_pll(vco_in, plli2s),
}
}
@ -286,6 +344,10 @@ pub(crate) unsafe fn init(config: Config) {
pllsrcclk,
config.hse.is_some(),
if sysclk_on_pll { Some(sysclk) } else { None },
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
config.plli2s.map(|i2s| i2s.0),
#[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))]
None,
config.pll48,
);
@ -376,6 +438,13 @@ pub(crate) unsafe fn init(config: Config) {
while !RCC.cr().read().pllrdy() {}
}
#[cfg(not(stm32f410))]
if plls.plli2sclk.is_some() {
RCC.cr().modify(|w| w.set_plli2son(true));
while !RCC.cr().read().plli2srdy() {}
}
RCC.cfgr().modify(|w| {
w.set_ppre2(Ppre(ppre2_bits));
w.set_ppre1(Ppre(ppre1_bits));
@ -409,6 +478,12 @@ pub(crate) unsafe fn init(config: Config) {
ahb3: Hertz(hclk),
pll48: plls.pll48clk.map(Hertz),
#[cfg(not(stm32f410))]
plli2s: plls.plli2sclk.map(Hertz),
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
pllsai: None,
});
}
@ -416,6 +491,8 @@ struct PllResults {
use_pll: bool,
pllsysclk: Option<u32>,
pll48clk: Option<u32>,
#[allow(dead_code)]
plli2sclk: Option<u32>,
}
mod max {

View File

@ -60,6 +60,12 @@ pub struct Clocks {
#[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))]
pub pll48: Option<Hertz>,
#[cfg(all(rcc_f4, not(stm32f410)))]
pub plli2s: Option<Hertz>,
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
pub pllsai: Option<Hertz>,
#[cfg(stm32f1)]
pub adc: Hertz,

View File

@ -0,0 +1,201 @@
#[cfg(feature = "chrono")]
use core::convert::From;
#[cfg(feature = "chrono")]
use chrono::{self, Datelike, NaiveDate, Timelike, Weekday};
use super::byte_to_bcd2;
use crate::pac::rtc::Rtc;
/// Errors regarding the [`DateTime`] struct.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Error {
/// The [DateTime] contains an invalid year value. Must be between `0..=4095`.
InvalidYear,
/// The [DateTime] contains an invalid month value. Must be between `1..=12`.
InvalidMonth,
/// The [DateTime] contains an invalid day value. Must be between `1..=31`.
InvalidDay,
/// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday.
InvalidDayOfWeek(
/// The value of the DayOfWeek that was given.
u8,
),
/// The [DateTime] contains an invalid hour value. Must be between `0..=23`.
InvalidHour,
/// The [DateTime] contains an invalid minute value. Must be between `0..=59`.
InvalidMinute,
/// The [DateTime] contains an invalid second value. Must be between `0..=59`.
InvalidSecond,
}
/// Structure containing date and time information
pub struct DateTime {
/// 0..4095
pub year: u16,
/// 1..12, 1 is January
pub month: u8,
/// 1..28,29,30,31 depending on month
pub day: u8,
///
pub day_of_week: DayOfWeek,
/// 0..23
pub hour: u8,
/// 0..59
pub minute: u8,
/// 0..59
pub second: u8,
}
#[cfg(feature = "chrono")]
impl From<chrono::NaiveDateTime> for DateTime {
fn from(date_time: chrono::NaiveDateTime) -> Self {
Self {
year: date_time.year() as u16,
month: date_time.month() as u8,
day: date_time.day() as u8,
day_of_week: date_time.weekday().into(),
hour: date_time.hour() as u8,
minute: date_time.minute() as u8,
second: date_time.second() as u8,
}
}
}
#[cfg(feature = "chrono")]
impl From<DateTime> for chrono::NaiveDateTime {
fn from(date_time: DateTime) -> Self {
NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32)
.unwrap()
.and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32)
.unwrap()
}
}
/// A day of the week
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub enum DayOfWeek {
Monday = 0,
Tuesday = 1,
Wednesday = 2,
Thursday = 3,
Friday = 4,
Saturday = 5,
Sunday = 6,
}
#[cfg(feature = "chrono")]
impl From<chrono::Weekday> for DayOfWeek {
fn from(weekday: Weekday) -> Self {
day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap()
}
}
#[cfg(feature = "chrono")]
impl From<DayOfWeek> for chrono::Weekday {
fn from(weekday: DayOfWeek) -> Self {
match weekday {
DayOfWeek::Monday => Weekday::Mon,
DayOfWeek::Tuesday => Weekday::Tue,
DayOfWeek::Wednesday => Weekday::Wed,
DayOfWeek::Thursday => Weekday::Thu,
DayOfWeek::Friday => Weekday::Fri,
DayOfWeek::Saturday => Weekday::Sat,
DayOfWeek::Sunday => Weekday::Sun,
}
}
}
fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
Ok(match v {
0 => DayOfWeek::Monday,
1 => DayOfWeek::Tuesday,
2 => DayOfWeek::Wednesday,
3 => DayOfWeek::Thursday,
4 => DayOfWeek::Friday,
5 => DayOfWeek::Saturday,
6 => DayOfWeek::Sunday,
x => return Err(Error::InvalidDayOfWeek(x)),
})
}
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
dotw as u8
}
pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
if dt.year > 4095 {
Err(Error::InvalidYear)
} else if dt.month < 1 || dt.month > 12 {
Err(Error::InvalidMonth)
} else if dt.day < 1 || dt.day > 31 {
Err(Error::InvalidDay)
} else if dt.hour > 23 {
Err(Error::InvalidHour)
} else if dt.minute > 59 {
Err(Error::InvalidMinute)
} else if dt.second > 59 {
Err(Error::InvalidSecond)
} else {
Ok(())
}
}
pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) {
let (ht, hu) = byte_to_bcd2(t.hour as u8);
let (mnt, mnu) = byte_to_bcd2(t.minute as u8);
let (st, su) = byte_to_bcd2(t.second as u8);
let (dt, du) = byte_to_bcd2(t.day as u8);
let (mt, mu) = byte_to_bcd2(t.month as u8);
let yr = t.year as u16;
let yr_offset = (yr - 1970_u16) as u8;
let (yt, yu) = byte_to_bcd2(yr_offset);
unsafe {
use crate::pac::rtc::vals::Ampm;
rtc.tr().write(|w| {
w.set_ht(ht);
w.set_hu(hu);
w.set_mnt(mnt);
w.set_mnu(mnu);
w.set_st(st);
w.set_su(su);
w.set_pm(Ampm::AM);
});
rtc.dr().write(|w| {
w.set_dt(dt);
w.set_du(du);
w.set_mt(mt > 0);
w.set_mu(mu);
w.set_yt(yt);
w.set_yu(yu);
w.set_wdu(day_of_week_to_u8(t.day_of_week));
});
}
}
pub(super) fn datetime(
year: u16,
month: u8,
day: u8,
day_of_week: u8,
hour: u8,
minute: u8,
second: u8,
) -> Result<DateTime, Error> {
let day_of_week = day_of_week_from_u8(day_of_week)?;
Ok(DateTime {
year,
month,
day,
day_of_week,
hour,
minute,
second,
})
}

View File

@ -0,0 +1,249 @@
//! RTC peripheral abstraction
use core::marker::PhantomData;
mod datetime;
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
/// refer to AN4759 to compare features of RTC2 and RTC3
#[cfg_attr(any(rtc_v1), path = "v1.rs")]
#[cfg_attr(
any(
rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb
),
path = "v2.rs"
)]
#[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")]
mod _version;
pub use _version::*;
use embassy_hal_common::Peripheral;
/// Errors that can occur on methods on [RtcClock]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RtcError {
/// An invalid DateTime was given or stored on the hardware.
InvalidDateTime(DateTimeError),
/// The RTC clock is not running
NotRunning,
}
/// RTC Abstraction
pub struct Rtc<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
rtc_config: RtcConfig,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum RtcClockSource {
/// 00: No clock
NoClock = 0b00,
/// 01: LSE oscillator clock used as RTC clock
LSE = 0b01,
/// 10: LSI oscillator clock used as RTC clock
LSI = 0b10,
/// 11: HSE oscillator clock divided by 32 used as RTC clock
HSE = 0b11,
}
#[derive(Copy, Clone, PartialEq)]
pub struct RtcConfig {
/// RTC clock source
clock_config: RtcClockSource,
/// Asynchronous prescaler factor
/// This is the asynchronous division factor:
/// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1)
/// ck_apre drives the subsecond register
async_prescaler: u8,
/// Synchronous prescaler factor
/// This is the synchronous division factor:
/// ck_spre frequency = ck_apre frequency/(PREDIV_S+1)
/// ck_spre must be 1Hz
sync_prescaler: u16,
}
impl Default for RtcConfig {
/// LSI with prescalers assuming 32.768 kHz.
/// Raw sub-seconds in 1/256.
fn default() -> Self {
RtcConfig {
clock_config: RtcClockSource::LSI,
async_prescaler: 127,
sync_prescaler: 255,
}
}
}
impl RtcConfig {
/// Sets the clock source of RTC config
pub fn clock_config(mut self, cfg: RtcClockSource) -> Self {
self.clock_config = cfg;
self
}
/// Set the asynchronous prescaler of RTC config
pub fn async_prescaler(mut self, prescaler: u8) -> Self {
self.async_prescaler = prescaler;
self
}
/// Set the synchronous prescaler of RTC config
pub fn sync_prescaler(mut self, prescaler: u16) -> Self {
self.sync_prescaler = prescaler;
self
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum RtcCalibrationCyclePeriod {
/// 8-second calibration period
Seconds8,
/// 16-second calibration period
Seconds16,
/// 32-second calibration period
Seconds32,
}
impl Default for RtcCalibrationCyclePeriod {
fn default() -> Self {
RtcCalibrationCyclePeriod::Seconds32
}
}
impl<'d, T: Instance> Rtc<'d, T> {
pub fn new(_rtc: impl Peripheral<P = T> + 'd, rtc_config: RtcConfig) -> Self {
unsafe { T::enable_peripheral_clk() };
let mut rtc_struct = Self {
phantom: PhantomData,
rtc_config,
};
rtc_struct.apply_config(rtc_config);
rtc_struct
}
/// Set the datetime to a new value.
///
/// # Errors
///
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
self.write(true, |rtc| self::datetime::write_date_time(rtc, t));
Ok(())
}
/// Return the current datetime.
///
/// # Errors
///
/// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`].
pub fn now(&self) -> Result<DateTime, RtcError> {
let r = T::regs();
unsafe {
let tr = r.tr().read();
let second = bcd2_to_byte((tr.st(), tr.su()));
let minute = bcd2_to_byte((tr.mnt(), tr.mnu()));
let hour = bcd2_to_byte((tr.ht(), tr.hu()));
// Reading either RTC_SSR or RTC_TR locks the values in the higher-order
// calendar shadow registers until RTC_DR is read.
let dr = r.dr().read();
let weekday = dr.wdu();
let day = bcd2_to_byte((dr.dt(), dr.du()));
let month = bcd2_to_byte((dr.mt() as u8, dr.mu()));
let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16;
self::datetime::datetime(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime)
}
}
/// Check if daylight savings time is active.
pub fn get_daylight_savings(&self) -> bool {
let cr = unsafe { T::regs().cr().read() };
cr.bkp()
}
/// Enable/disable daylight savings time.
pub fn set_daylight_savings(&mut self, daylight_savings: bool) {
self.write(true, |rtc| {
unsafe { rtc.cr().modify(|w| w.set_bkp(daylight_savings)) };
})
}
pub fn get_config(&self) -> RtcConfig {
self.rtc_config
}
pub const BACKUP_REGISTER_COUNT: usize = T::BACKUP_REGISTER_COUNT;
/// Read content of the backup register.
///
/// The registers retain their values during wakes from standby mode or system resets. They also
/// retain their value when Vdd is switched off as long as V_BAT is powered.
pub fn read_backup_register(&self, register: usize) -> Option<u32> {
T::read_backup_register(&T::regs(), register)
}
/// Set content of the backup register.
///
/// The registers retain their values during wakes from standby mode or system resets. They also
/// retain their value when Vdd is switched off as long as V_BAT is powered.
pub fn write_backup_register(&self, register: usize, value: u32) {
T::write_backup_register(&T::regs(), register, value)
}
}
pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) {
let mut bcd_high: u8 = 0;
let mut value = byte;
while value >= 10 {
bcd_high += 1;
value -= 10;
}
(bcd_high, ((bcd_high << 4) | value) as u8)
}
pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 {
let value = bcd.1 | bcd.0 << 4;
let tmp = ((value & 0xF0) >> 0x4) * 10;
tmp + (value & 0x0F)
}
pub(crate) mod sealed {
use crate::pac::rtc::Rtc;
pub trait Instance {
const BACKUP_REGISTER_COUNT: usize;
fn regs() -> Rtc {
crate::pac::RTC
}
unsafe fn enable_peripheral_clk() {}
/// Read content of the backup register.
///
/// The registers retain their values during wakes from standby mode or system resets. They also
/// retain their value when Vdd is switched off as long as V_BAT is powered.
fn read_backup_register(rtc: &Rtc, register: usize) -> Option<u32>;
/// Set content of the backup register.
///
/// The registers retain their values during wakes from standby mode or system resets. They also
/// retain their value when Vdd is switched off as long as V_BAT is powered.
fn write_backup_register(rtc: &Rtc, register: usize, value: u32);
// fn apply_config(&mut self, rtc_config: RtcConfig);
}
}
pub trait Instance: sealed::Instance + 'static {}

229
embassy-stm32/src/rtc/v2.rs Normal file
View File

@ -0,0 +1,229 @@
use stm32_metapac::rtc::vals::{Init, Osel, Pol};
use super::{sealed, Instance, RtcConfig};
use crate::pac::rtc::Rtc;
impl<'d, T: Instance> super::Rtc<'d, T> {
/// Applies the RTC config
/// It this changes the RTC clock source the time will be reset
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
// Unlock the backup domain
unsafe {
let clock_config = rtc_config.clock_config as u8;
#[cfg(not(rtc_v2wb))]
use stm32_metapac::rcc::vals::Rtcsel;
#[cfg(any(rtc_v2f2, rtc_v2f3, rtc_v2l1))]
let cr = crate::pac::PWR.cr();
#[cfg(any(rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))]
let cr = crate::pac::PWR.cr1();
// TODO: Missing from PAC for l0 and f0?
#[cfg(not(any(rtc_v2f0, rtc_v2l0)))]
{
cr.modify(|w| w.set_dbp(true));
while !cr.read().dbp() {}
}
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
let reg = crate::pac::RCC.bdcr().read();
#[cfg(any(rtc_v2l0, rtc_v2l1))]
let reg = crate::pac::RCC.csr().read();
#[cfg(any(rtc_v2h7, rtc_v2l4, rtc_v2wb))]
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
#[cfg(rtc_v2wb)]
let rtcsel = reg.rtcsel();
#[cfg(not(rtc_v2wb))]
let rtcsel = reg.rtcsel().0;
if !reg.rtcen() || rtcsel != clock_config {
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true));
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
let cr = crate::pac::RCC.bdcr();
#[cfg(any(rtc_v2l0, rtc_v2l1))]
let cr = crate::pac::RCC.csr();
cr.modify(|w| {
// Reset
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
w.set_bdrst(false);
// Select RTC source
#[cfg(not(rtc_v2wb))]
w.set_rtcsel(Rtcsel(clock_config));
#[cfg(rtc_v2wb)]
w.set_rtcsel(clock_config);
w.set_rtcen(true);
// Restore bcdr
#[cfg(any(rtc_v2l4, rtc_v2wb))]
w.set_lscosel(reg.lscosel());
#[cfg(any(rtc_v2l4, rtc_v2wb))]
w.set_lscoen(reg.lscoen());
w.set_lseon(reg.lseon());
#[cfg(any(rtc_v2f0, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))]
w.set_lsedrv(reg.lsedrv());
w.set_lsebyp(reg.lsebyp());
});
}
}
self.write(true, |rtc| unsafe {
rtc.cr().modify(|w| {
#[cfg(rtc_v2f2)]
w.set_fmt(false);
#[cfg(not(rtc_v2f2))]
w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR);
w.set_osel(Osel::DISABLED);
w.set_pol(Pol::HIGH);
});
rtc.prer().modify(|w| {
w.set_prediv_s(rtc_config.sync_prescaler);
w.set_prediv_a(rtc_config.async_prescaler);
});
});
self.rtc_config = rtc_config;
}
/// Calibrate the clock drift.
///
/// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range.
///
/// ### Note
///
/// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler`
/// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12).
#[cfg(not(rtc_v2f2))]
pub fn calibrate(&mut self, mut clock_drift: f32, period: super::RtcCalibrationCyclePeriod) {
const RTC_CALR_MIN_PPM: f32 = -487.1;
const RTC_CALR_MAX_PPM: f32 = 488.5;
const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537;
if clock_drift < RTC_CALR_MIN_PPM {
clock_drift = RTC_CALR_MIN_PPM;
} else if clock_drift > RTC_CALR_MAX_PPM {
clock_drift = RTC_CALR_MAX_PPM;
}
clock_drift = clock_drift / RTC_CALR_RESOLUTION_PPM;
self.write(false, |rtc| {
unsafe {
rtc.calr().write(|w| {
match period {
super::RtcCalibrationCyclePeriod::Seconds8 => {
w.set_calw8(stm32_metapac::rtc::vals::Calw8::EIGHT_SECOND);
}
super::RtcCalibrationCyclePeriod::Seconds16 => {
w.set_calw16(stm32_metapac::rtc::vals::Calw16::SIXTEEN_SECOND);
}
super::RtcCalibrationCyclePeriod::Seconds32 => {
// Set neither `calw8` nor `calw16` to use 32 seconds
}
}
// Extra pulses during calibration cycle period: CALP * 512 - CALM
//
// CALP sets whether pulses are added or omitted.
//
// CALM contains how many pulses (out of 512) are masked in a
// given calibration cycle period.
if clock_drift > 0.0 {
// Maximum (about 512.2) rounds to 512.
clock_drift += 0.5;
// When the offset is positive (0 to 512), the opposite of
// the offset (512 - offset) is masked, i.e. for the
// maximum offset (512), 0 pulses are masked.
w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ);
w.set_calm(512 - clock_drift as u16);
} else {
// Minimum (about -510.7) rounds to -511.
clock_drift -= 0.5;
// When the offset is negative or zero (-511 to 0),
// the absolute offset is masked, i.e. for the minimum
// offset (-511), 511 pulses are masked.
w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE);
w.set_calm((clock_drift * -1.0) as u16);
}
});
}
})
}
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
where
F: FnOnce(&crate::pac::rtc::Rtc) -> R,
{
let r = T::regs();
// Disable write protection.
// This is safe, as we're only writin the correct and expected values.
unsafe {
r.wpr().write(|w| w.set_key(0xca));
r.wpr().write(|w| w.set_key(0x53));
// true if initf bit indicates RTC peripheral is in init mode
if init_mode && !r.isr().read().initf() {
// to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode
r.isr().modify(|w| w.set_init(Init::INITMODE));
// wait till init state entered
// ~2 RTCCLK cycles
while !r.isr().read().initf() {}
}
}
let result = f(&r);
unsafe {
if init_mode {
r.isr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode
}
// Re-enable write protection.
// This is safe, as the field accepts the full range of 8-bit values.
r.wpr().write(|w| w.set_key(0xff));
}
result
}
}
impl sealed::Instance for crate::peripherals::RTC {
const BACKUP_REGISTER_COUNT: usize = 20;
unsafe fn enable_peripheral_clk() {
#[cfg(any(rtc_v2l4, rtc_v2wb))]
{
// enable peripheral clock for communication
crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true));
// read to allow the pwr clock to enable
crate::pac::PWR.cr1().read();
}
}
fn read_backup_register(rtc: &Rtc, register: usize) -> Option<u32> {
if register < Self::BACKUP_REGISTER_COUNT {
Some(unsafe { rtc.bkpr(register).read().bkp() })
} else {
None
}
}
fn write_backup_register(rtc: &Rtc, register: usize, value: u32) {
if register < Self::BACKUP_REGISTER_COUNT {
unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) }
}
}
}
impl Instance for crate::peripherals::RTC {}

205
embassy-stm32/src/rtc/v3.rs Normal file
View File

@ -0,0 +1,205 @@
use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType};
use super::{sealed, Instance, RtcCalibrationCyclePeriod, RtcConfig};
use crate::pac::rtc::Rtc;
impl<'d, T: Instance> super::Rtc<'d, T> {
/// Applies the RTC config
/// It this changes the RTC clock source the time will be reset
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
// Unlock the backup domain
unsafe {
#[cfg(any(rtc_v3u5, rcc_g0))]
use crate::pac::rcc::vals::Rtcsel;
#[cfg(not(any(rtc_v3u5, rcc_g0, rcc_g4, rcc_wl5, rcc_wle)))]
use crate::pac::rtc::vals::Rtcsel;
#[cfg(not(any(rtc_v3u5, rcc_wl5, rcc_wle)))]
{
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
}
#[cfg(any(rcc_wl5, rcc_wle))]
{
use crate::pac::pwr::vals::Dbp;
crate::pac::PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED));
while crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED {}
}
let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
let config_rtcsel = rtc_config.clock_config as u8;
#[cfg(not(any(rcc_wl5, rcc_wle, rcc_g4)))]
let config_rtcsel = Rtcsel(config_rtcsel);
if !reg.rtcen() || reg.rtcsel() != config_rtcsel {
crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true));
crate::pac::RCC.bdcr().modify(|w| {
// Reset
w.set_bdrst(false);
// Select RTC source
w.set_rtcsel(config_rtcsel);
w.set_rtcen(true);
// Restore bcdr
w.set_lscosel(reg.lscosel());
w.set_lscoen(reg.lscoen());
w.set_lseon(reg.lseon());
w.set_lsedrv(reg.lsedrv());
w.set_lsebyp(reg.lsebyp());
});
}
}
self.write(true, |rtc| {
unsafe {
rtc.cr().modify(|w| {
w.set_fmt(Fmt::TWENTYFOURHOUR);
w.set_osel(Osel::DISABLED);
w.set_pol(Pol::HIGH);
});
rtc.prer().modify(|w| {
w.set_prediv_s(rtc_config.sync_prescaler);
w.set_prediv_a(rtc_config.async_prescaler);
});
// TODO: configuration for output pins
rtc.cr().modify(|w| {
w.set_out2en(false);
w.set_tampalrm_type(TampalrmType::PUSHPULL);
w.set_tampalrm_pu(TampalrmPu::NOPULLUP);
});
}
});
self.rtc_config = rtc_config;
}
const RTC_CALR_MIN_PPM: f32 = -487.1;
const RTC_CALR_MAX_PPM: f32 = 488.5;
const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537;
/// Calibrate the clock drift.
///
/// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range.
///
/// ### Note
///
/// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler`
/// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12).
pub fn calibrate(&mut self, mut clock_drift: f32, period: RtcCalibrationCyclePeriod) {
if clock_drift < Self::RTC_CALR_MIN_PPM {
clock_drift = Self::RTC_CALR_MIN_PPM;
} else if clock_drift > Self::RTC_CALR_MAX_PPM {
clock_drift = Self::RTC_CALR_MAX_PPM;
}
clock_drift = clock_drift / Self::RTC_CALR_RESOLUTION_PPM;
self.write(false, |rtc| {
unsafe {
rtc.calr().write(|w| {
match period {
RtcCalibrationCyclePeriod::Seconds8 => {
w.set_calw8(Calw8::EIGHTSECONDS);
}
RtcCalibrationCyclePeriod::Seconds16 => {
w.set_calw16(Calw16::SIXTEENSECONDS);
}
RtcCalibrationCyclePeriod::Seconds32 => {
// Set neither `calw8` nor `calw16` to use 32 seconds
}
}
// Extra pulses during calibration cycle period: CALP * 512 - CALM
//
// CALP sets whether pulses are added or omitted.
//
// CALM contains how many pulses (out of 512) are masked in a
// given calibration cycle period.
if clock_drift > 0.0 {
// Maximum (about 512.2) rounds to 512.
clock_drift += 0.5;
// When the offset is positive (0 to 512), the opposite of
// the offset (512 - offset) is masked, i.e. for the
// maximum offset (512), 0 pulses are masked.
w.set_calp(Calp::INCREASEFREQ);
w.set_calm(512 - clock_drift as u16);
} else {
// Minimum (about -510.7) rounds to -511.
clock_drift -= 0.5;
// When the offset is negative or zero (-511 to 0),
// the absolute offset is masked, i.e. for the minimum
// offset (-511), 511 pulses are masked.
w.set_calp(Calp::NOCHANGE);
w.set_calm((clock_drift * -1.0) as u16);
}
});
}
})
}
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
where
F: FnOnce(&crate::pac::rtc::Rtc) -> R,
{
let r = T::regs();
// Disable write protection.
// This is safe, as we're only writin the correct and expected values.
unsafe {
r.wpr().write(|w| w.set_key(Key::DEACTIVATE1));
r.wpr().write(|w| w.set_key(Key::DEACTIVATE2));
if init_mode && !r.icsr().read().initf() {
r.icsr().modify(|w| w.set_init(Init::INITMODE));
// wait till init state entered
// ~2 RTCCLK cycles
while !r.icsr().read().initf() {}
}
}
let result = f(&r);
unsafe {
if init_mode {
r.icsr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode
}
// Re-enable write protection.
// This is safe, as the field accepts the full range of 8-bit values.
r.wpr().write(|w| w.set_key(Key::ACTIVATE));
}
result
}
}
impl sealed::Instance for crate::peripherals::RTC {
const BACKUP_REGISTER_COUNT: usize = 32;
fn read_backup_register(_rtc: &Rtc, register: usize) -> Option<u32> {
if register < Self::BACKUP_REGISTER_COUNT {
//Some(rtc.bkpr()[register].read().bits())
None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
} else {
None
}
}
fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) {
if register < Self::BACKUP_REGISTER_COUNT {
// RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
//unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) }
}
}
}
impl Instance for crate::peripherals::RTC {}

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,7 @@ use embassy_futures::join::join;
use embassy_hal_common::{into_ref, PeripheralRef};
pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
use self::sealed::WordSize;
use crate::dma::{slice_ptr_parts, Transfer};
use crate::dma::{slice_ptr_parts, word, Transfer};
use crate::gpio::sealed::{AFType, Pin as _};
use crate::gpio::{AnyPin, Pull};
use crate::pac::spi::{regs, vals, Spi as Regs};
@ -78,7 +77,7 @@ pub struct Spi<'d, T: Instance, Tx, Rx> {
miso: Option<PeripheralRef<'d, AnyPin>>,
txdma: PeripheralRef<'d, Tx>,
rxdma: PeripheralRef<'d, Rx>,
current_word_size: WordSize,
current_word_size: word_impl::Config,
}
impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
@ -178,10 +177,27 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
)
}
pub fn new_txonly_nosck(
peri: impl Peripheral<P = T> + 'd,
mosi: impl Peripheral<P = impl MosiPin<T>> + 'd,
txdma: impl Peripheral<P = Tx> + 'd,
rxdma: impl Peripheral<P = Rx> + 'd, // TODO: remove
freq: Hertz,
config: Config,
) -> Self {
into_ref!(mosi);
unsafe {
mosi.set_as_af_pull(mosi.af_num(), AFType::OutputPushPull, Pull::Down);
mosi.set_speed(crate::gpio::Speed::Medium);
}
Self::new_inner(peri, None, Some(mosi.map_into()), None, txdma, rxdma, freq, config)
}
/// Useful for on chip peripherals like SUBGHZ which are hardwired.
/// The bus can optionally be exposed externally with `Spi::new()` still.
#[allow(dead_code)]
pub(crate) fn new_internal(
pub fn new_subghz(
peri: impl Peripheral<P = T> + 'd,
txdma: impl Peripheral<P = Tx> + 'd,
rxdma: impl Peripheral<P = Rx> + 'd,
@ -234,14 +250,15 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
if mosi.is_none() {
w.set_rxonly(vals::Rxonly::OUTPUTDISABLED);
}
w.set_dff(WordSize::EightBit.dff())
w.set_dff(<u8 as sealed::Word>::CONFIG)
});
}
#[cfg(spi_v2)]
unsafe {
T::REGS.cr2().modify(|w| {
w.set_frxth(WordSize::EightBit.frxth());
w.set_ds(WordSize::EightBit.ds());
let (ds, frxth) = <u8 as sealed::Word>::CONFIG;
w.set_frxth(frxth);
w.set_ds(ds);
w.set_ssoe(false);
});
T::REGS.cr1().modify(|w| {
@ -279,7 +296,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
T::REGS.cfg1().modify(|w| {
w.set_crcen(false);
w.set_mbr(br);
w.set_dsize(WordSize::EightBit.dsize());
w.set_dsize(<u8 as sealed::Word>::CONFIG);
w.set_fthlv(vals::Fthlv::ONEFRAME);
});
T::REGS.cr2().modify(|w| {
w.set_tsize(0);
@ -297,7 +315,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
miso,
txdma,
rxdma,
current_word_size: WordSize::EightBit,
current_word_size: <u8 as sealed::Word>::CONFIG,
}
}
@ -355,7 +373,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
}
}
fn set_word_size(&mut self, word_size: WordSize) {
fn set_word_size(&mut self, word_size: word_impl::Config) {
if self.current_word_size == word_size {
return;
}
@ -364,7 +382,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
unsafe {
T::REGS.cr1().modify(|reg| {
reg.set_spe(false);
reg.set_dff(word_size.dff())
reg.set_dff(word_size)
});
T::REGS.cr1().modify(|reg| {
reg.set_spe(true);
@ -376,8 +394,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
w.set_spe(false);
});
T::REGS.cr2().modify(|w| {
w.set_frxth(word_size.frxth());
w.set_ds(word_size.ds());
w.set_frxth(word_size.1);
w.set_ds(word_size.0);
});
T::REGS.cr1().modify(|w| {
w.set_spe(true);
@ -393,7 +411,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
w.set_spe(false);
});
T::REGS.cfg1().modify(|w| {
w.set_dsize(word_size.dsize());
w.set_dsize(word_size);
});
T::REGS.cr1().modify(|w| {
w.set_csusp(false);
@ -412,7 +430,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
return Ok(());
}
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
unsafe {
T::REGS.cr1().modify(|w| {
w.set_spe(false);
@ -421,8 +439,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
let tx_request = self.txdma.request();
let tx_dst = T::REGS.tx_ptr();
unsafe { self.txdma.start_write(tx_request, data, tx_dst, Default::default()) }
let tx_f = Transfer::new(&mut self.txdma);
let tx_f = unsafe { Transfer::new_write(&mut self.txdma, tx_request, data, tx_dst, Default::default()) };
unsafe {
set_txdmaen(T::REGS, true);
@ -451,7 +468,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
return Ok(());
}
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
unsafe {
T::REGS.cr1().modify(|w| {
w.set_spe(false);
@ -468,13 +485,21 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
let rx_request = self.rxdma.request();
let rx_src = T::REGS.rx_ptr();
unsafe { self.rxdma.start_read(rx_request, rx_src, data, Default::default()) };
let rx_f = Transfer::new(&mut self.rxdma);
let rx_f = unsafe { Transfer::new_read(&mut self.rxdma, rx_request, rx_src, data, Default::default()) };
let tx_request = self.txdma.request();
let tx_dst = T::REGS.tx_ptr();
let clock_byte = 0x00u8;
let tx_f = crate::dma::write_repeated(&mut self.txdma, tx_request, &clock_byte, clock_byte_count, tx_dst);
let tx_f = unsafe {
Transfer::new_write_repeated(
&mut self.txdma,
tx_request,
&clock_byte,
clock_byte_count,
tx_dst,
Default::default(),
)
};
unsafe {
set_txdmaen(T::REGS, true);
@ -506,7 +531,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
return Ok(());
}
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
unsafe {
T::REGS.cr1().modify(|w| {
w.set_spe(false);
@ -521,13 +546,11 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
let rx_request = self.rxdma.request();
let rx_src = T::REGS.rx_ptr();
unsafe { self.rxdma.start_read(rx_request, rx_src, read, Default::default()) };
let rx_f = Transfer::new(&mut self.rxdma);
let rx_f = unsafe { Transfer::new_read_raw(&mut self.rxdma, rx_request, rx_src, read, Default::default()) };
let tx_request = self.txdma.request();
let tx_dst = T::REGS.tx_ptr();
unsafe { self.txdma.start_write(tx_request, write, tx_dst, Default::default()) }
let tx_f = Transfer::new(&mut self.txdma);
let tx_f = unsafe { Transfer::new_write_raw(&mut self.txdma, tx_request, write, tx_dst, Default::default()) };
unsafe {
set_txdmaen(T::REGS, true);
@ -566,7 +589,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
pub fn blocking_write<W: Word>(&mut self, words: &[W]) -> Result<(), Error> {
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
flush_rx_fifo(T::REGS);
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
for word in words.iter() {
let _ = transfer_word(T::REGS, *word)?;
}
@ -576,7 +599,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
pub fn blocking_read<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> {
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
flush_rx_fifo(T::REGS);
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
for word in words.iter_mut() {
*word = transfer_word(T::REGS, W::default())?;
}
@ -586,7 +609,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
pub fn blocking_transfer_in_place<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> {
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
flush_rx_fifo(T::REGS);
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
for word in words.iter_mut() {
*word = transfer_word(T::REGS, *word)?;
}
@ -596,7 +619,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
pub fn blocking_transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> {
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
flush_rx_fifo(T::REGS);
self.set_word_size(W::WORDSIZE);
self.set_word_size(W::CONFIG);
let len = read.len().max(write.len());
for i in 0..len {
let wb = write.get(i).copied().unwrap_or_default();
@ -928,70 +951,89 @@ pub(crate) mod sealed {
const REGS: Regs;
}
pub trait Word: Copy + 'static {
const WORDSIZE: WordSize;
}
impl Word for u8 {
const WORDSIZE: WordSize = WordSize::EightBit;
}
impl Word for u16 {
const WORDSIZE: WordSize = WordSize::SixteenBit;
}
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub enum WordSize {
EightBit,
SixteenBit,
}
impl WordSize {
#[cfg(any(spi_v1, spi_f1))]
pub fn dff(&self) -> vals::Dff {
match self {
WordSize::EightBit => vals::Dff::EIGHTBIT,
WordSize::SixteenBit => vals::Dff::SIXTEENBIT,
}
}
#[cfg(spi_v2)]
pub fn ds(&self) -> vals::Ds {
match self {
WordSize::EightBit => vals::Ds::EIGHTBIT,
WordSize::SixteenBit => vals::Ds::SIXTEENBIT,
}
}
#[cfg(spi_v2)]
pub fn frxth(&self) -> vals::Frxth {
match self {
WordSize::EightBit => vals::Frxth::QUARTER,
WordSize::SixteenBit => vals::Frxth::HALF,
}
}
#[cfg(any(spi_v3, spi_v4, spi_v5))]
pub fn dsize(&self) -> u8 {
match self {
WordSize::EightBit => 0b0111,
WordSize::SixteenBit => 0b1111,
}
}
#[cfg(any(spi_v3, spi_v4, spi_v5))]
pub fn _frxth(&self) -> vals::Fthlv {
match self {
WordSize::EightBit => vals::Fthlv::ONEFRAME,
WordSize::SixteenBit => vals::Fthlv::ONEFRAME,
}
}
pub trait Word {
const CONFIG: word_impl::Config;
}
}
pub trait Word: Copy + 'static + sealed::Word + Default + crate::dma::Word {}
pub trait Word: word::Word + sealed::Word {}
impl Word for u8 {}
impl Word for u16 {}
macro_rules! impl_word {
($T:ty, $config:expr) => {
impl sealed::Word for $T {
const CONFIG: Config = $config;
}
impl Word for $T {}
};
}
#[cfg(any(spi_v1, spi_f1))]
mod word_impl {
use super::*;
pub type Config = vals::Dff;
impl_word!(u8, vals::Dff::EIGHTBIT);
impl_word!(u16, vals::Dff::SIXTEENBIT);
}
#[cfg(any(spi_v2))]
mod word_impl {
use super::*;
pub type Config = (vals::Ds, vals::Frxth);
impl_word!(word::U4, (vals::Ds::FOURBIT, vals::Frxth::QUARTER));
impl_word!(word::U5, (vals::Ds::FIVEBIT, vals::Frxth::QUARTER));
impl_word!(word::U6, (vals::Ds::SIXBIT, vals::Frxth::QUARTER));
impl_word!(word::U7, (vals::Ds::SEVENBIT, vals::Frxth::QUARTER));
impl_word!(u8, (vals::Ds::EIGHTBIT, vals::Frxth::QUARTER));
impl_word!(word::U9, (vals::Ds::NINEBIT, vals::Frxth::HALF));
impl_word!(word::U10, (vals::Ds::TENBIT, vals::Frxth::HALF));
impl_word!(word::U11, (vals::Ds::ELEVENBIT, vals::Frxth::HALF));
impl_word!(word::U12, (vals::Ds::TWELVEBIT, vals::Frxth::HALF));
impl_word!(word::U13, (vals::Ds::THIRTEENBIT, vals::Frxth::HALF));
impl_word!(word::U14, (vals::Ds::FOURTEENBIT, vals::Frxth::HALF));
impl_word!(word::U15, (vals::Ds::FIFTEENBIT, vals::Frxth::HALF));
impl_word!(u16, (vals::Ds::SIXTEENBIT, vals::Frxth::HALF));
}
#[cfg(any(spi_v3, spi_v4, spi_v5))]
mod word_impl {
use super::*;
pub type Config = u8;
impl_word!(word::U4, 4 - 1);
impl_word!(word::U5, 5 - 1);
impl_word!(word::U6, 6 - 1);
impl_word!(word::U7, 7 - 1);
impl_word!(u8, 8 - 1);
impl_word!(word::U9, 9 - 1);
impl_word!(word::U10, 10 - 1);
impl_word!(word::U11, 11 - 1);
impl_word!(word::U12, 12 - 1);
impl_word!(word::U13, 13 - 1);
impl_word!(word::U14, 14 - 1);
impl_word!(word::U15, 15 - 1);
impl_word!(u16, 16 - 1);
impl_word!(word::U17, 17 - 1);
impl_word!(word::U18, 18 - 1);
impl_word!(word::U19, 19 - 1);
impl_word!(word::U20, 20 - 1);
impl_word!(word::U21, 21 - 1);
impl_word!(word::U22, 22 - 1);
impl_word!(word::U23, 23 - 1);
impl_word!(word::U24, 24 - 1);
impl_word!(word::U25, 25 - 1);
impl_word!(word::U26, 26 - 1);
impl_word!(word::U27, 27 - 1);
impl_word!(word::U28, 28 - 1);
impl_word!(word::U29, 29 - 1);
impl_word!(word::U30, 30 - 1);
impl_word!(word::U31, 31 - 1);
impl_word!(u32, 32 - 1);
}
pub trait Instance: Peripheral<P = Self> + sealed::Instance + RccPeripheral {}
pin_trait!(SckPin, Instance);

View File

@ -1,160 +0,0 @@
/// Bit synchronization.
///
/// This must be cleared to `0x00` (the reset value) when using packet types
/// other than LoRa.
///
/// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync).
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BitSync {
val: u8,
}
impl BitSync {
/// Bit synchronization register reset value.
pub const RESET: BitSync = BitSync { val: 0x00 };
/// Create a new [`BitSync`] structure from a raw value.
///
/// Reserved bits will be masked.
pub const fn from_raw(raw: u8) -> Self {
Self { val: raw & 0x70 }
}
/// Get the raw value of the [`BitSync`] register.
pub const fn as_bits(&self) -> u8 {
self.val
}
/// LoRa simple bit synchronization enable.
///
/// # Example
///
/// Enable simple bit synchronization.
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true);
/// # assert_eq!(u8::from(BIT_SYNC), 0x40u8);
/// ```
#[must_use = "set_simple_bit_sync_en returns a modified BitSync"]
pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync {
if en {
self.val |= 1 << 6;
} else {
self.val &= !(1 << 6);
}
self
}
/// Returns `true` if simple bit synchronization is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// let bs: BitSync = BitSync::RESET;
/// assert_eq!(bs.simple_bit_sync_en(), false);
/// let bs: BitSync = bs.set_simple_bit_sync_en(true);
/// assert_eq!(bs.simple_bit_sync_en(), true);
/// let bs: BitSync = bs.set_simple_bit_sync_en(false);
/// assert_eq!(bs.simple_bit_sync_en(), false);
/// ```
pub const fn simple_bit_sync_en(&self) -> bool {
self.val & (1 << 6) != 0
}
/// LoRa RX data inversion.
///
/// # Example
///
/// Invert receive data.
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true);
/// # assert_eq!(u8::from(BIT_SYNC), 0x20u8);
/// ```
#[must_use = "set_rx_data_inv returns a modified BitSync"]
pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync {
if inv {
self.val |= 1 << 5;
} else {
self.val &= !(1 << 5);
}
self
}
/// Returns `true` if LoRa RX data is inverted.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// let bs: BitSync = BitSync::RESET;
/// assert_eq!(bs.rx_data_inv(), false);
/// let bs: BitSync = bs.set_rx_data_inv(true);
/// assert_eq!(bs.rx_data_inv(), true);
/// let bs: BitSync = bs.set_rx_data_inv(false);
/// assert_eq!(bs.rx_data_inv(), false);
/// ```
pub const fn rx_data_inv(&self) -> bool {
self.val & (1 << 5) != 0
}
/// LoRa normal bit synchronization enable.
///
/// # Example
///
/// Enable normal bit synchronization.
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true);
/// # assert_eq!(u8::from(BIT_SYNC), 0x10u8);
/// ```
#[must_use = "set_norm_bit_sync_en returns a modified BitSync"]
pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync {
if en {
self.val |= 1 << 4;
} else {
self.val &= !(1 << 4);
}
self
}
/// Returns `true` if normal bit synchronization is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// let bs: BitSync = BitSync::RESET;
/// assert_eq!(bs.norm_bit_sync_en(), false);
/// let bs: BitSync = bs.set_norm_bit_sync_en(true);
/// assert_eq!(bs.norm_bit_sync_en(), true);
/// let bs: BitSync = bs.set_norm_bit_sync_en(false);
/// assert_eq!(bs.norm_bit_sync_en(), false);
/// ```
pub const fn norm_bit_sync_en(&self) -> bool {
self.val & (1 << 4) != 0
}
}
impl From<BitSync> for u8 {
fn from(bs: BitSync) -> Self {
bs.val
}
}
impl Default for BitSync {
fn default() -> Self {
Self::RESET
}
}

View File

@ -1,230 +0,0 @@
use super::Timeout;
/// Number of symbols used for channel activity detection scans.
///
/// Argument of [`CadParams::set_num_symbol`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum NbCadSymbol {
/// 1 symbol.
S1 = 0x0,
/// 2 symbols.
S2 = 0x1,
/// 4 symbols.
S4 = 0x2,
/// 8 symbols.
S8 = 0x3,
/// 16 symbols.
S16 = 0x4,
}
/// Mode to enter after a channel activity detection scan is finished.
///
/// Argument of [`CadParams::set_exit_mode`].
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ExitMode {
/// Standby with RC 13 MHz mode entry after CAD.
Standby = 0,
/// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected
/// during the CAD scan.
/// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode
/// until a packet is received or until the CAD timeout is reached.
StandbyLoRa = 1,
}
/// Channel activity detection (CAD) parameters.
///
/// Argument of [`set_cad_params`].
///
/// # Recommended CAD settings
///
/// This is taken directly from the datasheet.
///
/// "The correct values selected in the table below must be carefully tested to
/// ensure a good detection at sensitivity level and to limit the number of
/// false detections"
///
/// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] |
/// |-----------------------|------------------|-----------------|
/// | 5 | 0x18 | 0x10 |
/// | 6 | 0x19 | 0x10 |
/// | 7 | 0x20 | 0x10 |
/// | 8 | 0x21 | 0x10 |
/// | 9 | 0x22 | 0x10 |
/// | 10 | 0x23 | 0x10 |
/// | 11 | 0x24 | 0x10 |
/// | 12 | 0x25 | 0x10 |
///
/// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CadParams {
buf: [u8; 8],
}
impl CadParams {
/// Create a new `CadParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CadParams;
///
/// const CAD_PARAMS: CadParams = CadParams::new();
/// assert_eq!(CAD_PARAMS, CadParams::default());
/// ```
pub const fn new() -> CadParams {
CadParams {
buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0],
}
.set_num_symbol(NbCadSymbol::S1)
.set_det_peak(0x18)
.set_det_min(0x10)
.set_exit_mode(ExitMode::Standby)
}
/// Number of symbols used for a CAD scan.
///
/// # Example
///
/// Set the number of symbols to 4.
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, NbCadSymbol};
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4);
/// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2);
/// ```
#[must_use = "set_num_symbol returns a modified CadParams"]
pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams {
self.buf[1] = nb as u8;
self
}
/// Used with [`set_det_min`] to correlate the LoRa symbol.
///
/// See the table in [`CadParams`] docs for recommended values.
///
/// # Example
///
/// Setting the recommended value for a spreading factor of 7.
///
/// ```
/// use stm32wlxx_hal::subghz::CadParams;
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10);
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20);
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
/// ```
///
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
#[must_use = "set_det_peak returns a modified CadParams"]
pub const fn set_det_peak(mut self, peak: u8) -> CadParams {
self.buf[2] = peak;
self
}
/// Used with [`set_det_peak`] to correlate the LoRa symbol.
///
/// See the table in [`CadParams`] docs for recommended values.
///
/// # Example
///
/// Setting the recommended value for a spreading factor of 6.
///
/// ```
/// use stm32wlxx_hal::subghz::CadParams;
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10);
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18);
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
/// ```
///
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
#[must_use = "set_det_min returns a modified CadParams"]
pub const fn set_det_min(mut self, min: u8) -> CadParams {
self.buf[3] = min;
self
}
/// Mode to enter after a channel activity detection scan is finished.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode};
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby);
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00);
/// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01);
/// ```
#[must_use = "set_exit_mode returns a modified CadParams"]
pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams {
self.buf[4] = mode as u8;
self
}
/// Set the timeout.
///
/// This is only used with [`ExitMode::StandbyLoRa`].
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode, Timeout};
///
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
/// const CAD_PARAMS: CadParams = CadParams::new()
/// .set_exit_mode(ExitMode::StandbyLoRa)
/// .set_timeout(TIMEOUT);
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01);
/// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12);
/// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34);
/// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56);
/// ```
#[must_use = "set_timeout returns a modified CadParams"]
pub const fn set_timeout(mut self, to: Timeout) -> CadParams {
let to_bytes: [u8; 3] = to.as_bytes();
self.buf[5] = to_bytes[0];
self.buf[6] = to_bytes[1];
self.buf[7] = to_bytes[2];
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout};
///
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
/// const CAD_PARAMS: CadParams = CadParams::new()
/// .set_num_symbol(NbCadSymbol::S4)
/// .set_det_peak(0x18)
/// .set_det_min(0x10)
/// .set_exit_mode(ExitMode::StandbyLoRa)
/// .set_timeout(TIMEOUT);
///
/// assert_eq!(
/// CAD_PARAMS.as_slice(),
/// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for CadParams {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,122 +0,0 @@
/// Image calibration.
///
/// Argument of [`calibrate_image`].
///
/// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CalibrateImage(pub(crate) u8, pub(crate) u8);
impl CalibrateImage {
/// Image calibration for the 430 - 440 MHz ISM band.
pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F);
/// Image calibration for the 470 - 510 MHz ISM band.
pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81);
/// Image calibration for the 779 - 787 MHz ISM band.
pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5);
/// Image calibration for the 863 - 870 MHz ISM band.
pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB);
/// Image calibration for the 902 - 928 MHz ISM band.
pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9);
/// Create a new `CalibrateImage` structure from raw values.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CalibrateImage;
///
/// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9);
/// assert_eq!(CAL, CalibrateImage::ISM_902_928);
/// ```
pub const fn new(f1: u8, f2: u8) -> CalibrateImage {
CalibrateImage(f1, f2)
}
/// Create a new `CalibrateImage` structure from two frequencies.
///
/// # Arguments
///
/// The units for `freq1` and `freq2` are in MHz.
///
/// # Panics
///
/// * Panics if `freq1` is less than `freq2`.
/// * Panics if `freq1` or `freq2` is not a multiple of 4MHz.
/// * Panics if `freq1` or `freq2` is greater than `1020`.
///
/// # Example
///
/// Create an image calibration for the 430 - 440 MHz ISM band.
///
/// ```
/// use stm32wlxx_hal::subghz::CalibrateImage;
///
/// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444);
/// assert_eq!(cal, CalibrateImage::ISM_430_440);
/// ```
pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage {
assert!(freq2 >= freq1);
assert_eq!(freq1 % 4, 0);
assert_eq!(freq2 % 4, 0);
assert!(freq1 <= 1020);
assert!(freq2 <= 1020);
CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8)
}
}
impl Default for CalibrateImage {
fn default() -> Self {
CalibrateImage::new(0xE1, 0xE9)
}
}
/// Block calibration.
///
/// Argument of [`calibrate`].
///
/// [`calibrate`]: crate::subghz::SubGhz::calibrate
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Calibrate {
/// Image calibration
Image = 1 << 6,
/// RF-ADC bulk P calibration
AdcBulkP = 1 << 5,
/// RF-ADC bulk N calibration
AdcBulkN = 1 << 4,
/// RF-ADC pulse calibration
AdcPulse = 1 << 3,
/// RF-PLL calibration
Pll = 1 << 2,
/// Sub-GHz radio RC 13 MHz calibration
Rc13M = 1 << 1,
/// Sub-GHz radio RC 64 kHz calibration
Rc64K = 1,
}
impl Calibrate {
/// Get the bitmask for the block calibration.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Calibrate;
///
/// assert_eq!(Calibrate::Image.mask(), 0b0100_0000);
/// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000);
/// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000);
/// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000);
/// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100);
/// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010);
/// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001);
/// ```
pub const fn mask(self) -> u8 {
self as u8
}
}

View File

@ -1,37 +0,0 @@
/// Fallback mode after successful packet transmission or packet reception.
///
/// Argument of [`set_tx_rx_fallback_mode`].
///
/// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FallbackMode {
/// Standby mode entry.
Standby = 0x20,
/// Standby with HSE32 enabled.
StandbyHse = 0x30,
/// Frequency synthesizer entry.
Fs = 0x40,
}
impl From<FallbackMode> for u8 {
fn from(fm: FallbackMode) -> Self {
fm as u8
}
}
impl Default for FallbackMode {
/// Default fallback mode after power-on reset.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::FallbackMode;
///
/// assert_eq!(FallbackMode::default(), FallbackMode::Standby);
/// ```
fn default() -> Self {
FallbackMode::Standby
}
}

View File

@ -1,107 +0,0 @@
use super::ValueError;
/// HSE32 load capacitor trimming.
///
/// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`].
///
/// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim
/// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HseTrim {
val: u8,
}
impl HseTrim {
/// Maximum capacitor value, ~33.4 pF
pub const MAX: HseTrim = HseTrim::from_raw(0x2F);
/// Minimum capacitor value, ~11.3 pF
pub const MIN: HseTrim = HseTrim::from_raw(0x00);
/// Power-on-reset capacitor value, ~20.3 pF
///
/// This is the same as `default`.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert_eq!(HseTrim::POR, HseTrim::default());
/// ```
pub const POR: HseTrim = HseTrim::from_raw(0x12);
/// Create a new [`HseTrim`] structure from a raw value.
///
/// Values greater than the maximum of `0x2F` will be set to the maximum.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX);
/// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX);
/// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN);
/// ```
pub const fn from_raw(raw: u8) -> HseTrim {
if raw > 0x2F {
HseTrim { val: 0x2F }
} else {
HseTrim { val: raw }
}
}
/// Create a HSE trim value from farads.
///
/// Values greater than the maximum of 33.4 pF will be set to the maximum.
/// Values less than the minimum of 11.3 pF will be set to the minimum.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert!(HseTrim::from_farads(1.0).is_err());
/// assert!(HseTrim::from_farads(1e-12).is_err());
/// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default()));
/// ```
pub fn from_farads(farads: f32) -> Result<HseTrim, ValueError<f32>> {
const MAX: f32 = 33.4E-12;
const MIN: f32 = 11.3E-12;
if farads > MAX {
Err(ValueError::too_high(farads, MAX))
} else if farads < MIN {
Err(ValueError::too_low(farads, MIN))
} else {
Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8))
}
}
/// Get the capacitance as farads.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33);
/// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11);
/// ```
pub fn as_farads(&self) -> f32 {
(self.val as f32) * 0.47E-12 + 11.3E-12
}
}
impl From<HseTrim> for u8 {
fn from(ht: HseTrim) -> Self {
ht.val
}
}
impl Default for HseTrim {
fn default() -> Self {
Self::POR
}
}

View File

@ -1,292 +0,0 @@
/// Interrupt lines.
///
/// Argument of [`CfgIrq::irq_enable`] and [`CfgIrq::irq_disable`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum IrqLine {
/// Global interrupt.
Global,
/// Interrupt line 1.
///
/// This will output to the [`RfIrq0`](crate::gpio::RfIrq0) pin.
Line1,
/// Interrupt line 2.
///
/// This will output to the [`RfIrq1`](crate::gpio::RfIrq1) pin.
Line2,
/// Interrupt line 3.
///
/// This will output to the [`RfIrq2`](crate::gpio::RfIrq2) pin.
Line3,
}
impl IrqLine {
pub(super) const fn offset(&self) -> usize {
match self {
IrqLine::Global => 1,
IrqLine::Line1 => 3,
IrqLine::Line2 => 5,
IrqLine::Line3 => 7,
}
}
}
/// IRQ bit mapping
///
/// See table 37 "IRQ bit mapping and definition" in the reference manual for
/// more information.
#[repr(u16)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Irq {
/// Packet transmission finished.
///
/// * Packet type: LoRa and GFSK
/// * Operation: TX
TxDone = (1 << 0),
/// Packet reception finished.
///
/// * Packet type: LoRa and GFSK
/// * Operation: RX
RxDone = (1 << 1),
/// Preamble detected.
///
/// * Packet type: LoRa and GFSK
/// * Operation: RX
PreambleDetected = (1 << 2),
/// Synchronization word valid.
///
/// * Packet type: GFSK
/// * Operation: RX
SyncDetected = (1 << 3),
/// Header valid.
///
/// * Packet type: LoRa
/// * Operation: RX
HeaderValid = (1 << 4),
/// Header CRC error.
///
/// * Packet type: LoRa
/// * Operation: RX
HeaderErr = (1 << 5),
/// Dual meaning error.
///
/// For GFSK RX this indicates a preamble, syncword, address, CRC, or length
/// error.
///
/// For LoRa RX this indicates a CRC error.
Err = (1 << 6),
/// Channel activity detection finished.
///
/// * Packet type: LoRa
/// * Operation: CAD
CadDone = (1 << 7),
/// Channel activity detected.
///
/// * Packet type: LoRa
/// * Operation: CAD
CadDetected = (1 << 8),
/// RX or TX timeout.
///
/// * Packet type: LoRa and GFSK
/// * Operation: RX and TX
Timeout = (1 << 9),
}
impl Irq {
/// Get the bitmask for an IRQ.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Irq;
///
/// assert_eq!(Irq::TxDone.mask(), 0x0001);
/// assert_eq!(Irq::Timeout.mask(), 0x0200);
/// ```
pub const fn mask(self) -> u16 {
self as u16
}
}
/// Argument for [`set_irq_cfg`].
///
/// [`set_irq_cfg`]: crate::subghz::SubGhz::set_irq_cfg
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CfgIrq {
buf: [u8; 9],
}
impl CfgIrq {
/// Create a new `CfgIrq`.
///
/// This is the same as `default`, but in a `const` function.
///
/// The default value has all interrupts disabled on all lines.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CfgIrq;
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new();
/// ```
pub const fn new() -> CfgIrq {
CfgIrq {
buf: [
super::OpCode::CfgDioIrq as u8,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
],
}
}
/// Enable an interrupt.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable(IrqLine::Global, Irq::TxDone)
/// .irq_enable(IrqLine::Global, Irq::Timeout);
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
/// ```
#[must_use = "irq_enable returns a modified CfgIrq"]
pub const fn irq_enable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
let mask: u16 = irq as u16;
let offset: usize = line.offset();
self.buf[offset] |= ((mask >> 8) & 0xFF) as u8;
self.buf[offset + 1] |= (mask & 0xFF) as u8;
self
}
/// Enable an interrupt on all lines.
///
/// As far as I can tell with empirical testing all IRQ lines need to be
/// enabled for the internal interrupt to be pending in the NVIC.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable_all(Irq::TxDone)
/// .irq_enable_all(Irq::Timeout);
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[4], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[5], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[6], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[7], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[8], 0x01);
/// ```
#[must_use = "irq_enable_all returns a modified CfgIrq"]
pub const fn irq_enable_all(mut self, irq: Irq) -> CfgIrq {
let mask: [u8; 2] = irq.mask().to_be_bytes();
self.buf[1] |= mask[0];
self.buf[2] |= mask[1];
self.buf[3] |= mask[0];
self.buf[4] |= mask[1];
self.buf[5] |= mask[0];
self.buf[6] |= mask[1];
self.buf[7] |= mask[0];
self.buf[8] |= mask[1];
self
}
/// Disable an interrupt.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable(IrqLine::Global, Irq::TxDone)
/// .irq_enable(IrqLine::Global, Irq::Timeout)
/// .irq_disable(IrqLine::Global, Irq::TxDone)
/// .irq_disable(IrqLine::Global, Irq::Timeout);
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x00);
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x00);
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
/// ```
#[must_use = "irq_disable returns a modified CfgIrq"]
pub const fn irq_disable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
let mask: u16 = !(irq as u16);
let offset: usize = line.offset();
self.buf[offset] &= ((mask >> 8) & 0xFF) as u8;
self.buf[offset + 1] &= (mask & 0xFF) as u8;
self
}
/// Disable an interrupt on all lines.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable_all(Irq::TxDone)
/// .irq_enable_all(Irq::Timeout)
/// .irq_disable_all(Irq::TxDone)
/// .irq_disable_all(Irq::Timeout);
/// # assert_eq!(IRQ_CFG, CfgIrq::new());
/// ```
#[must_use = "irq_disable_all returns a modified CfgIrq"]
pub const fn irq_disable_all(mut self, irq: Irq) -> CfgIrq {
let mask: [u8; 2] = (!irq.mask()).to_be_bytes();
self.buf[1] &= mask[0];
self.buf[2] &= mask[1];
self.buf[3] &= mask[0];
self.buf[4] &= mask[1];
self.buf[5] &= mask[0];
self.buf[6] &= mask[1];
self.buf[7] &= mask[0];
self.buf[8] &= mask[1];
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable_all(Irq::TxDone)
/// .irq_enable_all(Irq::Timeout);
///
/// assert_eq!(
/// IRQ_CFG.as_slice(),
/// &[0x08, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for CfgIrq {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,20 +0,0 @@
/// LoRa synchronization word.
///
/// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LoRaSyncWord {
/// LoRa private network.
Private,
/// LoRa public network.
Public,
}
impl LoRaSyncWord {
pub(crate) const fn bytes(self) -> [u8; 2] {
match self {
LoRaSyncWord::Private => [0x14, 0x24],
LoRaSyncWord::Public => [0x34, 0x44],
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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