Compare commits

...

80 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
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
d960bf344a fixed missing imports 2023-04-27 16:22:41 +01: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
0d82ebea29 stm32/rtc: fix datetime and add f4 test 2023-04-25 17:35:01 -05: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
100 changed files with 3789 additions and 11271 deletions

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

View File

@ -7,22 +7,13 @@ license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/" 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/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
features = ["time", "defmt"] features = ["stm32wl", "time", "defmt"]
flavors = [ target = "thumbv7em-none-eabi"
{ 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] [features]
sx126x = []
sx127x = []
stm32wl = ["dep:embassy-stm32"] stm32wl = ["dep:embassy-stm32"]
time = [] time = []
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"] defmt = ["dep:defmt", "lorawan-device/defmt"]
external-lora-phy = ["dep:lora-phy"]
[dependencies] [dependencies]
@ -39,6 +30,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
embedded-hal = { version = "0.2", features = ["unproven"] } embedded-hal = { version = "0.2", features = ["unproven"] }
bit_field = { version = "0.10" } bit_field = { version = "0.10" }
lora-phy = { version = "1", optional = true } lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] } lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
lorawan = { version = "0.7.3", default-features = false }

View File

@ -1,23 +1,12 @@
#![no_std] #![no_std]
#![feature(async_fn_in_trait, impl_trait_projections)] #![feature(async_fn_in_trait, impl_trait_projections)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device //! embassy-lora holds LoRa-specific functionality.
//! crate's async LoRaWAN MAC implementation.
pub(crate) mod fmt; pub(crate) mod fmt;
#[cfg(feature = "external-lora-phy")]
/// interface variants required by the external lora crate
pub mod iv;
#[cfg(feature = "stm32wl")] /// interface variants required by the external lora physical layer crate (lora-phy)
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")] pub mod iv;
pub mod stm32wl;
#[cfg(feature = "sx126x")]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod sx126x;
#[cfg(feature = "sx127x")]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod sx127x;
#[cfg(feature = "time")] #[cfg(feature = "time")]
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Instant, Timer};

View File

@ -1,291 +0,0 @@
//! A radio driver integration for the radio found on STM32WL family devices.
#![allow(deprecated)]
use core::future::poll_fn;
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;
async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, Self::PhyError> {
self.do_tx(config, buf).await
}
async fn rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> {
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,137 +0,0 @@
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>;
async fn tx(&mut self, config: TxConfig, buffer: &[u8]) -> Result<u32, Self::PhyError> {
trace!("TX START");
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);
}
async fn rx(
&mut self,
config: RfConfig,
receiving_buffer: &mut [u8],
) -> Result<(usize, RxQuality), Self::PhyError> {
trace!("RX START");
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,192 +0,0 @@
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;
async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, Self::PhyError> {
trace!("TX START");
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);
}
}
}
async fn rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> {
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

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

View File

@ -617,6 +617,28 @@ mod eh02 {
self.blocking_write_read(address, bytes, buffer) 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")] #[cfg(feature = "unstable-traits")]

View File

@ -156,6 +156,8 @@ pub fn init(_config: config::Config) -> Peripherals {
#[cfg(feature = "time-driver")] #[cfg(feature = "time-driver")]
timer::init(); timer::init();
dma::init(); dma::init();
pio::init();
gpio::init();
} }
peripherals peripherals

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,14 @@ use core::future::{poll_fn, Future};
use core::slice; use core::slice;
use core::task::Poll; use core::task::Poll;
use atomic_polyfill::{AtomicU8, Ordering};
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
use embassy_hal_common::atomic_ring_buffer::RingBuffer; use embassy_hal_common::atomic_ring_buffer::RingBuffer;
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use embassy_time::{Duration, Timer};
use super::*; use super::*;
use crate::clocks::clk_peri_freq;
use crate::RegExt; use crate::RegExt;
pub struct State { pub struct State {
@ -14,8 +17,15 @@ pub struct State {
tx_buf: RingBuffer, tx_buf: RingBuffer,
rx_waker: AtomicWaker, rx_waker: AtomicWaker,
rx_buf: RingBuffer, 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 { impl State {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
@ -23,6 +33,7 @@ impl State {
tx_buf: RingBuffer::new(), tx_buf: RingBuffer::new(),
rx_waker: AtomicWaker::new(), rx_waker: AtomicWaker::new(),
tx_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], tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8],
) { ) {
let state = T::state(); let state = T::buffered_state();
let len = tx_buffer.len(); let len = tx_buffer.len();
unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
let len = rx_buffer.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. // to pend the ISR when we want data transmission to start.
let regs = T::regs(); let regs = T::regs();
unsafe { unsafe {
regs.uartimsc().write_set(|w| { regs.uartimsc().write(|w| {
w.set_rxim(true); w.set_rxim(true);
w.set_rtim(true); w.set_rtim(true);
w.set_txim(true); w.set_txim(true);
@ -136,6 +147,14 @@ impl<'d, T: Instance> BufferedUart<'d, T> {
self.rx.blocking_read(buffer) 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>) { pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) {
(self.rx, self.tx) (self.rx, self.tx)
} }
@ -173,26 +192,61 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> {
Self { phantom: PhantomData } 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| { poll_fn(move |cx| {
if let Poll::Ready(r) = Self::try_read(buf) {
return Poll::Ready(r);
}
T::buffered_state().rx_waker.register(cx.waker());
Poll::Pending
})
}
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() { if buf.is_empty() {
return Poll::Ready(Ok(0)); return Poll::Ready(Ok(0));
} }
let state = T::state(); let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() }; let mut rx_reader = unsafe { state.rx_buf.reader() };
let n = rx_reader.pop(|data| { let n = rx_reader.pop(|data| {
let n = data.len().min(buf.len()); let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]); buf[..n].copy_from_slice(&data[..n]);
n n
}); });
if n == 0 {
state.rx_waker.register(cx.waker()); let result = if n == 0 {
return Poll::Pending; 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 // (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(); let regs = T::regs();
unsafe { unsafe {
regs.uartimsc().write_set(|w| { regs.uartimsc().write_set(|w| {
@ -201,62 +255,50 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> {
}); });
} }
Poll::Ready(Ok(n)) Poll::Ready(result)
})
} }
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
if buf.is_empty() {
return Ok(0);
}
loop { loop {
let state = T::state(); match Self::try_read(buf) {
let mut rx_reader = unsafe { state.rx_buf.reader() }; Poll::Ready(res) => return res,
let n = rx_reader.pop(|data| { Poll::Pending => continue,
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
n
});
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);
});
}
return Ok(n);
} }
} }
} }
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| { poll_fn(move |cx| {
let state = T::state(); let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() }; let mut rx_reader = unsafe { state.rx_buf.reader() };
let (p, n) = rx_reader.pop_buf(); let (p, n) = rx_reader.pop_buf();
if n == 0 { let result = if n == 0 {
match Self::get_rx_error() {
None => {
state.rx_waker.register(cx.waker()); state.rx_waker.register(cx.waker());
return Poll::Pending; return Poll::Pending;
} }
Some(e) => Err(e),
}
} else {
let buf = unsafe { slice::from_raw_parts(p, n) }; let buf = unsafe { slice::from_raw_parts(p, n) };
Poll::Ready(Ok(buf)) Ok(buf)
};
Poll::Ready(result)
}) })
} }
fn consume(amt: usize) { fn consume(amt: usize) {
let state = T::state(); let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() }; let mut rx_reader = unsafe { state.rx_buf.reader() };
rx_reader.pop_done(amt); rx_reader.pop_done(amt);
// (Re-)Enable the interrupt to receive more data in case it was // (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(); let regs = T::regs();
unsafe { unsafe {
regs.uartimsc().write_set(|w| { regs.uartimsc().write_set(|w| {
@ -305,7 +347,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
return Poll::Ready(Ok(0)); return Poll::Ready(Ok(0));
} }
let state = T::state(); let state = T::buffered_state();
let mut tx_writer = unsafe { state.tx_buf.writer() }; let mut tx_writer = unsafe { state.tx_buf.writer() };
let n = tx_writer.push(|data| { let n = tx_writer.push(|data| {
let n = data.len().min(buf.len()); 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>> { fn flush() -> impl Future<Output = Result<(), Error>> {
poll_fn(move |cx| { poll_fn(move |cx| {
let state = T::state(); let state = T::buffered_state();
if !state.tx_buf.is_empty() { if !state.tx_buf.is_empty() {
state.tx_waker.register(cx.waker()); state.tx_waker.register(cx.waker());
return Poll::Pending; return Poll::Pending;
@ -344,7 +386,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
} }
loop { loop {
let state = T::state(); let state = T::buffered_state();
let mut tx_writer = unsafe { state.tx_buf.writer() }; let mut tx_writer = unsafe { state.tx_buf.writer() };
let n = tx_writer.push(|data| { let n = tx_writer.push(|data| {
let n = data.len().min(buf.len()); 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> { pub fn blocking_flush(&mut self) -> Result<(), Error> {
loop { loop {
let state = T::state(); let state = T::buffered_state();
if state.tx_buf.is_empty() { if state.tx_buf.is_empty() {
return Ok(()); 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> { impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
fn drop(&mut self) { fn drop(&mut self) {
let state = T::state(); let state = T::buffered_state();
unsafe { unsafe {
state.rx_buf.deinit(); 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> { impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
fn drop(&mut self) { fn drop(&mut self) {
let state = T::state(); let state = T::buffered_state();
unsafe { unsafe {
state.tx_buf.deinit(); 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 ()) { pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
let r = T::regs(); let r = T::regs();
let s = T::state(); let s = T::buffered_state();
unsafe { unsafe {
// Clear TX and error interrupt flags // 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 mut rx_writer = s.rx_buf.writer();
let rx_buf = rx_writer.push_slice(); let rx_buf = rx_writer.push_slice();
let mut n_read = 0; let mut n_read = 0;
let mut error = false;
for rx_byte in rx_buf { for rx_byte in rx_buf {
if r.uartfr().read().rxfe() { if r.uartfr().read().rxfe() {
break; 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; n_read += 1;
} }
if n_read > 0 { if n_read > 0 {
rx_writer.push_done(n_read); rx_writer.push_done(n_read);
s.rx_waker.wake(); s.rx_waker.wake();
} else if error {
s.rx_waker.wake();
} }
// Disable any further RX interrupts when the buffer becomes full. // Disable any further RX interrupts when the buffer becomes full or
if s.rx_buf.is_full() { // 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| { r.uartimsc().write_clear(|w| {
w.set_rxim(true); w.set_rxim(true);
w.set_rtim(true); w.set_rtim(true);

View File

@ -1,12 +1,21 @@
use core::future::poll_fn;
use core::marker::PhantomData; 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_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::dma::{AnyChannel, Channel};
use crate::gpio::sealed::Pin; use crate::gpio::sealed::Pin;
use crate::gpio::AnyPin; use crate::gpio::AnyPin;
use crate::pac::io::vals::{Inover, Outover}; use crate::pac::io::vals::{Inover, Outover};
use crate::{pac, peripherals, Peripheral}; use crate::{pac, peripherals, Peripheral, RegExt};
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
mod buffered; mod buffered;
@ -95,6 +104,11 @@ pub enum Error {
Framing, Framing,
} }
pub struct DmaState {
rx_err_waker: AtomicWaker,
rx_errs: AtomicU16,
}
pub struct Uart<'d, T: Instance, M: Mode> { pub struct Uart<'d, T: Instance, M: Mode> {
tx: UartTx<'d, T, M>, tx: UartTx<'d, T, M>,
rx: UartRx<'d, T, M>, rx: UartRx<'d, T, M>,
@ -146,6 +160,43 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> {
unsafe { while !r.uartfr().read().txfe() {} } unsafe { while !r.uartfr().read().txfe() {} }
Ok(()) 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> { impl<'d, T: Instance> UartTx<'d, T, Blocking> {
@ -167,7 +218,7 @@ impl<'d, T: Instance> UartTx<'d, T, Async> {
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
let ch = self.tx_dma.as_mut().unwrap(); let ch = self.tx_dma.as_mut().unwrap();
let transfer = unsafe { let transfer = unsafe {
T::regs().uartdmacr().modify(|reg| { T::regs().uartdmacr().write_set(|reg| {
reg.set_txdmae(true); reg.set_txdmae(true);
}); });
// If we don't assign future to a variable, the data register pointer // If we don't assign future to a variable, the data register pointer
@ -184,31 +235,48 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> {
pub fn new( pub fn new(
_uart: impl Peripheral<P = T> + 'd, _uart: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<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, rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config, config: Config,
) -> Self { ) -> Self {
into_ref!(rx, rx_dma); into_ref!(rx, irq, rx_dma);
Uart::<T, M>::init(None, Some(rx.map_into()), None, None, config); Uart::<T, M>::init(None, Some(rx.map_into()), None, None, config);
Self::new_inner(Some(rx_dma.map_into())) 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 { Self {
rx_dma, rx_dma,
phantom: PhantomData, phantom: PhantomData,
} }
} }
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> {
let r = T::regs(); while buffer.len() > 0 {
unsafe { let received = self.drain_fifo(buffer)?;
for b in buffer { buffer = &mut buffer[received..];
*b = loop { }
if r.uartfr().read().rxfe() { Ok(())
continue;
} }
let dr = r.uartdr().read(); 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() { if dr.oe() {
return Err(Error::Overrun); return Err(Error::Overrun);
@ -219,16 +287,34 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> {
} else if dr.fe() { } else if dr.fe() {
return Err(Error::Framing); return Err(Error::Framing);
} else { } else {
break dr.data(); *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();
} }
} }
Ok(())
} }
} }
impl<'d, T: Instance> UartRx<'d, T, Blocking> { 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")] #[cfg(feature = "nightly")]
pub fn into_buffered( pub fn into_buffered(
self, self,
@ -243,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> { impl<'d, T: Instance> UartRx<'d, T, Async> {
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { 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 ch = self.rx_dma.as_mut().unwrap();
let transfer = unsafe { 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_rxdmae(true);
reg.set_dmaonerr(true);
}); });
// If we don't assign future to a variable, the data register pointer // If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send. // 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) 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");
} }
} }
@ -268,7 +428,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
config: Config, config: Config,
) -> Self { ) -> Self {
into_ref!(tx, rx); 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) /// Create a new UART with hardware flow control (RTS/CTS)
@ -289,6 +449,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
Some(cts.map_into()), Some(cts.map_into()),
None, None,
None, None,
None,
config, config,
) )
} }
@ -317,17 +478,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
uart: impl Peripheral<P = T> + 'd, uart: impl Peripheral<P = T> + 'd,
tx: impl Peripheral<P = impl TxPin<T>> + 'd, tx: impl Peripheral<P = impl TxPin<T>> + 'd,
rx: impl Peripheral<P = impl RxPin<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, tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd, rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config, config: Config,
) -> Self { ) -> Self {
into_ref!(tx, rx, tx_dma, rx_dma); into_ref!(tx, rx, irq, tx_dma, rx_dma);
Self::new_inner( Self::new_inner(
uart, uart,
tx.map_into(), tx.map_into(),
rx.map_into(), rx.map_into(),
None, None,
None, None,
Some(irq),
Some(tx_dma.map_into()), Some(tx_dma.map_into()),
Some(rx_dma.map_into()), Some(rx_dma.map_into()),
config, config,
@ -341,17 +504,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
rx: impl Peripheral<P = impl RxPin<T>> + 'd, rx: impl Peripheral<P = impl RxPin<T>> + 'd,
rts: impl Peripheral<P = impl RtsPin<T>> + 'd, rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
cts: impl Peripheral<P = impl CtsPin<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, tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd, rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config, config: Config,
) -> Self { ) -> 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( Self::new_inner(
uart, uart,
tx.map_into(), tx.map_into(),
rx.map_into(), rx.map_into(),
Some(rts.map_into()), Some(rts.map_into()),
Some(cts.map_into()), Some(cts.map_into()),
Some(irq),
Some(tx_dma.map_into()), Some(tx_dma.map_into()),
Some(rx_dma.map_into()), Some(rx_dma.map_into()),
config, config,
@ -366,6 +531,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
mut rx: PeripheralRef<'d, AnyPin>, mut rx: PeripheralRef<'d, AnyPin>,
mut rts: Option<PeripheralRef<'d, AnyPin>>, mut rts: Option<PeripheralRef<'d, AnyPin>>,
mut cts: Option<PeripheralRef<'d, AnyPin>>, mut cts: Option<PeripheralRef<'d, AnyPin>>,
irq: Option<PeripheralRef<'d, T::Interrupt>>,
tx_dma: Option<PeripheralRef<'d, AnyChannel>>, tx_dma: Option<PeripheralRef<'d, AnyChannel>>,
rx_dma: Option<PeripheralRef<'d, AnyChannel>>, rx_dma: Option<PeripheralRef<'d, AnyChannel>>,
config: Config, config: Config,
@ -380,7 +546,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
Self { Self {
tx: UartTx::new_inner(tx_dma), tx: UartTx::new_inner(tx_dma),
rx: UartRx::new_inner(rx_dma), rx: UartRx::new_inner(irq, rx_dma),
} }
} }
@ -516,6 +682,14 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> {
self.rx.blocking_read(buffer) 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 /// Split the Uart into a transmitter and receiver, which is particuarly
/// useful when having two tasks correlating to transmitting and receiving. /// useful when having two tasks correlating to transmitting and receiving.
pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) {
@ -706,7 +880,9 @@ mod sealed {
fn regs() -> pac::uart::Uart; fn regs() -> pac::uart::Uart;
#[cfg(feature = "nightly")] #[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 TxPin<T: Instance> {}
pub trait RxPin<T: Instance> {} pub trait RxPin<T: Instance> {}
@ -744,10 +920,18 @@ macro_rules! impl_instance {
} }
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
fn state() -> &'static buffered::State { fn buffered_state() -> &'static buffered::State {
static STATE: buffered::State = buffered::State::new(); static STATE: buffered::State = buffered::State::new();
&STATE &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 {} impl Instance for peripherals::$inst {}
}; };

View File

@ -58,7 +58,7 @@ sdio-host = "0.5.0"
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
critical-section = "1.1" critical-section = "1.1"
atomic-polyfill = "1.0.1" atomic-polyfill = "1.0.1"
stm32-metapac = "6" stm32-metapac = "7"
vcell = "0.1.3" vcell = "0.1.3"
bxcan = "0.7.0" bxcan = "0.7.0"
nb = "1.0.0" nb = "1.0.0"
@ -74,7 +74,7 @@ critical-section = { version = "1.1", features = ["std"] }
[build-dependencies] [build-dependencies]
proc-macro2 = "1.0.36" proc-macro2 = "1.0.36"
quote = "1.0.15" quote = "1.0.15"
stm32-metapac = { version = "6", default-features = false, features = ["metadata"]} stm32-metapac = { version = "7", default-features = false, features = ["metadata"]}
[features] [features]
default = ["stm32-metapac/rt"] default = ["stm32-metapac/rt"]

View File

@ -3,18 +3,20 @@
use core::future::Future; use core::future::Future;
use core::pin::Pin; use core::pin::Pin;
use core::sync::atomic::{fence, Ordering}; use core::sync::atomic::{fence, Ordering};
use core::task::{Context, Poll}; use core::task::{Context, Poll, Waker};
use atomic_polyfill::AtomicUsize;
use embassy_cortex_m::interrupt::Priority; use embassy_cortex_m::interrupt::Priority;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError};
use super::word::{Word, WordSize}; use super::word::{Word, WordSize};
use super::Dir; use super::Dir;
use crate::_generated::BDMA_CHANNEL_COUNT; use crate::_generated::BDMA_CHANNEL_COUNT;
use crate::interrupt::{Interrupt, InterruptExt}; use crate::interrupt::{Interrupt, InterruptExt};
use crate::pac; use crate::pac;
use crate::pac::bdma::vals; use crate::pac::bdma::{regs, vals};
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -48,13 +50,16 @@ impl From<Dir> for vals::Dir {
struct State { struct State {
ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT], ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT],
complete_count: [AtomicUsize; BDMA_CHANNEL_COUNT],
} }
impl State { impl State {
const fn new() -> Self { const fn new() -> Self {
const ZERO: AtomicUsize = AtomicUsize::new(0);
const AW: AtomicWaker = AtomicWaker::new(); const AW: AtomicWaker = AtomicWaker::new();
Self { Self {
ch_wakers: [AW; BDMA_CHANNEL_COUNT], ch_wakers: [AW; BDMA_CHANNEL_COUNT],
complete_count: [ZERO; BDMA_CHANNEL_COUNT],
} }
} }
} }
@ -105,8 +110,23 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index
if isr.teif(channel_num) { if isr.teif(channel_num) {
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, 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() { if isr.tcif(channel_num) && cr.read().tcie() {
cr.write(|_| ()); // Disable channel interrupts with the default value. // 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(); STATE.ch_wakers[index].wake();
} }
} }
@ -252,6 +272,7 @@ impl<'a, C: Channel> Transfer<'a, C> {
let mut this = Self { channel }; let mut this = Self { channel };
this.clear_irqs(); this.clear_irqs();
STATE.complete_count[this.channel.index()].store(0, Ordering::Release);
#[cfg(dmamux)] #[cfg(dmamux)]
super::dmamux::configure_dmamux(&mut *this.channel, _request); super::dmamux::configure_dmamux(&mut *this.channel, _request);
@ -299,7 +320,9 @@ impl<'a, C: Channel> Transfer<'a, C> {
pub fn is_running(&mut self) -> bool { pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num()); let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.cr().read() }.en() 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 /// Gets the total remaining transfers for the channel
@ -342,3 +365,161 @@ impl<'a, C: Channel> Future for Transfer<'a, C> {
} }
} }
} }
// ==============================
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);
}
}

View File

@ -4,16 +4,17 @@ use core::pin::Pin;
use core::sync::atomic::{fence, Ordering}; use core::sync::atomic::{fence, Ordering};
use core::task::{Context, Poll, Waker}; use core::task::{Context, Poll, Waker};
use atomic_polyfill::AtomicUsize;
use embassy_cortex_m::interrupt::Priority; use embassy_cortex_m::interrupt::Priority;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use pac::dma::regs;
use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError};
use super::word::{Word, WordSize}; use super::word::{Word, WordSize};
use super::Dir; use super::Dir;
use crate::_generated::DMA_CHANNEL_COUNT; use crate::_generated::DMA_CHANNEL_COUNT;
use crate::interrupt::{Interrupt, InterruptExt}; use crate::interrupt::{Interrupt, InterruptExt};
use crate::pac::dma::vals; use crate::pac::dma::{regs, vals};
use crate::{interrupt, pac}; use crate::{interrupt, pac};
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -128,13 +129,16 @@ impl From<FifoThreshold> for vals::Fth {
struct State { struct State {
ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT], ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT],
complete_count: [AtomicUsize; DMA_CHANNEL_COUNT],
} }
impl State { impl State {
const fn new() -> Self { const fn new() -> Self {
const ZERO: AtomicUsize = AtomicUsize::new(0);
const AW: AtomicWaker = AtomicWaker::new(); const AW: AtomicWaker = AtomicWaker::new();
Self { Self {
ch_wakers: [AW; DMA_CHANNEL_COUNT], ch_wakers: [AW; DMA_CHANNEL_COUNT],
complete_count: [ZERO; DMA_CHANNEL_COUNT],
} }
} }
} }
@ -183,9 +187,22 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: usize, index:
panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num); panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num);
} }
let mut wake = false;
if isr.htif(channel_num % 4) && cr.read().htie() {
// Acknowledge half transfer complete interrupt
dma.ifcr(channel_num / 4).write(|w| w.set_htif(channel_num % 4, true));
wake = true;
}
if isr.tcif(channel_num % 4) && cr.read().tcie() { if isr.tcif(channel_num % 4) && cr.read().tcie() {
/* acknowledge transfer complete interrupt */ // Acknowledge transfer complete interrupt
dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true));
STATE.complete_count[index].fetch_add(1, Ordering::Release);
wake = true;
}
if wake {
STATE.ch_wakers[index].wake(); STATE.ch_wakers[index].wake();
} }
} }
@ -445,7 +462,6 @@ impl<'a, C: Channel> Future for Transfer<'a, C> {
// ================================== // ==================================
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct DoubleBuffered<'a, C: Channel, W: Word> { pub struct DoubleBuffered<'a, C: Channel, W: Word> {
channel: PeripheralRef<'a, C>, channel: PeripheralRef<'a, C>,
_phantom: PhantomData<W>, _phantom: PhantomData<W>,
@ -530,6 +546,7 @@ impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> {
unsafe { unsafe {
dma.ifcr(isrn).write(|w| { dma.ifcr(isrn).write(|w| {
w.set_htif(isrbit, true);
w.set_tcif(isrbit, true); w.set_tcif(isrbit, true);
w.set_teif(isrbit, true); w.set_teif(isrbit, true);
}) })
@ -578,15 +595,6 @@ impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> {
let ch = self.channel.regs().st(self.channel.num()); let ch = self.channel.regs().st(self.channel.num());
unsafe { ch.ndtr().read() }.ndt() unsafe { ch.ndtr().read() }.ndt()
} }
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);
}
} }
impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> { impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> {
@ -598,3 +606,180 @@ impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> {
fence(Ordering::SeqCst); fence(Ordering::SeqCst);
} }
} }
// ==============================
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().st(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);
let mut w = regs::Cr(0);
w.set_dir(dir.into());
w.set_msize(data_size.into());
w.set_psize(data_size.into());
w.set_pl(vals::Pl::VERYHIGH);
w.set_minc(vals::Inc::INCREMENTED);
w.set_pinc(vals::Inc::FIXED);
w.set_teie(true);
w.set_htie(true);
w.set_tcie(true);
w.set_circ(vals::Circ::ENABLED);
#[cfg(dma_v1)]
w.set_trbuff(true);
#[cfg(dma_v2)]
w.set_chsel(_request);
w.set_pburst(options.pburst.into());
w.set_mburst(options.mburst.into());
w.set_pfctrl(options.flow_ctrl.into());
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.st(channel_number);
ch.par().write_value(peri_addr as u32);
ch.m0ar().write_value(buffer_ptr as u32);
ch.ndtr().write_value(regs::Ndtr(len as _));
ch.fcr().write(|w| {
if let Some(fth) = options.fifo_threshold {
// FIFO mode
w.set_dmdis(vals::Dmdis::DISABLED);
w.set_fth(fth.into());
} else {
// Direct mode
w.set_dmdis(vals::Dmdis::ENABLED);
}
});
this
}
pub fn start(&mut self) {
let ch = self.channel.regs().st(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 channel_number = self.channel.num();
let dma = self.channel.regs();
let isrn = channel_number / 4;
let isrbit = channel_number % 4;
unsafe {
dma.ifcr(isrn).write(|w| {
w.set_htif(isrbit, true);
w.set_tcif(isrbit, true);
w.set_teif(isrbit, true);
})
}
}
pub fn request_stop(&mut self) {
let ch = self.channel.regs().st(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().st(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().st(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);
}
}

View File

@ -21,6 +21,7 @@ pub use gpdma::*;
#[cfg(dmamux)] #[cfg(dmamux)]
mod dmamux; mod dmamux;
pub(crate) mod ringbuffer;
pub mod word; pub mod word;
use core::mem; use core::mem;

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
}
}

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,20 +44,19 @@ pub mod i2c;
#[cfg(crc)] #[cfg(crc)]
pub mod crc; pub mod crc;
pub mod flash; pub mod flash;
#[cfg(stm32wb)]
pub mod ipcc;
pub mod pwm; pub mod pwm;
#[cfg(quadspi)] #[cfg(quadspi)]
pub mod qspi; pub mod qspi;
#[cfg(rng)] #[cfg(rng)]
pub mod rng; pub mod rng;
#[cfg(all(rtc, not(any(rtc_v1, rtc_v2f0, rtc_v2f7, rtc_v3, rtc_v3u5))))] #[cfg(all(rtc, not(rtc_v1)))]
pub mod rtc; pub mod rtc;
#[cfg(sdmmc)] #[cfg(sdmmc)]
pub mod sdmmc; pub mod sdmmc;
#[cfg(spi)] #[cfg(spi)]
pub mod spi; pub mod spi;
#[cfg(stm32wl)]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod subghz;
#[cfg(usart)] #[cfg(usart)]
pub mod usart; pub mod usart;
#[cfg(all(usb, feature = "time"))] #[cfg(all(usb, feature = "time"))]

View File

@ -51,7 +51,7 @@ pub struct DateTime {
impl From<chrono::NaiveDateTime> for DateTime { impl From<chrono::NaiveDateTime> for DateTime {
fn from(date_time: chrono::NaiveDateTime) -> Self { fn from(date_time: chrono::NaiveDateTime) -> Self {
Self { Self {
year: (date_time.year() - 1970) as u16, year: date_time.year() as u16,
month: date_time.month() as u8, month: date_time.month() as u8,
day: date_time.day() as u8, day: date_time.day() as u8,
day_of_week: date_time.weekday().into(), day_of_week: date_time.weekday().into(),
@ -65,11 +65,7 @@ impl From<chrono::NaiveDateTime> for DateTime {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
impl From<DateTime> for chrono::NaiveDateTime { impl From<DateTime> for chrono::NaiveDateTime {
fn from(date_time: DateTime) -> Self { fn from(date_time: DateTime) -> Self {
NaiveDate::from_ymd_opt( NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32)
(date_time.year + 1970) as i32,
date_time.month as u32,
date_time.day as u32,
)
.unwrap() .unwrap()
.and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32) .and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32)
.unwrap() .unwrap()
@ -159,6 +155,8 @@ pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) {
let (yt, yu) = byte_to_bcd2(yr_offset); let (yt, yu) = byte_to_bcd2(yr_offset);
unsafe { unsafe {
use crate::pac::rtc::vals::Ampm;
rtc.tr().write(|w| { rtc.tr().write(|w| {
w.set_ht(ht); w.set_ht(ht);
w.set_hu(hu); w.set_hu(hu);
@ -166,7 +164,7 @@ pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) {
w.set_mnu(mnu); w.set_mnu(mnu);
w.set_st(st); w.set_st(st);
w.set_su(su); w.set_su(su);
w.set_pm(stm32_metapac::rtc::vals::Ampm::AM); w.set_pm(Ampm::AM);
}); });
rtc.dr().write(|w| { rtc.dr().write(|w| {

View File

@ -1,85 +0,0 @@
use chrono::{Datelike, Timelike};
use super::byte_to_bcd2;
use crate::pac::rtc::Rtc;
/// Alias for [`chrono::NaiveDateTime`]
pub type DateTime = chrono::NaiveDateTime;
/// Alias for [`chrono::Weekday`]
pub type DayOfWeek = chrono::Weekday;
/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
///
/// [`DateTimeFilter`]: struct.DateTimeFilter.html
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Error {
/// The [DateTime] has an invalid year. The year must be between 0 and 4095.
InvalidYear,
/// The [DateTime] contains an invalid date.
InvalidDate,
/// The [DateTime] contains an invalid time.
InvalidTime,
}
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
dotw.num_days_from_monday() as u8
}
pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
if dt.year() < 0 || dt.year() > 4095 {
// rp2040 can't hold these years
Err(Error::InvalidYear)
} else {
// The rest of the chrono date is assumed to be valid
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 {
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(stm32_metapac::rtc::vals::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.weekday()));
});
}
}
pub(super) fn datetime(
year: u16,
month: u8,
day: u8,
_day_of_week: u8,
hour: u8,
minute: u8,
second: u8,
) -> Result<DateTime, Error> {
let date = chrono::NaiveDate::from_ymd_opt(year.into(), month.try_into().unwrap(), day.into())
.ok_or(Error::InvalidDate)?;
let time = chrono::NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidTime)?;
Ok(DateTime::new(date, time))
}

View File

@ -10,12 +10,12 @@ pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
any( any(
rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb
), ),
path = "v2/mod.rs" path = "v2.rs"
)] )]
#[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")] #[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")]
mod versions; mod _version;
pub use _version::*;
use embassy_hal_common::Peripheral; use embassy_hal_common::Peripheral;
pub use versions::*;
/// Errors that can occur on methods on [RtcClock] /// Errors that can occur on methods on [RtcClock]
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -113,7 +113,7 @@ impl Default for RtcCalibrationCyclePeriod {
impl<'d, T: Instance> Rtc<'d, T> { impl<'d, T: Instance> Rtc<'d, T> {
pub fn new(_rtc: impl Peripheral<P = T> + 'd, rtc_config: RtcConfig) -> Self { pub fn new(_rtc: impl Peripheral<P = T> + 'd, rtc_config: RtcConfig) -> Self {
unsafe { enable_peripheral_clk() }; unsafe { T::enable_peripheral_clk() };
let mut rtc_struct = Self { let mut rtc_struct = Self {
phantom: PhantomData, phantom: PhantomData,
@ -179,14 +179,14 @@ impl<'d, T: Instance> Rtc<'d, T> {
self.rtc_config self.rtc_config
} }
pub const BACKUP_REGISTER_COUNT: usize = BACKUP_REGISTER_COUNT; pub const BACKUP_REGISTER_COUNT: usize = T::BACKUP_REGISTER_COUNT;
/// Read content of the backup register. /// Read content of the backup register.
/// ///
/// The registers retain their values during wakes from standby mode or system resets. They also /// 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. /// 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> { pub fn read_backup_register(&self, register: usize) -> Option<u32> {
read_backup_register(&T::regs(), register) T::read_backup_register(&T::regs(), register)
} }
/// Set content of the backup register. /// Set content of the backup register.
@ -194,7 +194,7 @@ impl<'d, T: Instance> Rtc<'d, T> {
/// The registers retain their values during wakes from standby mode or system resets. They also /// 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. /// 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) { pub fn write_backup_register(&self, register: usize, value: u32) {
write_backup_register(&T::regs(), register, value) T::write_backup_register(&T::regs(), register, value)
} }
} }
@ -219,17 +219,31 @@ pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 {
} }
pub(crate) mod sealed { pub(crate) mod sealed {
use crate::pac::rtc::Rtc;
pub trait Instance { pub trait Instance {
fn regs() -> crate::pac::rtc::Rtc; 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 {} pub trait Instance: sealed::Instance + 'static {}
impl sealed::Instance for crate::peripherals::RTC {
fn regs() -> crate::pac::rtc::Rtc {
crate::pac::RTC
}
}
impl Instance for crate::peripherals::RTC {}

View File

@ -1,29 +1,78 @@
use stm32_metapac::rtc::vals::{Init, Osel, Pol}; use stm32_metapac::rtc::vals::{Init, Osel, Pol};
use super::{Instance, RtcConfig}; use super::{sealed, Instance, RtcConfig};
use crate::pac::rtc::Rtc; use crate::pac::rtc::Rtc;
#[cfg_attr(rtc_v2f0, path = "v2f0.rs")]
#[cfg_attr(rtc_v2f2, path = "v2f2.rs")]
#[cfg_attr(rtc_v2f3, path = "v2f3.rs")]
#[cfg_attr(rtc_v2f4, path = "v2f4.rs")]
#[cfg_attr(rtc_v2f7, path = "v2f7.rs")]
#[cfg_attr(rtc_v2h7, path = "v2h7.rs")]
#[cfg_attr(rtc_v2l0, path = "v2l0.rs")]
#[cfg_attr(rtc_v2l1, path = "v2l1.rs")]
#[cfg_attr(rtc_v2l4, path = "v2l4.rs")]
#[cfg_attr(rtc_v2wb, path = "v2wb.rs")]
mod family;
pub use family::*;
impl<'d, T: Instance> super::Rtc<'d, T> { impl<'d, T: Instance> super::Rtc<'d, T> {
/// Applies the RTC config /// Applies the RTC config
/// It this changes the RTC clock source the time will be reset /// It this changes the RTC clock source the time will be reset
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
// Unlock the backup domain // Unlock the backup domain
unsafe { unsafe {
unlock_backup_domain(rtc_config.clock_config as u8); 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 { self.write(true, |rtc| unsafe {
@ -148,24 +197,33 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
} }
} }
/// Read content of the backup register. impl sealed::Instance for crate::peripherals::RTC {
/// const BACKUP_REGISTER_COUNT: usize = 20;
/// 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. unsafe fn enable_peripheral_clk() {
pub fn read_backup_register(rtc: &Rtc, register: usize) -> Option<u32> { #[cfg(any(rtc_v2l4, rtc_v2wb))]
if register < BACKUP_REGISTER_COUNT { {
// 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() }) Some(unsafe { rtc.bkpr(register).read().bkp() })
} else { } else {
None None
} }
} }
/// Set content of the backup register. fn write_backup_register(rtc: &Rtc, register: usize, value: u32) {
/// if register < Self::BACKUP_REGISTER_COUNT {
/// 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(rtc: &Rtc, register: usize, value: u32) {
if register < BACKUP_REGISTER_COUNT {
unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) } unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) }
} }
}
} }
impl Instance for crate::peripherals::RTC {}

View File

@ -1,41 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
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());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// 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();
}

View File

@ -1,31 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
w.set_rtcen(true);
w.set_lseon(reg.lseon());
w.set_lsebyp(reg.lsebyp());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// Nothing to do
}

View File

@ -1,31 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
w.set_rtcen(true);
w.set_lseon(reg.lseon());
w.set_lsebyp(reg.lsebyp());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// Nothing to do
}

View File

@ -1,31 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
w.set_rtcen(true);
w.set_lseon(reg.lseon());
w.set_lsebyp(reg.lsebyp());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// Nothing to do
}

View File

@ -1,41 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
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());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// 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();
}

View File

@ -1,33 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
w.set_rtcen(true);
w.set_lseon(reg.lseon());
w.set_lsedrv(reg.lsedrv());
w.set_lsebyp(reg.lsebyp());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// Nothing to do
}

View File

@ -1,26 +0,0 @@
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
// TODO: Missing from PAC?
// crate::pac::PWR.cr().modify(|w| w.set_dbp(true));
// while !crate::pac::PWR.cr().read().dbp() {}
let reg = crate::pac::RCC.csr().read();
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
crate::pac::RCC.csr().modify(|w| {
// Select RTC source
w.set_rtcsel(crate::pac::rcc::vals::Rtcsel(clock_config));
w.set_rtcen(true);
w.set_lseon(reg.lseon());
w.set_lsedrv(reg.lsedrv());
w.set_lsebyp(reg.lsebyp());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// Nothing to do
}

View File

@ -1,24 +0,0 @@
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr().read().dbp() {}
let reg = crate::pac::RCC.csr().read();
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
crate::pac::RCC.csr().modify(|w| {
// Select RTC source
w.set_rtcsel(crate::pac::rcc::vals::Rtcsel(clock_config));
w.set_rtcen(true);
w.set_lseon(reg.lseon());
w.set_lsebyp(reg.lsebyp());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// Nothing to do
}

View File

@ -1,41 +0,0 @@
use stm32_metapac::rcc::vals::Rtcsel;
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
if !reg.rtcen() || reg.rtcsel().0 != clock_config {
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(Rtcsel(clock_config));
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());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// 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();
}

View File

@ -1,39 +0,0 @@
pub const BACKUP_REGISTER_COUNT: usize = 20;
/// Unlock the backup domain
pub(super) unsafe fn unlock_backup_domain(clock_config: u8) {
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {}
let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
if !reg.rtcen() || reg.rtcsel() != clock_config {
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(clock_config);
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());
});
}
}
pub(crate) unsafe fn enable_peripheral_clk() {
// 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();
}

View File

@ -1,6 +1,6 @@
use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType}; use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType};
use super::{Instance, RtcCalibrationCyclePeriod, RtcConfig}; use super::{sealed, Instance, RtcCalibrationCyclePeriod, RtcConfig};
use crate::pac::rtc::Rtc; use crate::pac::rtc::Rtc;
impl<'d, T: Instance> super::Rtc<'d, T> { impl<'d, T: Instance> super::Rtc<'d, T> {
@ -9,43 +9,30 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
// Unlock the backup domain // Unlock the backup domain
unsafe { unsafe {
#[cfg(feature = "stm32g0c1ve")] #[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)); crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
while !crate::pac::PWR.cr1().read().dbp() {} while !crate::pac::PWR.cr1().read().dbp() {}
} }
#[cfg(any(rcc_wl5, rcc_wle))]
#[cfg(not(any(
feature = "stm32g0c1ve",
feature = "stm32g491re",
feature = "stm32u585zi",
feature = "stm32g473cc"
)))]
{ {
crate::pac::PWR use crate::pac::pwr::vals::Dbp;
.cr1()
.modify(|w| w.set_dbp(stm32_metapac::pwr::vals::Dbp::ENABLED)); crate::pac::PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED));
while crate::pac::PWR.cr1().read().dbp() != stm32_metapac::pwr::vals::Dbp::DISABLED {} while crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED {}
} }
let reg = crate::pac::RCC.bdcr().read(); let reg = crate::pac::RCC.bdcr().read();
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
let config_rtcsel = rtc_config.clock_config as u8; let config_rtcsel = rtc_config.clock_config as u8;
#[cfg(not(any( #[cfg(not(any(rcc_wl5, rcc_wle, rcc_g4)))]
feature = "stm32wl54jc-cm0p", let config_rtcsel = Rtcsel(config_rtcsel);
feature = "stm32wle5ub",
feature = "stm32g0c1ve",
feature = "stm32wl55jc-cm4",
feature = "stm32wl55uc-cm4",
feature = "stm32g491re",
feature = "stm32g473cc",
feature = "stm32u585zi",
feature = "stm32wle5jb"
)))]
let config_rtcsel = stm32_metapac::rtc::vals::Rtcsel(config_rtcsel);
#[cfg(feature = "stm32g0c1ve")]
let config_rtcsel = stm32_metapac::rcc::vals::Rtcsel(config_rtcsel);
if !reg.rtcen() || reg.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| w.set_bdrst(true));
@ -195,32 +182,24 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
} }
} }
pub(super) unsafe fn enable_peripheral_clk() { impl sealed::Instance for crate::peripherals::RTC {
// Nothing to do const BACKUP_REGISTER_COUNT: usize = 32;
}
pub const BACKUP_REGISTER_COUNT: usize = 32; fn read_backup_register(_rtc: &Rtc, register: usize) -> Option<u32> {
if register < Self::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(_rtc: &Rtc, register: usize) -> Option<u32> {
if register < BACKUP_REGISTER_COUNT {
//Some(rtc.bkpr()[register].read().bits()) //Some(rtc.bkpr()[register].read().bits())
None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
} else { } else {
None None
} }
} }
/// Set content of the backup register. fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) {
/// if register < Self::BACKUP_REGISTER_COUNT {
/// 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(_rtc: &Rtc, register: usize, _value: u32) {
if register < BACKUP_REGISTER_COUNT {
// RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC // 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)) } //unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) }
} }
}
} }
impl Instance for crate::peripherals::RTC {}

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

View File

@ -1,14 +0,0 @@
/// Power amplifier over current protection.
///
/// Used by [`set_pa_ocp`].
///
/// [`set_pa_ocp`]: super::SubGhz::set_pa_ocp
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Ocp {
/// Maximum 60mA current for LP PA mode.
Max60m = 0x18,
/// Maximum 140mA for HP PA mode.
Max140m = 0x38,
}

View File

@ -1,48 +0,0 @@
/// Operation Errors.
///
/// Returned by [`op_error`].
///
/// [`op_error`]: super::SubGhz::op_error
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum OpError {
/// PA ramping failed
PaRampError = 8,
/// RF-PLL locking failed
PllLockError = 6,
/// HSE32 clock startup failed
XoscStartError = 5,
/// Image calibration failed
ImageCalibrationError = 4,
/// RF-ADC calibration failed
AdcCalibrationError = 3,
/// RF-PLL calibration failed
PllCalibrationError = 2,
/// Sub-GHz radio RC 13 MHz oscillator
RC13MCalibrationError = 1,
/// Sub-GHz radio RC 64 kHz oscillator
RC64KCalibrationError = 0,
}
impl OpError {
/// Get the bitmask for the error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::OpError;
///
/// assert_eq!(OpError::PaRampError.mask(), 0b1_0000_0000);
/// assert_eq!(OpError::PllLockError.mask(), 0b0_0100_0000);
/// assert_eq!(OpError::XoscStartError.mask(), 0b0_0010_0000);
/// assert_eq!(OpError::ImageCalibrationError.mask(), 0b0_0001_0000);
/// assert_eq!(OpError::AdcCalibrationError.mask(), 0b0_0000_1000);
/// assert_eq!(OpError::PllCalibrationError.mask(), 0b0_0000_0100);
/// assert_eq!(OpError::RC13MCalibrationError.mask(), 0b0_0000_0010);
/// assert_eq!(OpError::RC64KCalibrationError.mask(), 0b0_0000_0001);
/// ```
pub const fn mask(self) -> u16 {
1 << (self as u8)
}
}

View File

@ -1,196 +0,0 @@
/// Power amplifier configuration parameters.
///
/// Argument of [`set_pa_config`].
///
/// [`set_pa_config`]: super::SubGhz::set_pa_config
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PaConfig {
buf: [u8; 5],
}
impl PaConfig {
/// Optimal settings for +15dBm output power with the low-power PA.
///
/// This must be used with [`TxParams::LP_15`](super::TxParams::LP_15).
pub const LP_15: PaConfig = PaConfig::new().set_pa_duty_cycle(0x6).set_hp_max(0x0).set_pa(PaSel::Lp);
/// Optimal settings for +14dBm output power with the low-power PA.
///
/// This must be used with [`TxParams::LP_14`](super::TxParams::LP_14).
pub const LP_14: PaConfig = PaConfig::new().set_pa_duty_cycle(0x4).set_hp_max(0x0).set_pa(PaSel::Lp);
/// Optimal settings for +10dBm output power with the low-power PA.
///
/// This must be used with [`TxParams::LP_10`](super::TxParams::LP_10).
pub const LP_10: PaConfig = PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp);
/// Optimal settings for +22dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_22: PaConfig = PaConfig::new().set_pa_duty_cycle(0x4).set_hp_max(0x7).set_pa(PaSel::Hp);
/// Optimal settings for +20dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_20: PaConfig = PaConfig::new().set_pa_duty_cycle(0x3).set_hp_max(0x5).set_pa(PaSel::Hp);
/// Optimal settings for +17dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_17: PaConfig = PaConfig::new().set_pa_duty_cycle(0x2).set_hp_max(0x3).set_pa(PaSel::Hp);
/// Optimal settings for +14dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_14: PaConfig = PaConfig::new().set_pa_duty_cycle(0x2).set_hp_max(0x2).set_pa(PaSel::Hp);
/// Create a new `PaConfig` struct.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PaConfig;
///
/// const PA_CONFIG: PaConfig = PaConfig::new();
/// ```
pub const fn new() -> PaConfig {
PaConfig {
buf: [super::OpCode::SetPaConfig as u8, 0x01, 0x00, 0x01, 0x01],
}
}
/// Set the power amplifier duty cycle (conduit angle) control.
///
/// **Note:** Only the first 3 bits of the `pa_duty_cycle` argument are used.
///
/// Duty cycle = 0.2 + 0.04 × bits
///
/// # Caution
///
/// The following restrictions must be observed to avoid over-stress on the PA:
/// * LP PA mode with synthesis frequency > 400 MHz, `pa_duty_cycle` must be < 0x7.
/// * LP PA mode with synthesis frequency < 400 MHz, `pa_duty_cycle` must be < 0x4.
/// * HP PA mode, `pa_duty_cycle` must be < 0x4
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Lp).set_pa_duty_cycle(0x4);
/// # assert_eq!(PA_CONFIG.as_slice()[1], 0x04);
/// ```
#[must_use = "set_pa_duty_cycle returns a modified PaConfig"]
pub const fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> PaConfig {
self.buf[1] = pa_duty_cycle & 0b111;
self
}
/// Set the high power amplifier output power.
///
/// **Note:** Only the first 3 bits of the `hp_max` argument are used.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Hp).set_hp_max(0x2);
/// # assert_eq!(PA_CONFIG.as_slice()[2], 0x02);
/// ```
#[must_use = "set_hp_max returns a modified PaConfig"]
pub const fn set_hp_max(mut self, hp_max: u8) -> PaConfig {
self.buf[2] = hp_max & 0b111;
self
}
/// Set the power amplifier to use, low or high power.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG_HP: PaConfig = PaConfig::new().set_pa(PaSel::Hp);
/// const PA_CONFIG_LP: PaConfig = PaConfig::new().set_pa(PaSel::Lp);
/// # assert_eq!(PA_CONFIG_HP.as_slice()[3], 0x00);
/// # assert_eq!(PA_CONFIG_LP.as_slice()[3], 0x01);
/// ```
#[must_use = "set_pa returns a modified PaConfig"]
pub const fn set_pa(mut self, pa: PaSel) -> PaConfig {
self.buf[3] = pa as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG: PaConfig = PaConfig::new()
/// .set_pa(PaSel::Hp)
/// .set_pa_duty_cycle(0x2)
/// .set_hp_max(0x3);
///
/// assert_eq!(PA_CONFIG.as_slice(), &[0x95, 0x2, 0x03, 0x00, 0x01]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for PaConfig {
fn default() -> Self {
Self::new()
}
}
/// Power amplifier selection.
///
/// Argument of [`PaConfig::set_pa`].
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PaSel {
/// High power amplifier.
Hp = 0b0,
/// Low power amplifier.
Lp = 0b1,
}
impl PartialOrd for PaSel {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PaSel {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(PaSel::Hp, PaSel::Hp) | (PaSel::Lp, PaSel::Lp) => core::cmp::Ordering::Equal,
(PaSel::Hp, PaSel::Lp) => core::cmp::Ordering::Greater,
(PaSel::Lp, PaSel::Hp) => core::cmp::Ordering::Less,
}
}
}
impl Default for PaSel {
fn default() -> Self {
PaSel::Lp
}
}
#[cfg(test)]
mod test {
use super::PaSel;
#[test]
fn pa_sel_ord() {
assert!(PaSel::Lp < PaSel::Hp);
assert!(PaSel::Hp > PaSel::Lp);
}
}

View File

@ -1,534 +0,0 @@
/// Preamble detection length for [`GenericPacketParams`].
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PreambleDetection {
/// Preamble detection disabled.
Disabled = 0x0,
/// 8-bit preamble detection.
Bit8 = 0x4,
/// 16-bit preamble detection.
Bit16 = 0x5,
/// 24-bit preamble detection.
Bit24 = 0x6,
/// 32-bit preamble detection.
Bit32 = 0x7,
}
/// Address comparison/filtering for [`GenericPacketParams`].
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddrComp {
/// Address comparison/filtering disabled.
Disabled = 0x0,
/// Address comparison/filtering on node address.
Node = 0x1,
/// Address comparison/filtering on node and broadcast addresses.
Broadcast = 0x2,
}
/// Packet header type.
///
/// Argument of [`GenericPacketParams::set_header_type`] and
/// [`LoRaPacketParams::set_header_type`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum HeaderType {
/// Fixed; payload length and header field not added to packet.
Fixed,
/// Variable; payload length and header field added to packet.
Variable,
}
impl HeaderType {
pub(crate) const fn to_bits_generic(self) -> u8 {
match self {
HeaderType::Fixed => 0,
HeaderType::Variable => 1,
}
}
pub(crate) const fn to_bits_lora(self) -> u8 {
match self {
HeaderType::Fixed => 1,
HeaderType::Variable => 0,
}
}
}
/// CRC type definition for [`GenericPacketParams`].
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CrcType {
/// 1-byte CRC.
Byte1 = 0x0,
/// CRC disabled.
Disabled = 0x1,
/// 2-byte CRC.
Byte2 = 0x2,
/// 1-byte inverted CRC.
Byte1Inverted = 0x4,
/// 2-byte inverted CRC.
Byte2Inverted = 0x6,
}
/// Packet parameters for [`set_packet_params`].
///
/// [`set_packet_params`]: super::SubGhz::set_packet_params
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GenericPacketParams {
buf: [u8; 10],
}
impl GenericPacketParams {
/// Create a new `GenericPacketParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new();
/// assert_eq!(PKT_PARAMS, GenericPacketParams::default());
/// ```
pub const fn new() -> GenericPacketParams {
const OPCODE: u8 = super::OpCode::SetPacketParams as u8;
// const variable ensure the compile always optimizes the methods
const NEW: GenericPacketParams = GenericPacketParams {
buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
.set_preamble_len(1)
.set_preamble_detection(PreambleDetection::Disabled)
.set_sync_word_len(0)
.set_addr_comp(AddrComp::Disabled)
.set_header_type(HeaderType::Fixed)
.set_payload_len(1);
NEW
}
/// Preamble length in number of symbols.
///
/// Values of zero are invalid, and will automatically be set to 1.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_preamble_len(0x1234);
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12);
/// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34);
/// ```
#[must_use = "preamble_length returns a modified GenericPacketParams"]
pub const fn set_preamble_len(mut self, mut len: u16) -> GenericPacketParams {
if len == 0 {
len = 1
}
self.buf[1] = ((len >> 8) & 0xFF) as u8;
self.buf[2] = (len & 0xFF) as u8;
self
}
/// Preamble detection length in number of bit symbols.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{GenericPacketParams, PreambleDetection};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_preamble_detection(PreambleDetection::Bit8);
/// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x4);
/// ```
#[must_use = "set_preamble_detection returns a modified GenericPacketParams"]
pub const fn set_preamble_detection(mut self, pb_det: PreambleDetection) -> GenericPacketParams {
self.buf[3] = pb_det as u8;
self
}
/// Sync word length in number of bit symbols.
///
/// Valid values are `0x00` - `0x40` for 0 to 64-bits respectively.
/// Values that exceed the maximum will saturate at `0x40`.
///
/// # Example
///
/// Set the sync word length to 4 bytes (16 bits).
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_sync_word_len(16);
/// # assert_eq!(PKT_PARAMS.as_slice()[4], 0x10);
/// ```
#[must_use = "set_sync_word_len returns a modified GenericPacketParams"]
pub const fn set_sync_word_len(mut self, len: u8) -> GenericPacketParams {
const MAX: u8 = 0x40;
if len > MAX {
self.buf[4] = MAX;
} else {
self.buf[4] = len;
}
self
}
/// Address comparison/filtering.
///
/// # Example
///
/// Enable address on the node address.
///
/// ```
/// use stm32wlxx_hal::subghz::{AddrComp, GenericPacketParams};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_addr_comp(AddrComp::Node);
/// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x01);
/// ```
#[must_use = "set_addr_comp returns a modified GenericPacketParams"]
pub const fn set_addr_comp(mut self, addr_comp: AddrComp) -> GenericPacketParams {
self.buf[5] = addr_comp as u8;
self
}
/// Header type definition.
///
/// **Note:** The reference manual calls this packet type, but that results
/// in a conflicting variable name for the modulation scheme, which the
/// reference manual also calls packet type.
///
/// # Example
///
/// Set the header type to a variable length.
///
/// ```
/// use stm32wlxx_hal::subghz::{GenericPacketParams, HeaderType};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_header_type(HeaderType::Variable);
/// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x01);
/// ```
#[must_use = "set_header_type returns a modified GenericPacketParams"]
pub const fn set_header_type(mut self, header_type: HeaderType) -> GenericPacketParams {
self.buf[6] = header_type.to_bits_generic();
self
}
/// Set the payload length in bytes.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_payload_len(12);
/// # assert_eq!(PKT_PARAMS.as_slice()[7], 12);
/// ```
#[must_use = "set_payload_len returns a modified GenericPacketParams"]
pub const fn set_payload_len(mut self, len: u8) -> GenericPacketParams {
self.buf[7] = len;
self
}
/// CRC type definition.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CrcType, GenericPacketParams};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_crc_type(CrcType::Byte2Inverted);
/// # assert_eq!(PKT_PARAMS.as_slice()[8], 0x6);
/// ```
#[must_use = "set_payload_len returns a modified GenericPacketParams"]
pub const fn set_crc_type(mut self, crc_type: CrcType) -> GenericPacketParams {
self.buf[8] = crc_type as u8;
self
}
/// Whitening enable.
///
/// # Example
///
/// Enable whitening.
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_whitening_enable(true);
/// # assert_eq!(PKT_PARAMS.as_slice()[9], 1);
/// ```
#[must_use = "set_whitening_enable returns a modified GenericPacketParams"]
pub const fn set_whitening_enable(mut self, en: bool) -> GenericPacketParams {
self.buf[9] = en as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{
/// AddrComp, CrcType, GenericPacketParams, HeaderType, PreambleDetection,
/// };
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new()
/// .set_preamble_len(8)
/// .set_preamble_detection(PreambleDetection::Disabled)
/// .set_sync_word_len(2)
/// .set_addr_comp(AddrComp::Disabled)
/// .set_header_type(HeaderType::Fixed)
/// .set_payload_len(128)
/// .set_crc_type(CrcType::Byte2)
/// .set_whitening_enable(true);
///
/// assert_eq!(
/// PKT_PARAMS.as_slice(),
/// &[0x8C, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x80, 0x02, 0x01]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for GenericPacketParams {
fn default() -> Self {
Self::new()
}
}
/// Packet parameters for [`set_lora_packet_params`].
///
/// [`set_lora_packet_params`]: super::SubGhz::set_lora_packet_params
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LoRaPacketParams {
buf: [u8; 7],
}
impl LoRaPacketParams {
/// Create a new `LoRaPacketParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new();
/// assert_eq!(PKT_PARAMS, LoRaPacketParams::default());
/// ```
pub const fn new() -> LoRaPacketParams {
const OPCODE: u8 = super::OpCode::SetPacketParams as u8;
// const variable ensure the compile always optimizes the methods
const NEW: LoRaPacketParams = LoRaPacketParams {
buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
.set_preamble_len(1)
.set_header_type(HeaderType::Fixed)
.set_payload_len(1)
.set_crc_en(true)
.set_invert_iq(false);
NEW
}
/// Preamble length in number of symbols.
///
/// Values of zero are invalid, and will automatically be set to 1.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_preamble_len(0x1234);
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12);
/// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34);
/// ```
#[must_use = "preamble_length returns a modified LoRaPacketParams"]
pub const fn set_preamble_len(mut self, mut len: u16) -> LoRaPacketParams {
if len == 0 {
len = 1
}
self.buf[1] = ((len >> 8) & 0xFF) as u8;
self.buf[2] = (len & 0xFF) as u8;
self
}
/// Header type (fixed or variable).
///
/// # Example
///
/// Set the payload type to a fixed length.
///
/// ```
/// use stm32wlxx_hal::subghz::{HeaderType, LoRaPacketParams};
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_header_type(HeaderType::Fixed);
/// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x01);
/// ```
#[must_use = "set_header_type returns a modified LoRaPacketParams"]
pub const fn set_header_type(mut self, header_type: HeaderType) -> LoRaPacketParams {
self.buf[3] = header_type.to_bits_lora();
self
}
/// Set the payload length in bytes.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_payload_len(12);
/// # assert_eq!(PKT_PARAMS.as_slice()[4], 12);
/// ```
#[must_use = "set_payload_len returns a modified LoRaPacketParams"]
pub const fn set_payload_len(mut self, len: u8) -> LoRaPacketParams {
self.buf[4] = len;
self
}
/// CRC enable.
///
/// # Example
///
/// Enable CRC.
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_crc_en(true);
/// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x1);
/// ```
#[must_use = "set_crc_en returns a modified LoRaPacketParams"]
pub const fn set_crc_en(mut self, en: bool) -> LoRaPacketParams {
self.buf[5] = en as u8;
self
}
/// IQ setup.
///
/// # Example
///
/// Use an inverted IQ setup.
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_invert_iq(true);
/// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x1);
/// ```
#[must_use = "set_invert_iq returns a modified LoRaPacketParams"]
pub const fn set_invert_iq(mut self, invert: bool) -> LoRaPacketParams {
self.buf[6] = invert as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{HeaderType, LoRaPacketParams};
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new()
/// .set_preamble_len(5 * 8)
/// .set_header_type(HeaderType::Fixed)
/// .set_payload_len(64)
/// .set_crc_en(true)
/// .set_invert_iq(true);
///
/// assert_eq!(
/// PKT_PARAMS.as_slice(),
/// &[0x8C, 0x00, 0x28, 0x01, 0x40, 0x01, 0x01]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for LoRaPacketParams {
fn default() -> Self {
Self::new()
}
}
/// Packet parameters for [`set_bpsk_packet_params`].
///
/// [`set_bpsk_packet_params`]: super::SubGhz::set_bpsk_packet_params
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BpskPacketParams {
buf: [u8; 2],
}
impl BpskPacketParams {
/// Create a new `BpskPacketParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BpskPacketParams;
///
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new();
/// assert_eq!(PKT_PARAMS, BpskPacketParams::default());
/// ```
pub const fn new() -> BpskPacketParams {
BpskPacketParams {
buf: [super::OpCode::SetPacketParams as u8, 0x00],
}
}
/// Set the payload length in bytes.
///
/// The length includes preamble, sync word, device ID, and CRC.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BpskPacketParams;
///
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(12);
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 12);
/// ```
#[must_use = "set_payload_len returns a modified BpskPacketParams"]
pub const fn set_payload_len(mut self, len: u8) -> BpskPacketParams {
self.buf[1] = len;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{BpskPacketParams, HeaderType};
///
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(24);
///
/// assert_eq!(PKT_PARAMS.as_slice(), &[0x8C, 24]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for BpskPacketParams {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,282 +0,0 @@
use super::{Ratio, Status};
/// (G)FSK packet status.
///
/// Returned by [`fsk_packet_status`].
///
/// [`fsk_packet_status`]: super::SubGhz::fsk_packet_status
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FskPacketStatus {
buf: [u8; 4],
}
impl From<[u8; 4]> for FskPacketStatus {
fn from(buf: [u8; 4]) -> Self {
FskPacketStatus { buf }
}
}
impl FskPacketStatus {
/// Get the status.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, FskPacketStatus, Status, StatusMode};
///
/// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0];
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
/// let status: Status = pkt_status.status();
/// assert_eq!(status.mode(), Ok(StatusMode::Rx));
/// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn status(&self) -> Status {
Status::from_raw(self.buf[0])
}
/// Returns `true` if a preamble error occurred.
pub const fn preamble_err(&self) -> bool {
(self.buf[1] & (1 << 7)) != 0
}
/// Returns `true` if a synchronization error occurred.
pub const fn sync_err(&self) -> bool {
(self.buf[1] & (1 << 6)) != 0
}
/// Returns `true` if an address error occurred.
pub const fn addr_err(&self) -> bool {
(self.buf[1] & (1 << 5)) != 0
}
/// Returns `true` if an CRC error occurred.
pub const fn crc_err(&self) -> bool {
(self.buf[1] & (1 << 4)) != 0
}
/// Returns `true` if a length error occurred.
pub const fn length_err(&self) -> bool {
(self.buf[1] & (1 << 3)) != 0
}
/// Returns `true` if an abort error occurred.
pub const fn abort_err(&self) -> bool {
(self.buf[1] & (1 << 2)) != 0
}
/// Returns `true` if a packet is received.
pub const fn pkt_received(&self) -> bool {
(self.buf[1] & (1 << 1)) != 0
}
/// Returns `true` when a packet has been sent.
pub const fn pkt_sent(&self) -> bool {
(self.buf[1] & 1) != 0
}
/// Returns `true` if any error occurred.
pub const fn any_err(&self) -> bool {
(self.buf[1] & 0xFC) != 0
}
/// RSSI level when the synchronization address is detected.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::FskPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 80, 0];
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.rssi_sync().to_integer(), -40);
/// ```
pub fn rssi_sync(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[2]), -2)
}
/// Return the RSSI level over the received packet.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::FskPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 0, 100];
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.rssi_avg().to_integer(), -50);
/// ```
pub fn rssi_avg(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[3]), -2)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for FskPacketStatus {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
r#"FskPacketStatus {{
status: {},
preamble_err: {},
sync_err: {},
addr_err: {},
crc_err: {},
length_err: {},
abort_err: {},
pkt_received: {},
pkt_sent: {},
rssi_sync: {},
rssi_avg: {},
}}"#,
self.status(),
self.preamble_err(),
self.sync_err(),
self.addr_err(),
self.crc_err(),
self.length_err(),
self.abort_err(),
self.pkt_received(),
self.pkt_sent(),
self.rssi_sync().to_integer(),
self.rssi_avg().to_integer()
)
}
}
impl core::fmt::Debug for FskPacketStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FskPacketStatus")
.field("status", &self.status())
.field("preamble_err", &self.preamble_err())
.field("sync_err", &self.sync_err())
.field("addr_err", &self.addr_err())
.field("crc_err", &self.crc_err())
.field("length_err", &self.length_err())
.field("abort_err", &self.abort_err())
.field("pkt_received", &self.pkt_received())
.field("pkt_sent", &self.pkt_sent())
.field("rssi_sync", &self.rssi_sync().to_integer())
.field("rssi_avg", &self.rssi_avg().to_integer())
.finish()
}
}
/// (G)FSK packet status.
///
/// Returned by [`lora_packet_status`].
///
/// [`lora_packet_status`]: super::SubGhz::lora_packet_status
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct LoRaPacketStatus {
buf: [u8; 4],
}
impl From<[u8; 4]> for LoRaPacketStatus {
fn from(buf: [u8; 4]) -> Self {
LoRaPacketStatus { buf }
}
}
impl LoRaPacketStatus {
/// Get the status.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, LoRaPacketStatus, Status, StatusMode};
///
/// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// let status: Status = pkt_status.status();
/// assert_eq!(status.mode(), Ok(StatusMode::Rx));
/// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn status(&self) -> Status {
Status::from_raw(self.buf[0])
}
/// Average RSSI level over the received packet.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 80, 0, 0];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.rssi_pkt().to_integer(), -40);
/// ```
pub fn rssi_pkt(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[1]), -2)
}
/// Estimation of SNR over the received packet.
///
/// Units are in dB.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 40, 0];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.snr_pkt().to_integer(), 10);
/// ```
pub fn snr_pkt(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[2]), 4)
}
/// Estimation of RSSI level of the LoRa signal after despreading.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 0, 80];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.signal_rssi_pkt().to_integer(), -40);
/// ```
pub fn signal_rssi_pkt(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[3]), -2)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for LoRaPacketStatus {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
r#"LoRaPacketStatus {{
status: {},
rssi_pkt: {},
snr_pkt: {},
signal_rssi_pkt: {},
}}"#,
self.status(),
self.rssi_pkt().to_integer(),
self.snr_pkt().to_integer(),
self.signal_rssi_pkt().to_integer(),
)
}
}
impl core::fmt::Debug for LoRaPacketStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LoRaPacketStatus")
.field("status", &self.status())
.field("rssi_pkt", &self.rssi_pkt().to_integer())
.field("snr_pkt", &self.snr_pkt().to_integer())
.field("signal_rssi_pkt", &self.signal_rssi_pkt().to_integer())
.finish()
}
}

View File

@ -1,44 +0,0 @@
/// Packet type definition.
///
/// Argument of [`set_packet_type`]
///
/// [`set_packet_type`]: super::SubGhz::set_packet_type
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PacketType {
/// FSK (frequency shift keying) generic packet type.
Fsk = 0,
/// LoRa (long range) packet type.
LoRa = 1,
/// BPSK (binary phase shift keying) packet type.
Bpsk = 2,
/// MSK (minimum shift keying) generic packet type.
Msk = 3,
}
impl PacketType {
/// Create a new `PacketType` from bits.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PacketType;
///
/// assert_eq!(PacketType::from_raw(0), Ok(PacketType::Fsk));
/// assert_eq!(PacketType::from_raw(1), Ok(PacketType::LoRa));
/// assert_eq!(PacketType::from_raw(2), Ok(PacketType::Bpsk));
/// assert_eq!(PacketType::from_raw(3), Ok(PacketType::Msk));
/// // Other values are reserved
/// assert_eq!(PacketType::from_raw(4), Err(4));
/// ```
pub const fn from_raw(bits: u8) -> Result<PacketType, u8> {
match bits {
0 => Ok(PacketType::Fsk),
1 => Ok(PacketType::LoRa),
2 => Ok(PacketType::Bpsk),
3 => Ok(PacketType::Msk),
_ => Err(bits),
}
}
}

View File

@ -1,247 +0,0 @@
/// Generic packet infinite sequence selection.
///
/// Argument of [`PktCtrl::set_inf_seq_sel`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InfSeqSel {
/// Preamble `0x5555`.
Five = 0b00,
/// Preamble `0x0000`.
Zero = 0b01,
/// Preamble `0xFFFF`.
One = 0b10,
/// PRBS9.
Prbs9 = 0b11,
}
impl Default for InfSeqSel {
fn default() -> Self {
InfSeqSel::Five
}
}
/// Generic packet control.
///
/// Argument of [`set_pkt_ctrl`](super::SubGhz::set_pkt_ctrl).
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PktCtrl {
val: u8,
}
impl PktCtrl {
/// Reset value of the packet control register.
pub const RESET: PktCtrl = PktCtrl { val: 0x21 };
/// Create a new [`PktCtrl`] structure from a raw value.
///
/// Reserved bits will be masked.
pub const fn from_raw(raw: u8) -> Self {
Self { val: raw & 0x3F }
}
/// Get the raw value of the [`PktCtrl`] register.
pub const fn as_bits(&self) -> u8 {
self.val
}
/// Generic packet synchronization word detection enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_sync_det_en(true);
/// ```
#[must_use = "set_sync_det_en returns a modified PktCtrl"]
pub const fn set_sync_det_en(mut self, en: bool) -> PktCtrl {
if en {
self.val |= 1 << 5;
} else {
self.val &= !(1 << 5);
}
self
}
/// Returns `true` if generic packet synchronization word detection is
/// enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.sync_det_en(), true);
/// let pc: PktCtrl = pc.set_sync_det_en(false);
/// assert_eq!(pc.sync_det_en(), false);
/// let pc: PktCtrl = pc.set_sync_det_en(true);
/// assert_eq!(pc.sync_det_en(), true);
/// ```
pub const fn sync_det_en(&self) -> bool {
self.val & (1 << 5) != 0
}
/// Generic packet continuous transmit enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_cont_tx_en(true);
/// ```
#[must_use = "set_cont_tx_en returns a modified PktCtrl"]
pub const fn set_cont_tx_en(mut self, en: bool) -> PktCtrl {
if en {
self.val |= 1 << 4;
} else {
self.val &= !(1 << 4);
}
self
}
/// Returns `true` if generic packet continuous transmit is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.cont_tx_en(), false);
/// let pc: PktCtrl = pc.set_cont_tx_en(true);
/// assert_eq!(pc.cont_tx_en(), true);
/// let pc: PktCtrl = pc.set_cont_tx_en(false);
/// assert_eq!(pc.cont_tx_en(), false);
/// ```
pub const fn cont_tx_en(&self) -> bool {
self.val & (1 << 4) != 0
}
/// Set the continuous sequence type.
#[must_use = "set_inf_seq_sel returns a modified PktCtrl"]
pub const fn set_inf_seq_sel(mut self, sel: InfSeqSel) -> PktCtrl {
self.val &= !(0b11 << 2);
self.val |= (sel as u8) << 2;
self
}
/// Get the continuous sequence type.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{InfSeqSel, PktCtrl};
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Zero);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Zero);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::One);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::One);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Prbs9);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Prbs9);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Five);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five);
/// ```
pub const fn inf_seq_sel(&self) -> InfSeqSel {
match (self.val >> 2) & 0b11 {
0b00 => InfSeqSel::Five,
0b01 => InfSeqSel::Zero,
0b10 => InfSeqSel::One,
_ => InfSeqSel::Prbs9,
}
}
/// Enable infinite sequence generation.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_inf_seq_en(true);
/// ```
#[must_use = "set_inf_seq_en returns a modified PktCtrl"]
pub const fn set_inf_seq_en(mut self, en: bool) -> PktCtrl {
if en {
self.val |= 1 << 1;
} else {
self.val &= !(1 << 1);
}
self
}
/// Returns `true` if infinite sequence generation is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.inf_seq_en(), false);
/// let pc: PktCtrl = pc.set_inf_seq_en(true);
/// assert_eq!(pc.inf_seq_en(), true);
/// let pc: PktCtrl = pc.set_inf_seq_en(false);
/// assert_eq!(pc.inf_seq_en(), false);
/// ```
pub const fn inf_seq_en(&self) -> bool {
self.val & (1 << 1) != 0
}
/// Set the value of bit-8 (9th bit) for generic packet whitening.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_whitening_init(true);
/// ```
#[must_use = "set_whitening_init returns a modified PktCtrl"]
pub const fn set_whitening_init(mut self, val: bool) -> PktCtrl {
if val {
self.val |= 1;
} else {
self.val &= !1;
}
self
}
/// Returns `true` if bit-8 of the generic packet whitening is set.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.whitening_init(), true);
/// let pc: PktCtrl = pc.set_whitening_init(false);
/// assert_eq!(pc.whitening_init(), false);
/// let pc: PktCtrl = pc.set_whitening_init(true);
/// assert_eq!(pc.whitening_init(), true);
/// ```
pub const fn whitening_init(&self) -> bool {
self.val & 0b1 != 0
}
}
impl From<PktCtrl> for u8 {
fn from(pc: PktCtrl) -> Self {
pc.val
}
}
impl Default for PktCtrl {
fn default() -> Self {
Self::RESET
}
}

View File

@ -1,27 +0,0 @@
/// RX gain power modes.
///
/// Argument of [`set_rx_gain`].
///
/// [`set_rx_gain`]: super::SubGhz::set_rx_gain
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PMode {
/// Power saving mode.
///
/// Reduces sensitivity.
#[allow(clippy::identity_op)]
PowerSaving = (0x25 << 2) | 0b00,
/// Boost mode level 1.
///
/// Improves sensitivity at detriment of power consumption.
Boost1 = (0x25 << 2) | 0b01,
/// Boost mode level 2.
///
/// Improves a set further sensitivity at detriment of power consumption.
Boost2 = (0x25 << 2) | 0b10,
/// Boost mode.
///
/// Best receiver sensitivity.
Boost = (0x25 << 2) | 0b11,
}

View File

@ -1,160 +0,0 @@
/// Power-supply current limit.
///
/// Argument of [`PwrCtrl::set_current_lim`].
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CurrentLim {
/// 25 mA
Milli25 = 0x0,
/// 50 mA (default)
Milli50 = 0x1,
/// 100 mA
Milli100 = 0x2,
/// 200 mA
Milli200 = 0x3,
}
impl CurrentLim {
/// Get the SMPS drive value as milliamps.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CurrentLim;
///
/// assert_eq!(CurrentLim::Milli25.as_milliamps(), 25);
/// assert_eq!(CurrentLim::Milli50.as_milliamps(), 50);
/// assert_eq!(CurrentLim::Milli100.as_milliamps(), 100);
/// assert_eq!(CurrentLim::Milli200.as_milliamps(), 200);
/// ```
pub const fn as_milliamps(&self) -> u8 {
match self {
CurrentLim::Milli25 => 25,
CurrentLim::Milli50 => 50,
CurrentLim::Milli100 => 100,
CurrentLim::Milli200 => 200,
}
}
}
impl Default for CurrentLim {
fn default() -> Self {
CurrentLim::Milli50
}
}
/// Power control.
///
/// Argument of [`set_bit_sync`](super::SubGhz::set_bit_sync).
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PwrCtrl {
val: u8,
}
impl PwrCtrl {
/// Power control register reset value.
pub const RESET: PwrCtrl = PwrCtrl { val: 0x50 };
/// Create a new [`PwrCtrl`] 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 [`PwrCtrl`] register.
pub const fn as_bits(&self) -> u8 {
self.val
}
/// Set the current limiter enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PwrCtrl;
///
/// const PWR_CTRL: PwrCtrl = PwrCtrl::RESET.set_current_lim_en(true);
/// # assert_eq!(u8::from(PWR_CTRL), 0x50u8);
/// ```
#[must_use = "set_current_lim_en returns a modified PwrCtrl"]
pub const fn set_current_lim_en(mut self, en: bool) -> PwrCtrl {
if en {
self.val |= 1 << 6;
} else {
self.val &= !(1 << 6);
}
self
}
/// Returns `true` if current limiting is enabled
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PwrCtrl;
///
/// let pc: PwrCtrl = PwrCtrl::RESET;
/// assert_eq!(pc.current_limit_en(), true);
/// let pc: PwrCtrl = pc.set_current_lim_en(false);
/// assert_eq!(pc.current_limit_en(), false);
/// let pc: PwrCtrl = pc.set_current_lim_en(true);
/// assert_eq!(pc.current_limit_en(), true);
/// ```
pub const fn current_limit_en(&self) -> bool {
self.val & (1 << 6) != 0
}
/// Set the current limit.
#[must_use = "set_current_lim returns a modified PwrCtrl"]
pub const fn set_current_lim(mut self, lim: CurrentLim) -> PwrCtrl {
self.val &= !(0x30);
self.val |= (lim as u8) << 4;
self
}
/// Get the current limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CurrentLim, PwrCtrl};
///
/// let pc: PwrCtrl = PwrCtrl::RESET;
/// assert_eq!(pc.current_lim(), CurrentLim::Milli50);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli25);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli25);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli50);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli50);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli100);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli100);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli200);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli200);
/// ```
pub const fn current_lim(&self) -> CurrentLim {
match (self.val >> 4) & 0b11 {
0x0 => CurrentLim::Milli25,
0x1 => CurrentLim::Milli50,
0x2 => CurrentLim::Milli100,
_ => CurrentLim::Milli200,
}
}
}
impl From<PwrCtrl> for u8 {
fn from(bs: PwrCtrl) -> Self {
bs.val
}
}
impl Default for PwrCtrl {
fn default() -> Self {
Self::RESET
}
}

View File

@ -1,18 +0,0 @@
/// Radio power supply selection.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum RegMode {
/// Linear dropout regulator
Ldo = 0b0,
/// Switch mode power supply.
///
/// Used in standby with HSE32, FS, RX, and TX modes.
Smps = 0b1,
}
impl Default for RegMode {
fn default() -> Self {
RegMode::Ldo
}
}

View File

@ -1,135 +0,0 @@
/// RF frequency structure.
///
/// Argument of [`set_rf_frequency`].
///
/// [`set_rf_frequency`]: super::SubGhz::set_rf_frequency
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RfFreq {
buf: [u8; 5],
}
impl RfFreq {
/// 915MHz, often used in Australia and North America.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F915.freq(), 915_000_000);
/// ```
pub const F915: RfFreq = RfFreq::from_raw(0x39_30_00_00);
/// 868MHz, often used in Europe.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F868.freq(), 868_000_000);
/// ```
pub const F868: RfFreq = RfFreq::from_raw(0x36_40_00_00);
/// 433MHz, often used in Europe.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F433.freq(), 433_000_000);
/// ```
pub const F433: RfFreq = RfFreq::from_raw(0x1B_10_00_00);
/// Create a new `RfFreq` from a raw bit value.
///
/// The equation used to get the PLL frequency from the raw bits is:
///
/// RF<sub>PLL</sub> = 32e6 × bits / 2<sup>25</sup>
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// const FREQ: RfFreq = RfFreq::from_raw(0x39300000);
/// assert_eq!(FREQ, RfFreq::F915);
/// ```
pub const fn from_raw(bits: u32) -> RfFreq {
RfFreq {
buf: [
super::OpCode::SetRfFrequency as u8,
((bits >> 24) & 0xFF) as u8,
((bits >> 16) & 0xFF) as u8,
((bits >> 8) & 0xFF) as u8,
(bits & 0xFF) as u8,
],
}
}
/// Create a new `RfFreq` from a PLL frequency.
///
/// The equation used to get the raw bits from the PLL frequency is:
///
/// bits = RF<sub>PLL</sub> * 2<sup>25</sup> / 32e6
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// const FREQ: RfFreq = RfFreq::from_frequency(915_000_000);
/// assert_eq!(FREQ, RfFreq::F915);
/// ```
pub const fn from_frequency(freq: u32) -> RfFreq {
Self::from_raw((((freq as u64) * (1 << 25)) / 32_000_000) as u32)
}
// Get the frequency bit value.
const fn as_bits(&self) -> u32 {
((self.buf[1] as u32) << 24) | ((self.buf[2] as u32) << 16) | ((self.buf[3] as u32) << 8) | (self.buf[4] as u32)
}
/// Get the actual frequency.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::from_raw(0x39300000).freq(), 915_000_000);
/// ```
pub fn freq(&self) -> u32 {
(32_000_000 * (self.as_bits() as u64) / (1 << 25)) as u32
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F915.as_slice(), &[0x86, 0x39, 0x30, 0x00, 0x00]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
#[cfg(test)]
mod test {
use super::RfFreq;
#[test]
fn max() {
assert_eq!(RfFreq::from_raw(u32::MAX).freq(), 4_095_999_999);
}
#[test]
fn min() {
assert_eq!(RfFreq::from_raw(u32::MIN).freq(), 0);
}
}

View File

@ -1,21 +0,0 @@
/// Receiver event which stops the RX timeout timer.
///
/// Used by [`set_rx_timeout_stop`].
///
/// [`set_rx_timeout_stop`]: super::SubGhz::set_rx_timeout_stop
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum RxTimeoutStop {
/// Receive timeout stopped on synchronization word detection in generic
/// packet mode or header detection in LoRa packet mode.
Sync = 0b0,
/// Receive timeout stopped on preamble detection.
Preamble = 0b1,
}
impl From<RxTimeoutStop> for u8 {
fn from(rx_ts: RxTimeoutStop) -> Self {
rx_ts as u8
}
}

View File

@ -1,107 +0,0 @@
/// Startup configurations when exiting sleep mode.
///
/// Argument of [`SleepCfg::set_startup`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Startup {
/// Cold startup when exiting Sleep mode, configuration registers reset.
Cold = 0,
/// Warm startup when exiting Sleep mode,
/// configuration registers kept in retention.
///
/// **Note:** Only the configuration of the activated modem,
/// before going to sleep mode, is retained.
/// The configuration of the other modes is lost and must be re-configured
/// when exiting sleep mode.
Warm = 1,
}
impl Default for Startup {
fn default() -> Self {
Startup::Warm
}
}
/// Sleep configuration.
///
/// Argument of [`set_sleep`].
///
/// [`set_sleep`]: super::SubGhz::set_sleep
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SleepCfg(u8);
impl SleepCfg {
/// Create a new `SleepCfg` structure.
///
/// This is the same as `default`, but in a `const` function.
///
/// The defaults are a warm startup, with RTC wakeup enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::SleepCfg;
///
/// const SLEEP_CFG: SleepCfg = SleepCfg::new();
/// assert_eq!(SLEEP_CFG, SleepCfg::default());
/// # assert_eq!(u8::from(SLEEP_CFG), 0b101);
/// ```
pub const fn new() -> SleepCfg {
SleepCfg(0).set_startup(Startup::Warm).set_rtc_wakeup_en(true)
}
/// Set the startup mode.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{SleepCfg, Startup};
///
/// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_startup(Startup::Cold);
/// # assert_eq!(u8::from(SLEEP_CFG), 0b001);
/// # assert_eq!(u8::from(SLEEP_CFG.set_startup(Startup::Warm)), 0b101);
/// ```
pub const fn set_startup(mut self, startup: Startup) -> SleepCfg {
if startup as u8 == 1 {
self.0 |= 1 << 2
} else {
self.0 &= !(1 << 2)
}
self
}
/// Set the RTC wakeup enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::SleepCfg;
///
/// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_rtc_wakeup_en(false);
/// # assert_eq!(u8::from(SLEEP_CFG), 0b100);
/// # assert_eq!(u8::from(SLEEP_CFG.set_rtc_wakeup_en(true)), 0b101);
/// ```
#[must_use = "set_rtc_wakeup_en returns a modified SleepCfg"]
pub const fn set_rtc_wakeup_en(mut self, en: bool) -> SleepCfg {
if en {
self.0 |= 0b1
} else {
self.0 &= !0b1
}
self
}
}
impl From<SleepCfg> for u8 {
fn from(sc: SleepCfg) -> Self {
sc.0
}
}
impl Default for SleepCfg {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,45 +0,0 @@
/// SMPS maximum drive capability.
///
/// Argument of [`set_smps_drv`](super::SubGhz::set_smps_drv).
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum SmpsDrv {
/// 20 mA
Milli20 = 0x0,
/// 40 mA
Milli40 = 0x1,
/// 60 mA
Milli60 = 0x2,
/// 100 mA (default)
Milli100 = 0x3,
}
impl SmpsDrv {
/// Get the SMPS drive value as milliamps.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::SmpsDrv;
///
/// assert_eq!(SmpsDrv::Milli20.as_milliamps(), 20);
/// assert_eq!(SmpsDrv::Milli40.as_milliamps(), 40);
/// assert_eq!(SmpsDrv::Milli60.as_milliamps(), 60);
/// assert_eq!(SmpsDrv::Milli100.as_milliamps(), 100);
/// ```
pub const fn as_milliamps(&self) -> u8 {
match self {
SmpsDrv::Milli20 => 20,
SmpsDrv::Milli40 => 40,
SmpsDrv::Milli60 => 60,
SmpsDrv::Milli100 => 100,
}
}
}
impl Default for SmpsDrv {
fn default() -> Self {
SmpsDrv::Milli100
}
}

View File

@ -1,20 +0,0 @@
/// Clock in standby mode.
///
/// Used by [`set_standby`].
///
/// [`set_standby`]: super::SubGhz::set_standby
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum StandbyClk {
/// RC 13 MHz used in standby mode.
Rc = 0b0,
/// HSE32 used in standby mode.
Hse = 0b1,
}
impl From<StandbyClk> for u8 {
fn from(sc: StandbyClk) -> Self {
sc as u8
}
}

View File

@ -1,184 +0,0 @@
use super::Status;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LoRaStats;
impl LoRaStats {
pub const fn new() -> Self {
Self {}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FskStats;
impl FskStats {
pub const fn new() -> Self {
Self {}
}
}
/// Packet statistics.
///
/// Returned by [`fsk_stats`] and [`lora_stats`].
///
/// [`fsk_stats`]: super::SubGhz::fsk_stats
/// [`lora_stats`]: super::SubGhz::lora_stats
#[derive(Eq, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Stats<ModType> {
status: Status,
pkt_rx: u16,
pkt_crc: u16,
pkt_len_or_hdr_err: u16,
ty: ModType,
}
impl<ModType> Stats<ModType> {
const fn from_buf(buf: [u8; 7], ty: ModType) -> Stats<ModType> {
Stats {
status: Status::from_raw(buf[0]),
pkt_rx: u16::from_be_bytes([buf[1], buf[2]]),
pkt_crc: u16::from_be_bytes([buf[3], buf[4]]),
pkt_len_or_hdr_err: u16::from_be_bytes([buf[5], buf[6]]),
ty,
}
}
/// Get the radio status returned with the packet statistics.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, FskStats, Stats, StatusMode};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// assert_eq!(stats.status().mode(), Ok(StatusMode::Rx));
/// assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn status(&self) -> Status {
self.status
}
/// Number of packets received.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{FskStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 3, 0, 0, 0, 0];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// assert_eq!(stats.pkt_rx(), 3);
/// ```
pub const fn pkt_rx(&self) -> u16 {
self.pkt_rx
}
/// Number of packets received with a payload CRC error
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{LoRaStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 1, 0, 0];
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
/// assert_eq!(stats.pkt_crc(), 1);
/// ```
pub const fn pkt_crc(&self) -> u16 {
self.pkt_crc
}
}
impl Stats<FskStats> {
/// Create a new FSK packet statistics structure from a raw buffer.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{FskStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// ```
pub const fn from_raw_fsk(buf: [u8; 7]) -> Stats<FskStats> {
Self::from_buf(buf, FskStats::new())
}
/// Number of packets received with a payload length error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{FskStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// assert_eq!(stats.pkt_len_err(), 1);
/// ```
pub const fn pkt_len_err(&self) -> u16 {
self.pkt_len_or_hdr_err
}
}
impl Stats<LoRaStats> {
/// Create a new LoRa packet statistics structure from a raw buffer.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{LoRaStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
/// ```
pub const fn from_raw_lora(buf: [u8; 7]) -> Stats<LoRaStats> {
Self::from_buf(buf, LoRaStats::new())
}
/// Number of packets received with a header CRC error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{LoRaStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1];
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
/// assert_eq!(stats.pkt_hdr_err(), 1);
/// ```
pub const fn pkt_hdr_err(&self) -> u16 {
self.pkt_len_or_hdr_err
}
}
impl core::fmt::Debug for Stats<FskStats> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Stats")
.field("status", &self.status())
.field("pkt_rx", &self.pkt_rx())
.field("pkt_crc", &self.pkt_crc())
.field("pkt_len_err", &self.pkt_len_err())
.finish()
}
}
#[cfg(test)]
mod test {
use super::super::{CmdStatus, LoRaStats, Stats, StatusMode};
#[test]
fn mixed() {
let example_data_from_radio: [u8; 7] = [0x54, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
assert_eq!(stats.status().mode(), Ok(StatusMode::Rx));
assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable));
assert_eq!(stats.pkt_rx(), 0x0102);
assert_eq!(stats.pkt_crc(), 0x0304);
assert_eq!(stats.pkt_hdr_err(), 0x0506);
}
}

View File

@ -1,197 +0,0 @@
/// sub-GHz radio operating mode.
///
/// See `Get_Status` under section 5.8.5 "Communication status information commands"
/// in the reference manual.
///
/// This is returned by [`Status::mode`].
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum StatusMode {
/// Standby mode with RC 13MHz.
StandbyRc = 0x2,
/// Standby mode with HSE32.
StandbyHse = 0x3,
/// Frequency Synthesis mode.
Fs = 0x4,
/// Receive mode.
Rx = 0x5,
/// Transmit mode.
Tx = 0x6,
}
impl StatusMode {
/// Create a new `StatusMode` from bits.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::StatusMode;
///
/// assert_eq!(StatusMode::from_raw(0x2), Ok(StatusMode::StandbyRc));
/// assert_eq!(StatusMode::from_raw(0x3), Ok(StatusMode::StandbyHse));
/// assert_eq!(StatusMode::from_raw(0x4), Ok(StatusMode::Fs));
/// assert_eq!(StatusMode::from_raw(0x5), Ok(StatusMode::Rx));
/// assert_eq!(StatusMode::from_raw(0x6), Ok(StatusMode::Tx));
/// // Other values are reserved
/// assert_eq!(StatusMode::from_raw(0), Err(0));
/// ```
pub const fn from_raw(bits: u8) -> Result<Self, u8> {
match bits {
0x2 => Ok(StatusMode::StandbyRc),
0x3 => Ok(StatusMode::StandbyHse),
0x4 => Ok(StatusMode::Fs),
0x5 => Ok(StatusMode::Rx),
0x6 => Ok(StatusMode::Tx),
_ => Err(bits),
}
}
}
/// Command status.
///
/// See `Get_Status` under section 5.8.5 "Communication status information commands"
/// in the reference manual.
///
/// This is returned by [`Status::cmd`].
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CmdStatus {
/// Data available to host.
///
/// Packet received successfully and data can be retrieved.
Avaliable = 0x2,
/// Command time out.
///
/// Command took too long to complete triggering a sub-GHz radio watchdog
/// timeout.
Timeout = 0x3,
/// Command processing error.
///
/// Invalid opcode or incorrect number of parameters.
ProcessingError = 0x4,
/// Command execution failure.
///
/// Command successfully received but cannot be executed at this time,
/// requested operating mode cannot be entered or requested data cannot be
/// sent.
ExecutionFailure = 0x5,
/// Transmit command completed.
///
/// Current packet transmission completed.
Complete = 0x6,
}
impl CmdStatus {
/// Create a new `CmdStatus` from bits.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CmdStatus;
///
/// assert_eq!(CmdStatus::from_raw(0x2), Ok(CmdStatus::Avaliable));
/// assert_eq!(CmdStatus::from_raw(0x3), Ok(CmdStatus::Timeout));
/// assert_eq!(CmdStatus::from_raw(0x4), Ok(CmdStatus::ProcessingError));
/// assert_eq!(CmdStatus::from_raw(0x5), Ok(CmdStatus::ExecutionFailure));
/// assert_eq!(CmdStatus::from_raw(0x6), Ok(CmdStatus::Complete));
/// // Other values are reserved
/// assert_eq!(CmdStatus::from_raw(0), Err(0));
/// ```
pub const fn from_raw(bits: u8) -> Result<Self, u8> {
match bits {
0x2 => Ok(CmdStatus::Avaliable),
0x3 => Ok(CmdStatus::Timeout),
0x4 => Ok(CmdStatus::ProcessingError),
0x5 => Ok(CmdStatus::ExecutionFailure),
0x6 => Ok(CmdStatus::Complete),
_ => Err(bits),
}
}
}
/// Radio status.
///
/// This is returned by [`status`].
///
/// [`status`]: super::SubGhz::status
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Status(u8);
impl From<u8> for Status {
fn from(x: u8) -> Self {
Status(x)
}
}
impl From<Status> for u8 {
fn from(x: Status) -> Self {
x.0
}
}
impl Status {
/// Create a new `Status` from a raw `u8` value.
///
/// This is the same as `Status::from(u8)`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, Status, StatusMode};
///
/// const STATUS: Status = Status::from_raw(0x54_u8);
/// assert_eq!(STATUS.mode(), Ok(StatusMode::Rx));
/// assert_eq!(STATUS.cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn from_raw(value: u8) -> Status {
Status(value)
}
/// sub-GHz radio operating mode.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{Status, StatusMode};
///
/// let status: Status = 0xACu8.into();
/// assert_eq!(status.mode(), Ok(StatusMode::StandbyRc));
/// ```
pub const fn mode(&self) -> Result<StatusMode, u8> {
StatusMode::from_raw((self.0 >> 4) & 0b111)
}
/// Command status.
///
/// This method frequently returns reserved values such as `Err(1)`.
/// ST support has confirmed that this is normal and should be ignored.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, Status};
///
/// let status: Status = 0xACu8.into();
/// assert_eq!(status.cmd(), Ok(CmdStatus::Complete));
/// ```
pub const fn cmd(&self) -> Result<CmdStatus, u8> {
CmdStatus::from_raw((self.0 >> 1) & 0b111)
}
}
impl core::fmt::Debug for Status {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Status")
.field("mode", &self.mode())
.field("cmd", &self.cmd())
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Status {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "Status {{ mode: {}, cmd: {} }}", self.mode(), self.cmd())
}
}

View File

@ -1,170 +0,0 @@
use super::Timeout;
/// TCXO trim.
///
/// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be at
/// least + 200 mV higher than the selected `TcxoTrim` voltage level.
///
/// Used by [`TcxoMode`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum TcxoTrim {
/// 1.6V
Volts1pt6 = 0x0,
/// 1.7V
Volts1pt7 = 0x1,
/// 1.8V
Volts1pt8 = 0x2,
/// 2.2V
Volts2pt2 = 0x3,
/// 2.4V
Volts2pt4 = 0x4,
/// 2.7V
Volts2pt7 = 0x5,
/// 3.0V
Volts3pt0 = 0x6,
/// 3.3V
Volts3pt3 = 0x7,
}
impl core::fmt::Display for TcxoTrim {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TcxoTrim::Volts1pt6 => write!(f, "1.6V"),
TcxoTrim::Volts1pt7 => write!(f, "1.7V"),
TcxoTrim::Volts1pt8 => write!(f, "1.8V"),
TcxoTrim::Volts2pt2 => write!(f, "2.2V"),
TcxoTrim::Volts2pt4 => write!(f, "2.4V"),
TcxoTrim::Volts2pt7 => write!(f, "2.7V"),
TcxoTrim::Volts3pt0 => write!(f, "3.0V"),
TcxoTrim::Volts3pt3 => write!(f, "3.3V"),
}
}
}
impl TcxoTrim {
/// Get the value of the TXCO trim in millivolts.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::TcxoTrim;
///
/// assert_eq!(TcxoTrim::Volts1pt6.as_millivolts(), 1600);
/// assert_eq!(TcxoTrim::Volts1pt7.as_millivolts(), 1700);
/// assert_eq!(TcxoTrim::Volts1pt8.as_millivolts(), 1800);
/// assert_eq!(TcxoTrim::Volts2pt2.as_millivolts(), 2200);
/// assert_eq!(TcxoTrim::Volts2pt4.as_millivolts(), 2400);
/// assert_eq!(TcxoTrim::Volts2pt7.as_millivolts(), 2700);
/// assert_eq!(TcxoTrim::Volts3pt0.as_millivolts(), 3000);
/// assert_eq!(TcxoTrim::Volts3pt3.as_millivolts(), 3300);
/// ```
pub const fn as_millivolts(&self) -> u16 {
match self {
TcxoTrim::Volts1pt6 => 1600,
TcxoTrim::Volts1pt7 => 1700,
TcxoTrim::Volts1pt8 => 1800,
TcxoTrim::Volts2pt2 => 2200,
TcxoTrim::Volts2pt4 => 2400,
TcxoTrim::Volts2pt7 => 2700,
TcxoTrim::Volts3pt0 => 3000,
TcxoTrim::Volts3pt3 => 3300,
}
}
}
/// TCXO trim and HSE32 ready timeout.
///
/// Argument of [`set_tcxo_mode`].
///
/// [`set_tcxo_mode`]: super::SubGhz::set_tcxo_mode
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TcxoMode {
buf: [u8; 5],
}
impl TcxoMode {
/// Create a new `TcxoMode` struct.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::TcxoMode;
///
/// const TCXO_MODE: TcxoMode = TcxoMode::new();
/// ```
pub const fn new() -> TcxoMode {
TcxoMode {
buf: [super::OpCode::SetTcxoMode as u8, 0x00, 0x00, 0x00, 0x00],
}
}
/// Set the TCXO trim.
///
/// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be
/// at least + 200 mV higher than the selected `TcxoTrim` voltage level.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim};
///
/// const TCXO_MODE: TcxoMode = TcxoMode::new().set_txco_trim(TcxoTrim::Volts1pt6);
/// # assert_eq!(TCXO_MODE.as_slice()[1], 0x00);
/// ```
#[must_use = "set_txco_trim returns a modified TcxoMode"]
pub const fn set_txco_trim(mut self, tcxo_trim: TcxoTrim) -> TcxoMode {
self.buf[1] = tcxo_trim as u8;
self
}
/// Set the ready timeout duration.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{TcxoMode, Timeout};
///
/// // 15.625 ms timeout
/// const TIMEOUT: Timeout = Timeout::from_duration_sat(Duration::from_millis(15_625));
/// const TCXO_MODE: TcxoMode = TcxoMode::new().set_timeout(TIMEOUT);
/// # assert_eq!(TCXO_MODE.as_slice()[2], 0x0F);
/// # assert_eq!(TCXO_MODE.as_slice()[3], 0x42);
/// # assert_eq!(TCXO_MODE.as_slice()[4], 0x40);
/// ```
#[must_use = "set_timeout returns a modified TcxoMode"]
pub const fn set_timeout(mut self, timeout: Timeout) -> TcxoMode {
let timeout_bits: u32 = timeout.into_bits();
self.buf[2] = ((timeout_bits >> 16) & 0xFF) as u8;
self.buf[3] = ((timeout_bits >> 8) & 0xFF) as u8;
self.buf[4] = (timeout_bits & 0xFF) as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim, Timeout};
///
/// const TCXO_MODE: TcxoMode = TcxoMode::new()
/// .set_txco_trim(TcxoTrim::Volts1pt7)
/// .set_timeout(Timeout::from_raw(0x123456));
/// assert_eq!(TCXO_MODE.as_slice(), &[0x97, 0x1, 0x12, 0x34, 0x56]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for TcxoMode {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,492 +0,0 @@
use core::time::Duration;
use super::ValueError;
const fn abs_diff(a: u64, b: u64) -> u64 {
if a > b {
a - b
} else {
b - a
}
}
/// Timeout argument.
///
/// This is used by:
/// * [`set_rx`]
/// * [`set_tx`]
/// * [`TcxoMode`]
///
/// Each timeout has 3 bytes, with a resolution of 15.625µs per bit, giving a
/// range of 0s to 262.143984375s.
///
/// [`set_rx`]: super::SubGhz::set_rx
/// [`set_tx`]: super::SubGhz::set_tx
/// [`TcxoMode`]: super::TcxoMode
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Timeout {
bits: u32,
}
impl Timeout {
const BITS_PER_MILLI: u32 = 64; // 1e-3 / 15.625e-6
const BITS_PER_SEC: u32 = 64_000; // 1 / 15.625e-6
/// Disable the timeout (0s timeout).
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const TIMEOUT: Timeout = Timeout::DISABLED;
/// assert_eq!(TIMEOUT.as_duration(), Duration::from_secs(0));
/// ```
pub const DISABLED: Timeout = Timeout { bits: 0x0 };
/// Minimum timeout, 15.625µs.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const TIMEOUT: Timeout = Timeout::MIN;
/// assert_eq!(TIMEOUT.into_bits(), 1);
/// ```
pub const MIN: Timeout = Timeout { bits: 1 };
/// Maximum timeout, 262.143984375s.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const TIMEOUT: Timeout = Timeout::MAX;
/// assert_eq!(TIMEOUT.as_duration(), Duration::from_nanos(262_143_984_375));
/// ```
pub const MAX: Timeout = Timeout { bits: 0x00FF_FFFF };
/// Timeout resolution in nanoseconds, 15.625µs.
pub const RESOLUTION_NANOS: u16 = 15_625;
/// Timeout resolution, 15.625µs.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(
/// Timeout::RESOLUTION.as_nanos(),
/// Timeout::RESOLUTION_NANOS as u128
/// );
/// ```
pub const RESOLUTION: Duration = Duration::from_nanos(Self::RESOLUTION_NANOS as u64);
/// Create a new timeout from a [`Duration`].
///
/// This will return the nearest timeout value possible, or a
/// [`ValueError`] if the value is out of bounds.
///
/// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout
/// construction.
/// This is not _that_ useful right now, it is simply future proofing for a
/// time when `Result::unwrap` is available for `const fn`.
///
/// # Example
///
/// Value within bounds:
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{Timeout, ValueError};
///
/// const MIN: Duration = Timeout::RESOLUTION;
/// assert_eq!(Timeout::from_duration(MIN).unwrap(), Timeout::MIN);
/// ```
///
/// Value too low:
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{Timeout, ValueError};
///
/// const LOWER_LIMIT_NANOS: u128 = 7813;
/// const TOO_LOW_NANOS: u128 = LOWER_LIMIT_NANOS - 1;
/// const TOO_LOW_DURATION: Duration = Duration::from_nanos(TOO_LOW_NANOS as u64);
/// assert_eq!(
/// Timeout::from_duration(TOO_LOW_DURATION),
/// Err(ValueError::too_low(TOO_LOW_NANOS, LOWER_LIMIT_NANOS))
/// );
/// ```
///
/// Value too high:
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{Timeout, ValueError};
///
/// const UPPER_LIMIT_NANOS: u128 = Timeout::MAX.as_nanos() as u128 + 7812;
/// const TOO_HIGH_NANOS: u128 = UPPER_LIMIT_NANOS + 1;
/// const TOO_HIGH_DURATION: Duration = Duration::from_nanos(TOO_HIGH_NANOS as u64);
/// assert_eq!(
/// Timeout::from_duration(TOO_HIGH_DURATION),
/// Err(ValueError::too_high(TOO_HIGH_NANOS, UPPER_LIMIT_NANOS))
/// );
/// ```
pub const fn from_duration(duration: Duration) -> Result<Timeout, ValueError<u128>> {
// at the time of development many methods in
// `core::Duration` were not `const fn`, which leads to the hacks
// you see here.
let nanos: u128 = duration.as_nanos();
const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128 + (Timeout::RESOLUTION_NANOS as u128) / 2;
const LOWER_LIMIT: u128 = (((Timeout::RESOLUTION_NANOS as u128) + 1) / 2) as u128;
if nanos > UPPER_LIMIT {
Err(ValueError::too_high(nanos, UPPER_LIMIT))
} else if nanos < LOWER_LIMIT {
Err(ValueError::too_low(nanos, LOWER_LIMIT))
} else {
// safe to truncate here because of previous bounds check.
let duration_nanos: u64 = nanos as u64;
let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64);
let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64);
let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32);
let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32);
let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos);
let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos);
if error_ceil < error_floor {
Ok(timeout_ceil)
} else {
Ok(timeout_floor)
}
}
}
/// Create a new timeout from a [`Duration`].
///
/// This will return the nearest timeout value possible, saturating at the
/// limits.
///
/// This is an expensive function to call outside of `const` contexts.
/// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout
/// construction.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const DURATION_MAX_NS: u64 = 262_143_984_376;
///
/// assert_eq!(
/// Timeout::from_duration_sat(Duration::from_millis(0)),
/// Timeout::MIN
/// );
/// assert_eq!(
/// Timeout::from_duration_sat(Duration::from_nanos(DURATION_MAX_NS)),
/// Timeout::MAX
/// );
/// assert_eq!(
/// Timeout::from_duration_sat(Timeout::RESOLUTION).into_bits(),
/// 1
/// );
/// ```
pub const fn from_duration_sat(duration: Duration) -> Timeout {
// at the time of development many methods in
// `core::Duration` were not `const fn`, which leads to the hacks
// you see here.
let nanos: u128 = duration.as_nanos();
const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128;
if nanos > UPPER_LIMIT {
Timeout::MAX
} else if nanos < (Timeout::RESOLUTION_NANOS as u128) {
Timeout::from_raw(1)
} else {
// safe to truncate here because of previous bounds check.
let duration_nanos: u64 = duration.as_nanos() as u64;
let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64);
let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64);
let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32);
let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32);
let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos);
let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos);
if error_ceil < error_floor {
timeout_ceil
} else {
timeout_floor
}
}
}
/// Create a new timeout from a milliseconds value.
///
/// This will round towards zero and saturate at the limits.
///
/// This is the preferred method to call when you need to generate a
/// timeout value at runtime.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_millis_sat(0), Timeout::MIN);
/// assert_eq!(Timeout::from_millis_sat(262_144), Timeout::MAX);
/// assert_eq!(Timeout::from_millis_sat(1).into_bits(), 64);
/// ```
pub const fn from_millis_sat(millis: u32) -> Timeout {
if millis == 0 {
Timeout::MIN
} else if millis >= 262_144 {
Timeout::MAX
} else {
Timeout::from_raw(millis * Self::BITS_PER_MILLI)
}
}
/// Create a timeout from raw bits, where each bit has the resolution of
/// [`Timeout::RESOLUTION`].
///
/// **Note:** Only the first 24 bits of the `u32` are used, the `bits`
/// argument will be masked.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_raw(u32::MAX), Timeout::MAX);
/// assert_eq!(Timeout::from_raw(0x00_FF_FF_FF), Timeout::MAX);
/// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION);
/// assert_eq!(Timeout::from_raw(0), Timeout::DISABLED);
/// ```
pub const fn from_raw(bits: u32) -> Timeout {
Timeout {
bits: bits & 0x00FF_FFFF,
}
}
/// Get the timeout as nanoseconds.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_nanos(), 262_143_984_375);
/// assert_eq!(Timeout::DISABLED.as_nanos(), 0);
/// assert_eq!(Timeout::from_raw(1).as_nanos(), 15_625);
/// assert_eq!(Timeout::from_raw(64_000).as_nanos(), 1_000_000_000);
/// ```
pub const fn as_nanos(&self) -> u64 {
(self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64)
}
/// Get the timeout as microseconds, rounding towards zero.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_micros(), 262_143_984);
/// assert_eq!(Timeout::DISABLED.as_micros(), 0);
/// assert_eq!(Timeout::from_raw(1).as_micros(), 15);
/// assert_eq!(Timeout::from_raw(64_000).as_micros(), 1_000_000);
/// ```
pub const fn as_micros(&self) -> u32 {
(self.as_nanos() / 1_000) as u32
}
/// Get the timeout as milliseconds, rounding towards zero.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_millis(), 262_143);
/// assert_eq!(Timeout::DISABLED.as_millis(), 0);
/// assert_eq!(Timeout::from_raw(1).as_millis(), 0);
/// assert_eq!(Timeout::from_raw(64_000).as_millis(), 1_000);
/// ```
pub const fn as_millis(&self) -> u32 {
self.into_bits() / Self::BITS_PER_MILLI
}
/// Get the timeout as seconds, rounding towards zero.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_secs(), 262);
/// assert_eq!(Timeout::DISABLED.as_secs(), 0);
/// assert_eq!(Timeout::from_raw(1).as_secs(), 0);
/// assert_eq!(Timeout::from_raw(64_000).as_secs(), 1);
/// ```
pub const fn as_secs(&self) -> u16 {
(self.into_bits() / Self::BITS_PER_SEC) as u16
}
/// Get the timeout as a [`Duration`].
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(
/// Timeout::MAX.as_duration(),
/// Duration::from_nanos(262_143_984_375)
/// );
/// assert_eq!(Timeout::DISABLED.as_duration(), Duration::from_nanos(0));
/// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION);
/// ```
pub const fn as_duration(&self) -> Duration {
Duration::from_nanos((self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64))
}
/// Get the bit value for the timeout.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_raw(u32::MAX).into_bits(), 0x00FF_FFFF);
/// assert_eq!(Timeout::from_raw(1).into_bits(), 1);
/// ```
pub const fn into_bits(self) -> u32 {
self.bits
}
/// Get the byte value for the timeout.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_raw(u32::MAX).as_bytes(), [0xFF, 0xFF, 0xFF]);
/// assert_eq!(Timeout::from_raw(1).as_bytes(), [0, 0, 1]);
/// ```
pub const fn as_bytes(self) -> [u8; 3] {
[
((self.bits >> 16) & 0xFF) as u8,
((self.bits >> 8) & 0xFF) as u8,
(self.bits & 0xFF) as u8,
]
}
/// Saturating timeout addition. Computes `self + rhs`, saturating at the
/// numeric bounds instead of overflowing.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(
/// Timeout::from_raw(0xFF_FF_F0).saturating_add(Timeout::from_raw(0xFF)),
/// Timeout::from_raw(0xFF_FF_FF)
/// );
/// assert_eq!(
/// Timeout::from_raw(100).saturating_add(Timeout::from_raw(23)),
/// Timeout::from_raw(123)
/// );
/// ```
#[must_use = "saturating_add returns a new Timeout"]
pub const fn saturating_add(self, rhs: Self) -> Self {
// TODO: use core::cmp::min when it is const
let bits: u32 = self.bits.saturating_add(rhs.bits);
if bits > Self::MAX.bits {
Self::MAX
} else {
Self { bits }
}
}
}
impl From<Timeout> for Duration {
fn from(to: Timeout) -> Self {
to.as_duration()
}
}
impl From<Timeout> for [u8; 3] {
fn from(to: Timeout) -> Self {
to.as_bytes()
}
}
#[cfg(feature = "time")]
impl From<Timeout> for embassy_time::Duration {
fn from(to: Timeout) -> Self {
embassy_time::Duration::from_micros(to.as_micros().into())
}
}
#[cfg(test)]
mod tests {
use core::time::Duration;
use super::{Timeout, ValueError};
#[test]
fn saturate() {
assert_eq!(Timeout::from_duration_sat(Duration::from_secs(u64::MAX)), Timeout::MAX);
}
#[test]
fn rounding() {
const NANO1: Duration = Duration::from_nanos(1);
let res_sub_1_ns: Duration = Timeout::RESOLUTION - NANO1;
let res_add_1_ns: Duration = Timeout::RESOLUTION + NANO1;
assert_eq!(Timeout::from_duration_sat(res_sub_1_ns).into_bits(), 1);
assert_eq!(Timeout::from_duration_sat(res_add_1_ns).into_bits(), 1);
}
#[test]
fn lower_limit() {
let low: Duration = (Timeout::RESOLUTION + Duration::from_nanos(1)) / 2;
assert_eq!(Timeout::from_duration(low), Ok(Timeout::from_raw(1)));
let too_low: Duration = low - Duration::from_nanos(1);
assert_eq!(
Timeout::from_duration(too_low),
Err(ValueError::too_low(too_low.as_nanos(), low.as_nanos()))
);
}
#[test]
fn upper_limit() {
let high: Duration = Timeout::MAX.as_duration() + Timeout::RESOLUTION / 2;
assert_eq!(Timeout::from_duration(high), Ok(Timeout::from_raw(0xFFFFFF)));
let too_high: Duration = high + Duration::from_nanos(1);
assert_eq!(
Timeout::from_duration(too_high),
Err(ValueError::too_high(too_high.as_nanos(), high.as_nanos()))
);
}
}

View File

@ -1,192 +0,0 @@
/// Power amplifier ramp time for FSK, MSK, and LoRa modulation.
///
/// Argument of [`set_ramp_time`][`super::TxParams::set_ramp_time`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum RampTime {
/// 10µs
Micros10 = 0x00,
/// 20µs
Micros20 = 0x01,
/// 40µs
Micros40 = 0x02,
/// 80µs
Micros80 = 0x03,
/// 200µs
Micros200 = 0x04,
/// 800µs
Micros800 = 0x05,
/// 1.7ms
Micros1700 = 0x06,
/// 3.4ms
Micros3400 = 0x07,
}
impl From<RampTime> for u8 {
fn from(rt: RampTime) -> Self {
rt as u8
}
}
impl From<RampTime> for core::time::Duration {
fn from(rt: RampTime) -> Self {
match rt {
RampTime::Micros10 => core::time::Duration::from_micros(10),
RampTime::Micros20 => core::time::Duration::from_micros(20),
RampTime::Micros40 => core::time::Duration::from_micros(40),
RampTime::Micros80 => core::time::Duration::from_micros(80),
RampTime::Micros200 => core::time::Duration::from_micros(200),
RampTime::Micros800 => core::time::Duration::from_micros(800),
RampTime::Micros1700 => core::time::Duration::from_micros(1700),
RampTime::Micros3400 => core::time::Duration::from_micros(3400),
}
}
}
#[cfg(feature = "time")]
impl From<RampTime> for embassy_time::Duration {
fn from(rt: RampTime) -> Self {
match rt {
RampTime::Micros10 => embassy_time::Duration::from_micros(10),
RampTime::Micros20 => embassy_time::Duration::from_micros(20),
RampTime::Micros40 => embassy_time::Duration::from_micros(40),
RampTime::Micros80 => embassy_time::Duration::from_micros(80),
RampTime::Micros200 => embassy_time::Duration::from_micros(200),
RampTime::Micros800 => embassy_time::Duration::from_micros(800),
RampTime::Micros1700 => embassy_time::Duration::from_micros(1700),
RampTime::Micros3400 => embassy_time::Duration::from_micros(3400),
}
}
}
/// Transmit parameters, output power and power amplifier ramp up time.
///
/// Argument of [`set_tx_params`][`super::SubGhz::set_tx_params`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TxParams {
buf: [u8; 3],
}
impl TxParams {
/// Optimal power setting for +15dBm output power with the low-power PA.
///
/// This must be used with [`PaConfig::LP_15`](super::PaConfig::LP_15).
pub const LP_15: TxParams = TxParams::new().set_power(0x0E);
/// Optimal power setting for +14dBm output power with the low-power PA.
///
/// This must be used with [`PaConfig::LP_14`](super::PaConfig::LP_14).
pub const LP_14: TxParams = TxParams::new().set_power(0x0E);
/// Optimal power setting for +10dBm output power with the low-power PA.
///
/// This must be used with [`PaConfig::LP_10`](super::PaConfig::LP_10).
pub const LP_10: TxParams = TxParams::new().set_power(0x0D);
/// Optimal power setting for the high-power PA.
///
/// This must be used with one of:
///
/// * [`PaConfig::HP_22`](super::PaConfig::HP_22)
/// * [`PaConfig::HP_20`](super::PaConfig::HP_20)
/// * [`PaConfig::HP_17`](super::PaConfig::HP_17)
/// * [`PaConfig::HP_14`](super::PaConfig::HP_14)
pub const HP: TxParams = TxParams::new().set_power(0x16);
/// Create a new `TxParams` struct.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::TxParams;
///
/// const TX_PARAMS: TxParams = TxParams::new();
/// assert_eq!(TX_PARAMS, TxParams::default());
/// ```
pub const fn new() -> TxParams {
TxParams {
buf: [super::OpCode::SetTxParams as u8, 0x00, 0x00],
}
}
/// Set the output power.
///
/// For low power selected in [`set_pa_config`]:
///
/// * 0x0E: +14 dB
/// * ...
/// * 0x00: 0 dB
/// * ...
/// * 0xEF: -17 dB
/// * Others: reserved
///
/// For high power selected in [`set_pa_config`]:
///
/// * 0x16: +22 dB
/// * ...
/// * 0x00: 0 dB
/// * ...
/// * 0xF7: -9 dB
/// * Others: reserved
///
/// # Example
///
/// Set the output power to 0 dB.
///
/// ```
/// use stm32wlxx_hal::subghz::{RampTime, TxParams};
///
/// const TX_PARAMS: TxParams = TxParams::new().set_power(0x00);
/// # assert_eq!(TX_PARAMS.as_slice()[1], 0x00);
/// ```
///
/// [`set_pa_config`]: super::SubGhz::set_pa_config
#[must_use = "set_power returns a modified TxParams"]
pub const fn set_power(mut self, power: u8) -> TxParams {
self.buf[1] = power;
self
}
/// Set the Power amplifier ramp time for FSK, MSK, and LoRa modulation.
///
/// # Example
///
/// Set the ramp time to 200 microseconds.
///
/// ```
/// use stm32wlxx_hal::subghz::{RampTime, TxParams};
///
/// const TX_PARAMS: TxParams = TxParams::new().set_ramp_time(RampTime::Micros200);
/// # assert_eq!(TX_PARAMS.as_slice()[2], 0x04);
/// ```
#[must_use = "set_ramp_time returns a modified TxParams"]
pub const fn set_ramp_time(mut self, rt: RampTime) -> TxParams {
self.buf[2] = rt as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{RampTime, TxParams};
///
/// const TX_PARAMS: TxParams = TxParams::new()
/// .set_ramp_time(RampTime::Micros80)
/// .set_power(0x0E);
/// assert_eq!(TX_PARAMS.as_slice(), &[0x8E, 0x0E, 0x03]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for TxParams {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,129 +0,0 @@
/// Error for a value that is out-of-bounds.
///
/// Used by [`Timeout::from_duration`].
///
/// [`Timeout::from_duration`]: super::Timeout::from_duration
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueError<T> {
value: T,
limit: T,
over: bool,
}
impl<T> ValueError<T> {
/// Create a new `ValueError` for a value that exceeded an upper bound.
///
/// Unfortunately panic is not available in `const fn`, so there are no
/// guarantees on the value being greater than the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert!(ERROR.over());
/// assert!(!ERROR.under());
/// ```
pub const fn too_high(value: T, limit: T) -> ValueError<T> {
ValueError {
value,
limit,
over: true,
}
}
/// Create a new `ValueError` for a value that exceeded a lower bound.
///
/// Unfortunately panic is not available in `const fn`, so there are no
/// guarantees on the value being less than the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8);
/// assert!(ERROR.under());
/// assert!(!ERROR.over());
/// ```
pub const fn too_low(value: T, limit: T) -> ValueError<T> {
ValueError {
value,
limit,
over: false,
}
}
/// Get the value that caused the error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert_eq!(ERROR.value(), &101u8);
/// ```
pub const fn value(&self) -> &T {
&self.value
}
/// Get the limit for the value.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert_eq!(ERROR.limit(), &100u8);
/// ```
pub const fn limit(&self) -> &T {
&self.limit
}
/// Returns `true` if the value was over the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert!(ERROR.over());
/// assert!(!ERROR.under());
/// ```
pub const fn over(&self) -> bool {
self.over
}
/// Returns `true` if the value was under the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8);
/// assert!(ERROR.under());
/// assert!(!ERROR.over());
/// ```
pub const fn under(&self) -> bool {
!self.over
}
}
impl<T> core::fmt::Display for ValueError<T>
where
T: core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.over {
write!(f, "Value is too high {} > {}", self.value, self.limit)
} else {
write!(f, "Value is too low {} < {}", self.value, self.limit)
}
}
}

View File

@ -84,7 +84,7 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
Self::new_inner(peri, irq, rx, tx, tx_buffer, rx_buffer, config) Self::new_inner(peri, irq, rx, tx, tx_buffer, rx_buffer, config)
} }
#[cfg(not(usart_v1))] #[cfg(not(any(usart_v1, usart_v2)))]
pub fn new_with_de( pub fn new_with_de(
peri: impl Peripheral<P = T> + 'd, peri: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd, irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -133,7 +133,7 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
tx.set_as_af(tx.af_num(), AFType::OutputPushPull); tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
} }
configure(r, &config, T::frequency(), T::MULTIPLIER, true, true); configure(r, &config, T::frequency(), T::KIND, true, true);
unsafe { unsafe {
r.cr1().modify(|w| { r.cr1().modify(|w| {

View File

@ -12,10 +12,11 @@ use futures::future::{select, Either};
use crate::dma::{NoDma, Transfer}; use crate::dma::{NoDma, Transfer};
use crate::gpio::sealed::AFType; use crate::gpio::sealed::AFType;
#[cfg(any(lpuart_v1, lpuart_v2))] #[cfg(not(any(usart_v1, usart_v2)))]
use crate::pac::lpuart::{regs, vals, Lpuart as Regs}; use crate::pac::usart::Lpuart as Regs;
#[cfg(not(any(lpuart_v1, lpuart_v2)))] #[cfg(any(usart_v1, usart_v2))]
use crate::pac::usart::{regs, vals, Usart as Regs}; use crate::pac::usart::Usart as Regs;
use crate::pac::usart::{regs, vals};
use crate::time::Hertz; use crate::time::Hertz;
use crate::{peripherals, Peripheral}; use crate::{peripherals, Peripheral};
@ -159,7 +160,7 @@ impl<'d, T: BasicInstance, TxDma> UartTx<'d, T, TxDma> {
tx.set_as_af(tx.af_num(), AFType::OutputPushPull); tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
} }
configure(r, &config, T::frequency(), T::MULTIPLIER, false, true); configure(r, &config, T::frequency(), T::KIND, false, true);
// create state once! // create state once!
let _s = T::state(); let _s = T::state();
@ -261,7 +262,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
rx.set_as_af(rx.af_num(), AFType::Input); rx.set_as_af(rx.af_num(), AFType::Input);
} }
configure(r, &config, T::frequency(), T::MULTIPLIER, true, false); configure(r, &config, T::frequency(), T::KIND, true, false);
irq.set_handler(Self::on_interrupt); irq.set_handler(Self::on_interrupt);
irq.unpend(); irq.unpend();
@ -283,8 +284,8 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) }; let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) };
let mut wake = false;
let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie()); let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie());
if has_errors { if has_errors {
// clear all interrupts and DMA Rx Request // clear all interrupts and DMA Rx Request
unsafe { unsafe {
@ -304,22 +305,30 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
}); });
} }
compiler_fence(Ordering::SeqCst); wake = true;
} else {
s.rx_waker.wake(); if cr1.idleie() && sr.idle() {
} else if cr1.idleie() && sr.idle() {
// IDLE detected: no more data will come // IDLE detected: no more data will come
unsafe { unsafe {
r.cr1().modify(|w| { r.cr1().modify(|w| {
// disable idle line detection // disable idle line detection
w.set_idleie(false); w.set_idleie(false);
}); });
r.cr3().modify(|w| {
// disable DMA Rx Request
w.set_dmar(false);
});
} }
wake = true;
}
if cr1.rxneie() {
// We cannot check the RXNE flag as it is auto-cleared by the DMA controller
// It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection
wake = true;
}
}
if wake {
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
s.rx_waker.wake(); s.rx_waker.wake();
@ -497,12 +506,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
unreachable!(); unreachable!();
} }
if !enable_idle_line_detection { if enable_idle_line_detection {
transfer.await;
return Ok(ReadCompletionEvent::DmaCompleted);
}
// clear idle flag // clear idle flag
let sr = sr(r).read(); let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1. // This read also clears the error and idle interrupt flags on v1.
@ -514,11 +518,12 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
w.set_idleie(true); w.set_idleie(true);
}); });
} }
}
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
// future which completes when idle line is detected // future which completes when idle line or error is detected
let idle = poll_fn(move |cx| { let abort = poll_fn(move |cx| {
let s = T::state(); let s = T::state();
s.rx_waker.register(cx.waker()); s.rx_waker.register(cx.waker());
@ -554,7 +559,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
} }
} }
if sr.idle() { if enable_idle_line_detection && sr.idle() {
// Idle line detected // Idle line detected
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
@ -565,7 +570,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
// wait for the first of DMA request or idle line detected to completes // wait for the first of DMA request or idle line detected to completes
// select consumes its arguments // select consumes its arguments
// when transfer is dropped, it will stop the DMA request // when transfer is dropped, it will stop the DMA request
let r = match select(transfer, idle).await { let r = match select(transfer, abort).await {
// DMA transfer completed first // DMA transfer completed first
Either::Left(((), _)) => Ok(ReadCompletionEvent::DmaCompleted), Either::Left(((), _)) => Ok(ReadCompletionEvent::DmaCompleted),
@ -649,7 +654,7 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config) Self::new_inner(peri, rx, tx, irq, tx_dma, rx_dma, config)
} }
#[cfg(not(usart_v1))] #[cfg(not(any(usart_v1, usart_v2)))]
pub fn new_with_de( pub fn new_with_de(
peri: impl Peripheral<P = T> + 'd, peri: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd, rx: impl Peripheral<P = impl RxPin<T>> + 'd,
@ -692,7 +697,7 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
tx.set_as_af(tx.af_num(), AFType::OutputPushPull); tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
} }
configure(r, &config, T::frequency(), T::MULTIPLIER, true, true); configure(r, &config, T::frequency(), T::KIND, true, true);
irq.set_handler(UartRx::<T, RxDma>::on_interrupt); irq.set_handler(UartRx::<T, RxDma>::on_interrupt);
irq.unpend(); irq.unpend();
@ -759,16 +764,74 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
} }
} }
fn configure(r: Regs, config: &Config, pclk_freq: Hertz, multiplier: u32, enable_rx: bool, enable_tx: bool) { fn configure(r: Regs, config: &Config, pclk_freq: Hertz, kind: Kind, enable_rx: bool, enable_tx: bool) {
if !enable_rx && !enable_tx { if !enable_rx && !enable_tx {
panic!("USART: At least one of RX or TX should be enabled"); panic!("USART: At least one of RX or TX should be enabled");
} }
// TODO: better calculation, including error checking and OVER8 if possible. #[cfg(not(usart_v4))]
let div = (pclk_freq.0 + (config.baudrate / 2)) / config.baudrate * multiplier; static DIVS: [(u16, ()); 1] = [(1, ())];
#[cfg(usart_v4)]
static DIVS: [(u16, vals::Presc); 12] = [
(1, vals::Presc::DIV1),
(2, vals::Presc::DIV2),
(4, vals::Presc::DIV4),
(6, vals::Presc::DIV6),
(8, vals::Presc::DIV8),
(10, vals::Presc::DIV10),
(12, vals::Presc::DIV12),
(16, vals::Presc::DIV16),
(32, vals::Presc::DIV32),
(64, vals::Presc::DIV64),
(128, vals::Presc::DIV128),
(256, vals::Presc::DIV256),
];
let (mul, brr_min, brr_max) = match kind {
#[cfg(any(usart_v3, usart_v4))]
Kind::Lpuart => (256, 0x300, 0x10_0000),
Kind::Uart => (1, 0x10, 0x1_0000),
};
#[cfg(not(usart_v1))]
let mut over8 = false;
let mut found = false;
for &(presc, _presc_val) in &DIVS {
let denom = (config.baudrate * presc as u32) as u64;
let div = (pclk_freq.0 as u64 * mul + (denom / 2)) / denom;
trace!("USART: presc={} div={:08x}", presc, div);
if div < brr_min {
#[cfg(not(usart_v1))]
if div * 2 >= brr_min && kind == Kind::Uart && !cfg!(usart_v1) {
over8 = true;
let div = div as u32;
unsafe {
r.brr().write_value(regs::Brr(((div << 1) & !0xF) | (div & 0x07)));
#[cfg(usart_v4)]
r.presc().write(|w| w.set_prescaler(_presc_val));
}
found = true;
break;
}
panic!("USART: baudrate too high");
}
if div < brr_max {
unsafe {
r.brr().write_value(regs::Brr(div as u32));
#[cfg(usart_v4)]
r.presc().write(|w| w.set_prescaler(_presc_val));
}
found = true;
break;
}
}
assert!(found, "USART: baudrate too low");
unsafe { unsafe {
r.brr().write_value(regs::Brr(div));
r.cr2().write(|w| { r.cr2().write(|w| {
w.set_stop(match config.stop_bits { w.set_stop(match config.stop_bits {
StopBits::STOP0P5 => vals::Stop::STOP0P5, StopBits::STOP0P5 => vals::Stop::STOP0P5,
@ -797,6 +860,8 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, multiplier: u32, enable
Parity::ParityEven => vals::Ps::EVEN, Parity::ParityEven => vals::Ps::EVEN,
_ => vals::Ps::EVEN, _ => vals::Ps::EVEN,
}); });
#[cfg(not(usart_v1))]
w.set_over8(vals::Over8(over8 as _));
}); });
} }
} }
@ -977,43 +1042,50 @@ pub use buffered::*;
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
mod buffered; mod buffered;
#[cfg(usart_v1)] #[cfg(not(gpdma))]
mod rx_ringbuffered;
#[cfg(not(gpdma))]
pub use rx_ringbuffered::RingBufferedUartRx;
use self::sealed::Kind;
#[cfg(any(usart_v1, usart_v2))]
fn tdr(r: crate::pac::usart::Usart) -> *mut u8 { fn tdr(r: crate::pac::usart::Usart) -> *mut u8 {
r.dr().ptr() as _ r.dr().ptr() as _
} }
#[cfg(usart_v1)] #[cfg(any(usart_v1, usart_v2))]
fn rdr(r: crate::pac::usart::Usart) -> *mut u8 { fn rdr(r: crate::pac::usart::Usart) -> *mut u8 {
r.dr().ptr() as _ r.dr().ptr() as _
} }
#[cfg(usart_v1)] #[cfg(any(usart_v1, usart_v2))]
fn sr(r: crate::pac::usart::Usart) -> crate::pac::common::Reg<regs::Sr, crate::pac::common::RW> { fn sr(r: crate::pac::usart::Usart) -> crate::pac::common::Reg<regs::Sr, crate::pac::common::RW> {
r.sr() r.sr()
} }
#[cfg(usart_v1)] #[cfg(any(usart_v1, usart_v2))]
#[allow(unused)] #[allow(unused)]
unsafe fn clear_interrupt_flags(_r: Regs, _sr: regs::Sr) { unsafe fn clear_interrupt_flags(_r: Regs, _sr: regs::Sr) {
// On v1 the flags are cleared implicitly by reads and writes to DR. // On v1 the flags are cleared implicitly by reads and writes to DR.
} }
#[cfg(usart_v2)] #[cfg(any(usart_v3, usart_v4))]
fn tdr(r: Regs) -> *mut u8 { fn tdr(r: Regs) -> *mut u8 {
r.tdr().ptr() as _ r.tdr().ptr() as _
} }
#[cfg(usart_v2)] #[cfg(any(usart_v3, usart_v4))]
fn rdr(r: Regs) -> *mut u8 { fn rdr(r: Regs) -> *mut u8 {
r.rdr().ptr() as _ r.rdr().ptr() as _
} }
#[cfg(usart_v2)] #[cfg(any(usart_v3, usart_v4))]
fn sr(r: Regs) -> crate::pac::common::Reg<regs::Isr, crate::pac::common::R> { fn sr(r: Regs) -> crate::pac::common::Reg<regs::Isr, crate::pac::common::R> {
r.isr() r.isr()
} }
#[cfg(usart_v2)] #[cfg(any(usart_v3, usart_v4))]
#[allow(unused)] #[allow(unused)]
unsafe fn clear_interrupt_flags(r: Regs, sr: regs::Isr) { unsafe fn clear_interrupt_flags(r: Regs, sr: regs::Isr) {
r.icr().write(|w| *w = regs::Icr(sr.0)); r.icr().write(|w| *w = regs::Icr(sr.0));
@ -1024,6 +1096,13 @@ pub(crate) mod sealed {
use super::*; use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Kind {
Uart,
#[cfg(any(usart_v3, usart_v4))]
Lpuart,
}
pub struct State { pub struct State {
pub rx_waker: AtomicWaker, pub rx_waker: AtomicWaker,
pub tx_waker: AtomicWaker, pub tx_waker: AtomicWaker,
@ -1039,7 +1118,7 @@ pub(crate) mod sealed {
} }
pub trait BasicInstance: crate::rcc::RccPeripheral { pub trait BasicInstance: crate::rcc::RccPeripheral {
const MULTIPLIER: u32; const KIND: Kind;
type Interrupt: crate::interrupt::Interrupt; type Interrupt: crate::interrupt::Interrupt;
fn regs() -> Regs; fn regs() -> Regs;
@ -1068,10 +1147,10 @@ pin_trait!(DePin, BasicInstance);
dma_trait!(TxDma, BasicInstance); dma_trait!(TxDma, BasicInstance);
dma_trait!(RxDma, BasicInstance); dma_trait!(RxDma, BasicInstance);
macro_rules! impl_lpuart { macro_rules! impl_usart {
($inst:ident, $irq:ident, $mul:expr) => { ($inst:ident, $irq:ident, $kind:expr) => {
impl sealed::BasicInstance for crate::peripherals::$inst { impl sealed::BasicInstance for crate::peripherals::$inst {
const MULTIPLIER: u32 = $mul; const KIND: Kind = $kind;
type Interrupt = crate::interrupt::$irq; type Interrupt = crate::interrupt::$irq;
fn regs() -> Regs { fn regs() -> Regs {
@ -1095,21 +1174,19 @@ macro_rules! impl_lpuart {
} }
foreach_interrupt!( foreach_interrupt!(
($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => { ($inst:ident, usart, LPUART, $signal_name:ident, $irq:ident) => {
impl_lpuart!($inst, $irq, 256); impl_usart!($inst, $irq, Kind::Lpuart);
}; };
($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => { ($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => {
impl_lpuart!($inst, $irq, 1); impl_usart!($inst, $irq, Kind::Uart);
impl sealed::FullInstance for peripherals::$inst { impl sealed::FullInstance for peripherals::$inst {
fn regs_uart() -> crate::pac::usart::Usart { fn regs_uart() -> crate::pac::usart::Usart {
crate::pac::$inst crate::pac::$inst
} }
} }
impl FullInstance for peripherals::$inst { impl FullInstance for peripherals::$inst {}
}
}; };
); );

View File

@ -0,0 +1,286 @@
use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::PeripheralRef;
use futures::future::{select, Either};
use super::{clear_interrupt_flags, rdr, sr, BasicInstance, Error, UartRx};
use crate::dma::ringbuffer::OverrunError;
use crate::dma::RingBuffer;
pub struct RingBufferedUartRx<'d, T: BasicInstance, RxDma: super::RxDma<T>> {
_peri: PeripheralRef<'d, T>,
ring_buf: RingBuffer<'d, RxDma, u8>,
}
impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> UartRx<'d, T, RxDma> {
/// Turn the `UartRx` into a buffered uart which can continously receive in the background
/// without the possibility of loosing bytes. The `dma_buf` is a buffer registered to the
/// DMA controller, and must be sufficiently large, such that it will not overflow.
pub fn into_ring_buffered(self, dma_buf: &'d mut [u8]) -> RingBufferedUartRx<'d, T, RxDma> {
assert!(dma_buf.len() > 0 && dma_buf.len() <= 0xFFFF);
let request = self.rx_dma.request();
let opts = Default::default();
let ring_buf = unsafe { RingBuffer::new_read(self.rx_dma, request, rdr(T::regs()), dma_buf, opts) };
RingBufferedUartRx {
_peri: self._peri,
ring_buf,
}
}
}
impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxDma> {
pub fn start(&mut self) -> Result<(), Error> {
// Clear the ring buffer so that it is ready to receive data
self.ring_buf.clear();
self.setup_uart();
Ok(())
}
/// Start uart background receive
fn setup_uart(&mut self) {
// fence before starting DMA.
compiler_fence(Ordering::SeqCst);
self.ring_buf.start();
let r = T::regs();
// clear all interrupts and DMA Rx Request
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable RXNE interrupt
w.set_rxneie(false);
// enable parity interrupt if not ParityNone
w.set_peie(w.pce());
// disable idle line interrupt
w.set_idleie(false);
});
r.cr3().modify(|w| {
// enable Error Interrupt: (Frame error, Noise error, Overrun error)
w.set_eie(true);
// enable DMA Rx Request
w.set_dmar(true);
});
}
}
/// Stop uart background receive
fn teardown_uart(&mut self) {
let r = T::regs();
// clear all interrupts and DMA Rx Request
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable RXNE interrupt
w.set_rxneie(false);
// disable parity interrupt
w.set_peie(false);
// disable idle line interrupt
w.set_idleie(false);
});
r.cr3().modify(|w| {
// disable Error Interrupt: (Frame error, Noise error, Overrun error)
w.set_eie(false);
// disable DMA Rx Request
w.set_dmar(false);
});
}
compiler_fence(Ordering::SeqCst);
self.ring_buf.request_stop();
while self.ring_buf.is_running() {}
}
/// Read bytes that are readily available in the ring buffer.
/// If no bytes are currently available in the buffer the call waits until the some
/// bytes are available (at least one byte and at most half the buffer size)
///
/// Background receive is started if `start()` has not been previously called.
///
/// Receive in the background is terminated if an error is returned.
/// It must then manually be started again by calling `start()` or by re-calling `read()`.
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
let r = T::regs();
// Start background receive if it was not already started
// SAFETY: read only
let is_started = unsafe { r.cr3().read().dmar() };
if !is_started {
self.start()?;
}
// SAFETY: read only and we only use Rx related flags
let s = unsafe { sr(r).read() };
let has_errors = s.pe() || s.fe() || s.ne() || s.ore();
if has_errors {
self.teardown_uart();
if s.pe() {
return Err(Error::Parity);
} else if s.fe() {
return Err(Error::Framing);
} else if s.ne() {
return Err(Error::Noise);
} else {
return Err(Error::Overrun);
}
}
self.ring_buf.reload_position();
match self.ring_buf.read(buf) {
Ok(len) if len == 0 => {}
Ok(len) => {
assert!(len > 0);
return Ok(len);
}
Err(OverrunError) => {
// Stop any transfer from now on
// The user must re-start to receive any more data
self.teardown_uart();
return Err(Error::Overrun);
}
}
loop {
self.wait_for_data_or_idle().await?;
self.ring_buf.reload_position();
if !self.ring_buf.is_empty() {
break;
}
}
let len = self.ring_buf.read(buf).map_err(|_err| Error::Overrun)?;
assert!(len > 0);
Ok(len)
}
/// Wait for uart idle or dma half-full or full
async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> {
let r = T::regs();
// make sure USART state is restored to neutral state
let _on_drop = OnDrop::new(move || {
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable idle line interrupt
w.set_idleie(false);
});
}
});
// SAFETY: only sets Rx related flags
unsafe {
r.cr1().modify(|w| {
// enable idle line interrupt
w.set_idleie(true);
});
}
compiler_fence(Ordering::SeqCst);
// Future which completes when there is dma is half full or full
let dma = poll_fn(|cx| {
self.ring_buf.set_waker(cx.waker());
compiler_fence(Ordering::SeqCst);
self.ring_buf.reload_position();
if !self.ring_buf.is_empty() {
// Some data is now available
Poll::Ready(())
} else {
Poll::Pending
}
});
// Future which completes when idle line is detected
let uart = poll_fn(|cx| {
let s = T::state();
s.rx_waker.register(cx.waker());
compiler_fence(Ordering::SeqCst);
// SAFETY: read only and we only use Rx related flags
let sr = unsafe { sr(r).read() };
// SAFETY: only clears Rx related flags
unsafe {
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
}
let has_errors = sr.pe() || sr.fe() || sr.ne() || sr.ore();
if has_errors {
if sr.pe() {
return Poll::Ready(Err(Error::Parity));
} else if sr.fe() {
return Poll::Ready(Err(Error::Framing));
} else if sr.ne() {
return Poll::Ready(Err(Error::Noise));
} else {
return Poll::Ready(Err(Error::Overrun));
}
}
if sr.idle() {
// Idle line is detected
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
});
match select(dma, uart).await {
Either::Left(((), _)) => Ok(()),
Either::Right((Ok(()), _)) => Ok(()),
Either::Right((Err(e), _)) => {
self.teardown_uart();
Err(e)
}
}
}
}
impl<T: BasicInstance, RxDma: super::RxDma<T>> Drop for RingBufferedUartRx<'_, T, RxDma> {
fn drop(&mut self) {
self.teardown_uart();
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
mod eio {
use embedded_io::asynch::Read;
use embedded_io::Io;
use super::RingBufferedUartRx;
use crate::usart::{BasicInstance, Error, RxDma};
impl<T, Rx> Io for RingBufferedUartRx<'_, T, Rx>
where
T: BasicInstance,
Rx: RxDma<T>,
{
type Error = Error;
}
impl<T, Rx> Read for RingBufferedUartRx<'_, T, Rx>
where
T: BasicInstance,
Rx: RxDma<T>,
{
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.read(buf).await
}
}
}

View File

@ -18,7 +18,7 @@ embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defm
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true }
embedded-io = "0.4.0" embedded-io = "0.4.0"
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx126x", "time", "defmt", "external-lora-phy"], optional = true } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true }
lora-phy = { version = "1", optional = true } lora-phy = { version = "1", optional = true }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }

View File

@ -15,7 +15,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt", "external-lora-phy"] } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] }
lora-phy = { version = "1" } lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] }

View File

@ -4,13 +4,13 @@
use defmt::info; use defmt::info;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::gpio::{AnyPin, Pin}; use embassy_rp::gpio::{AnyPin, Pin};
use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachine, PioStateMachineInstance, ShiftDirection, Sm0, Sm1, Sm2}; use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Pio, PioCommon, PioStateMachine, PioStateMachineInstance, ShiftDirection};
use embassy_rp::pio_instr_util; use embassy_rp::pio_instr_util;
use embassy_rp::relocate::RelocatedProgram; use embassy_rp::relocate::RelocatedProgram;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::task] fn setup_pio_task_sm0(pio: &mut PioCommon<PIO0>, sm: &mut PioStateMachineInstance<PIO0, 0>, pin: AnyPin) {
async fn pio_task_sm0(mut sm: PioStateMachineInstance<Pio0, Sm0>, pin: AnyPin) {
// Setup sm0 // Setup sm0
// Send data serially to pin // Send data serially to pin
@ -23,11 +23,11 @@ async fn pio_task_sm0(mut sm: PioStateMachineInstance<Pio0, Sm0>, pin: AnyPin) {
); );
let relocated = RelocatedProgram::new(&prg.program); let relocated = RelocatedProgram::new(&prg.program);
let out_pin = sm.make_pio_pin(pin); let out_pin = pio.make_pio_pin(pin);
let pio_pins = [&out_pin]; let pio_pins = [&out_pin];
sm.set_out_pins(&pio_pins); sm.set_out_pins(&pio_pins);
sm.write_instr(relocated.origin() as usize, relocated.code()); pio.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin()); pio_instr_util::exec_jmp(sm, relocated.origin());
sm.set_clkdiv((125e6 / 20.0 / 2e2 * 256.0) as u32); sm.set_clkdiv((125e6 / 20.0 / 2e2 * 256.0) as u32);
sm.set_set_range(0, 1); sm.set_set_range(0, 1);
let pio::Wrap { source, target } = relocated.wrap(); let pio::Wrap { source, target } = relocated.wrap();
@ -35,7 +35,10 @@ async fn pio_task_sm0(mut sm: PioStateMachineInstance<Pio0, Sm0>, pin: AnyPin) {
sm.set_autopull(true); sm.set_autopull(true);
sm.set_out_shift_dir(ShiftDirection::Left); sm.set_out_shift_dir(ShiftDirection::Left);
}
#[embassy_executor::task]
async fn pio_task_sm0(mut sm: PioStateMachineInstance<'static, PIO0, 0>) {
sm.set_enable(true); sm.set_enable(true);
let mut v = 0x0f0caffa; let mut v = 0x0f0caffa;
@ -46,16 +49,15 @@ async fn pio_task_sm0(mut sm: PioStateMachineInstance<Pio0, Sm0>, pin: AnyPin) {
} }
} }
#[embassy_executor::task] fn setup_pio_task_sm1(pio: &mut PioCommon<PIO0>, sm: &mut PioStateMachineInstance<PIO0, 1>) {
async fn pio_task_sm1(mut sm: PioStateMachineInstance<Pio0, Sm1>) {
// Setupm sm1 // Setupm sm1
// Read 0b10101 repeatedly until ISR is full // Read 0b10101 repeatedly until ISR is full
let prg = pio_proc::pio_asm!(".origin 8", "set x, 0x15", ".wrap_target", "in x, 5 [31]", ".wrap",); let prg = pio_proc::pio_asm!(".origin 8", "set x, 0x15", ".wrap_target", "in x, 5 [31]", ".wrap",);
let relocated = RelocatedProgram::new(&prg.program); let relocated = RelocatedProgram::new(&prg.program);
sm.write_instr(relocated.origin() as usize, relocated.code()); pio.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin()); pio_instr_util::exec_jmp(sm, relocated.origin());
sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32); sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32);
sm.set_set_range(0, 0); sm.set_set_range(0, 0);
let pio::Wrap { source, target } = relocated.wrap(); let pio::Wrap { source, target } = relocated.wrap();
@ -63,6 +65,10 @@ async fn pio_task_sm1(mut sm: PioStateMachineInstance<Pio0, Sm1>) {
sm.set_autopush(true); sm.set_autopush(true);
sm.set_in_shift_dir(ShiftDirection::Right); sm.set_in_shift_dir(ShiftDirection::Right);
}
#[embassy_executor::task]
async fn pio_task_sm1(mut sm: PioStateMachineInstance<'static, PIO0, 1>) {
sm.set_enable(true); sm.set_enable(true);
loop { loop {
let rx = sm.wait_pull().await; let rx = sm.wait_pull().await;
@ -70,8 +76,7 @@ async fn pio_task_sm1(mut sm: PioStateMachineInstance<Pio0, Sm1>) {
} }
} }
#[embassy_executor::task] fn setup_pio_task_sm2(pio: &mut PioCommon<PIO0>, sm: &mut PioStateMachineInstance<PIO0, 2>) {
async fn pio_task_sm2(mut sm: PioStateMachineInstance<Pio0, Sm2>) {
// Setup sm2 // Setup sm2
// Repeatedly trigger IRQ 3 // Repeatedly trigger IRQ 3
@ -85,13 +90,17 @@ async fn pio_task_sm2(mut sm: PioStateMachineInstance<Pio0, Sm2>) {
".wrap", ".wrap",
); );
let relocated = RelocatedProgram::new(&prg.program); let relocated = RelocatedProgram::new(&prg.program);
sm.write_instr(relocated.origin() as usize, relocated.code()); pio.write_instr(relocated.origin() as usize, relocated.code());
let pio::Wrap { source, target } = relocated.wrap(); let pio::Wrap { source, target } = relocated.wrap();
sm.set_wrap(source, target); sm.set_wrap(source, target);
pio_instr_util::exec_jmp(&mut sm, relocated.origin()); pio_instr_util::exec_jmp(sm, relocated.origin());
sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32); sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32);
}
#[embassy_executor::task]
async fn pio_task_sm2(mut sm: PioStateMachineInstance<'static, PIO0, 2>) {
sm.set_enable(true); sm.set_enable(true);
loop { loop {
sm.wait_irq(3).await; sm.wait_irq(3).await;
@ -104,9 +113,18 @@ async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
let pio = p.PIO0; let pio = p.PIO0;
let (_, sm0, sm1, sm2, ..) = pio.split(); let Pio {
mut common,
mut sm0,
mut sm1,
mut sm2,
..
} = Pio::new(pio);
spawner.spawn(pio_task_sm0(sm0, p.PIN_0.degrade())).unwrap(); setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0.degrade());
setup_pio_task_sm1(&mut common, &mut sm1);
setup_pio_task_sm2(&mut common, &mut sm2);
spawner.spawn(pio_task_sm0(sm0)).unwrap();
spawner.spawn(pio_task_sm1(sm1)).unwrap(); spawner.spawn(pio_task_sm1(sm1)).unwrap();
spawner.spawn(pio_task_sm2(sm2)).unwrap(); spawner.spawn(pio_task_sm2(sm2)).unwrap();
} }

View File

@ -4,7 +4,7 @@
use defmt::info; use defmt::info;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_futures::join::join; use embassy_futures::join::join;
use embassy_rp::pio::{PioPeripheral, PioStateMachine, ShiftDirection}; use embassy_rp::pio::{Pio, PioStateMachine, ShiftDirection};
use embassy_rp::relocate::RelocatedProgram; use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{pio_instr_util, Peripheral}; use embassy_rp::{pio_instr_util, Peripheral};
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@ -19,7 +19,11 @@ fn swap_nibbles(v: u32) -> u32 {
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
let pio = p.PIO0; let pio = p.PIO0;
let (_, mut sm, ..) = pio.split(); let Pio {
mut common,
sm0: mut sm,
..
} = Pio::new(pio);
let prg = pio_proc::pio_asm!( let prg = pio_proc::pio_asm!(
".origin 0", ".origin 0",
@ -34,7 +38,7 @@ async fn main(_spawner: Spawner) {
); );
let relocated = RelocatedProgram::new(&prg.program); let relocated = RelocatedProgram::new(&prg.program);
sm.write_instr(relocated.origin() as usize, relocated.code()); common.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin()); pio_instr_util::exec_jmp(&mut sm, relocated.origin());
sm.set_clkdiv((125e6 / 10e3 * 256.0) as u32); sm.set_clkdiv((125e6 / 10e3 * 256.0) as u32);
let pio::Wrap { source, target } = relocated.wrap(); let pio::Wrap { source, target } = relocated.wrap();

View File

@ -0,0 +1,243 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use core::fmt::Write;
use embassy_executor::Spawner;
use embassy_rp::dma::{AnyChannel, Channel};
use embassy_rp::gpio::Pin;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{FifoJoin, Pio, PioStateMachine, PioStateMachineInstance, ShiftDirection};
use embassy_rp::pwm::{Config, Pwm};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{into_ref, Peripheral, PeripheralRef};
use embassy_time::{Duration, Instant, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
// this test assumes a 2x16 HD44780 display attached as follow:
// rs = PIN0
// rw = PIN1
// e = PIN2
// db4 = PIN3
// db5 = PIN4
// db6 = PIN5
// db7 = PIN6
// additionally a pwm signal for a bias voltage charge pump is provided on pin 15,
// allowing direct connection of the display to the RP2040 without level shifters.
let p = embassy_rp::init(Default::default());
let _pwm = Pwm::new_output_b(p.PWM_CH7, p.PIN_15, {
let mut c = Config::default();
c.divider = 125.into();
c.top = 100;
c.compare_b = 50;
c
});
let mut hd = HD44780::new(
p.PIO0, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6,
)
.await;
loop {
struct Buf<const N: usize>([u8; N], usize);
impl<const N: usize> Write for Buf<N> {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
for b in s.as_bytes() {
if self.1 >= N {
return Err(core::fmt::Error);
}
self.0[self.1] = *b;
self.1 += 1;
}
Ok(())
}
}
let mut buf = Buf([0; 16], 0);
write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap();
hd.add_line(&buf.0[0..buf.1]).await;
Timer::after(Duration::from_secs(1)).await;
}
}
pub struct HD44780<'l> {
dma: PeripheralRef<'l, AnyChannel>,
sm: PioStateMachineInstance<'l, PIO0, 0>,
buf: [u8; 40],
}
impl<'l> HD44780<'l> {
pub async fn new(
pio: impl Peripheral<P = PIO0> + 'l,
dma: impl Peripheral<P = impl Channel> + 'l,
rs: impl Pin,
rw: impl Pin,
e: impl Pin,
db4: impl Pin,
db5: impl Pin,
db6: impl Pin,
db7: impl Pin,
) -> HD44780<'l> {
into_ref!(dma);
let db7pin = db7.pin();
let Pio {
mut common, mut sm0, ..
} = Pio::new(pio);
// takes command words (<wait:24> <command:4> <0:4>)
let prg = pio_proc::pio_asm!(
r#"
.side_set 1 opt
loop:
out x, 24
delay:
jmp x--, delay
out pins, 4 side 1
out null, 4 side 0
jmp !osre, loop
irq 0
"#,
);
let rs = common.make_pio_pin(rs);
let rw = common.make_pio_pin(rw);
let e = common.make_pio_pin(e);
let db4 = common.make_pio_pin(db4);
let db5 = common.make_pio_pin(db5);
let db6 = common.make_pio_pin(db6);
let db7 = common.make_pio_pin(db7);
sm0.set_set_pins(&[&rs, &rw]);
embassy_rp::pio_instr_util::set_pindir(&mut sm0, 0b11);
sm0.set_set_pins(&[&e]);
embassy_rp::pio_instr_util::set_pindir(&mut sm0, 0b1);
sm0.set_set_pins(&[&db4, &db5, &db6, &db7]);
embassy_rp::pio_instr_util::set_pindir(&mut sm0, 0b11111);
let relocated = RelocatedProgram::new(&prg.program);
common.write_instr(relocated.origin() as usize, relocated.code());
embassy_rp::pio_instr_util::exec_jmp(&mut sm0, relocated.origin());
sm0.set_clkdiv(125 * 256);
let pio::Wrap { source, target } = relocated.wrap();
sm0.set_wrap(source, target);
sm0.set_side_enable(true);
sm0.set_out_pins(&[&db4, &db5, &db6, &db7]);
sm0.set_sideset_base_pin(&e);
sm0.set_sideset_count(2);
sm0.set_out_shift_dir(ShiftDirection::Left);
sm0.set_fifo_join(FifoJoin::TxOnly);
sm0.set_autopull(true);
sm0.set_pull_threshold(32);
sm0.set_enable(true);
// init to 8 bit thrice
sm0.push_tx((50000 << 8) | 0x30);
sm0.push_tx((5000 << 8) | 0x30);
sm0.push_tx((200 << 8) | 0x30);
// init 4 bit
sm0.push_tx((200 << 8) | 0x20);
// set font and lines
sm0.push_tx((50 << 8) | 0x20);
sm0.push_tx(0b1100_0000);
sm0.wait_irq(0).await;
sm0.set_enable(false);
// takes command sequences (<rs:1> <count:7>, data...)
// many side sets are only there to free up a delay bit!
let prg = pio_proc::pio_asm!(
r#"
.origin 7
.side_set 1
.wrap_target
pull side 0
out x 1 side 0 ; !rs
out y 7 side 0 ; #data - 1
; rs/rw to e: >= 60ns
; e high time: >= 500ns
; e low time: >= 500ns
; read data valid after e falling: ~5ns
; write data hold after e falling: ~10ns
loop:
pull side 0
jmp !x data side 0
command:
set pins 0b00 side 0
jmp shift side 0
data:
set pins 0b01 side 0
shift:
out pins 4 side 1 [9]
nop side 0 [9]
out pins 4 side 1 [9]
mov osr null side 0 [7]
out pindirs 4 side 0
set pins 0b10 side 0
busy:
nop side 1 [9]
jmp pin more side 0 [9]
mov osr ~osr side 1 [9]
nop side 0 [4]
out pindirs 4 side 0
jmp y-- loop side 0
.wrap
more:
nop side 1 [9]
jmp busy side 0 [9]
"#
);
let relocated = RelocatedProgram::new(&prg.program);
common.write_instr(relocated.origin() as usize, relocated.code());
embassy_rp::pio_instr_util::exec_jmp(&mut sm0, relocated.origin());
let pio::Wrap { source, target } = relocated.wrap();
sm0.set_clkdiv(8 * 256); // ~64ns/insn
sm0.set_side_enable(false);
sm0.set_jmp_pin(db7pin);
sm0.set_wrap(source, target);
sm0.set_set_pins(&[&rs, &rw]);
sm0.set_out_pins(&[&db4, &db5, &db6, &db7]);
sm0.set_sideset_base_pin(&e);
sm0.set_sideset_count(1);
sm0.set_out_shift_dir(ShiftDirection::Left);
sm0.set_fifo_join(FifoJoin::TxOnly);
sm0.set_enable(true);
// display on and cursor on and blinking, reset display
sm0.dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await;
Self {
dma: dma.map_into(),
sm: sm0,
buf: [0x20; 40],
}
}
pub async fn add_line(&mut self, s: &[u8]) {
// move cursor to 0:0, prepare 16 characters
self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]);
// move line 2 up
self.buf.copy_within(22..38, 3);
// move cursor to 1:0, prepare 16 characters
self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]);
// file line 2 with spaces
self.buf[22..38].fill(0x20);
// copy input line
let len = s.len().min(16);
self.buf[22..22 + len].copy_from_slice(&s[0..len]);
// set cursor to 1:15
self.buf[38..].copy_from_slice(&[0x80, 0xcf]);
self.sm.dma_push(self.dma.reborrow(), &self.buf).await;
}
}

View File

@ -7,6 +7,7 @@
use defmt::*; use defmt::*;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::interrupt;
use embassy_rp::peripherals::UART1; use embassy_rp::peripherals::UART1;
use embassy_rp::uart::{Async, Config, UartRx, UartTx}; use embassy_rp::uart::{Async, Config, UartRx, UartTx};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
@ -17,7 +18,13 @@ async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default()); let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default());
let uart_rx = UartRx::new(p.UART1, p.PIN_5, p.DMA_CH1, Config::default()); let uart_rx = UartRx::new(
p.UART1,
p.PIN_5,
interrupt::take!(UART1_IRQ),
p.DMA_CH1,
Config::default(),
);
unwrap!(spawner.spawn(reader(uart_rx))); unwrap!(spawner.spawn(reader(uart_rx)));

View File

@ -6,19 +6,19 @@ use defmt::*;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::gpio::{self, Pin}; use embassy_rp::gpio::{self, Pin};
use embassy_rp::pio::{ use embassy_rp::pio::{
FifoJoin, PioInstance, PioPeripheral, PioStateMachine, PioStateMachineInstance, ShiftDirection, SmInstance, FifoJoin, Pio, PioCommon, PioInstance, PioStateMachine, PioStateMachineInstance, ShiftDirection,
}; };
use embassy_rp::pio_instr_util; use embassy_rp::pio_instr_util;
use embassy_rp::relocate::RelocatedProgram; use embassy_rp::relocate::RelocatedProgram;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use smart_leds::RGB8; use smart_leds::RGB8;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
pub struct Ws2812<P: PioInstance, S: SmInstance> { pub struct Ws2812<'d, P: PioInstance, const S: usize> {
sm: PioStateMachineInstance<P, S>, sm: PioStateMachineInstance<'d, P, S>,
} }
impl<P: PioInstance, S: SmInstance> Ws2812<P, S> { impl<'d, P: PioInstance, const S: usize> Ws2812<'d, P, S> {
pub fn new(mut sm: PioStateMachineInstance<P, S>, pin: gpio::AnyPin) -> Self { pub fn new(mut pio: PioCommon<'d, P>, mut sm: PioStateMachineInstance<'d, P, S>, pin: gpio::AnyPin) -> Self {
// Setup sm0 // Setup sm0
// prepare the PIO program // prepare the PIO program
@ -49,11 +49,11 @@ impl<P: PioInstance, S: SmInstance> Ws2812<P, S> {
let prg = a.assemble_with_wrap(wrap_source, wrap_target); let prg = a.assemble_with_wrap(wrap_source, wrap_target);
let relocated = RelocatedProgram::new(&prg); let relocated = RelocatedProgram::new(&prg);
sm.write_instr(relocated.origin() as usize, relocated.code()); pio.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin()); pio_instr_util::exec_jmp(&mut sm, relocated.origin());
// Pin config // Pin config
let out_pin = sm.make_pio_pin(pin); let out_pin = pio.make_pio_pin(pin);
sm.set_set_pins(&[&out_pin]); sm.set_set_pins(&[&out_pin]);
sm.set_sideset_base_pin(&out_pin); sm.set_sideset_base_pin(&out_pin);
sm.set_sideset_count(1); sm.set_sideset_count(1);
@ -115,7 +115,7 @@ async fn main(_spawner: Spawner) {
info!("Start"); info!("Start");
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
let (_pio0, sm0, _sm1, _sm2, _sm3) = p.PIO0.split(); let Pio { common, sm0, .. } = Pio::new(p.PIO0);
// This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit
// feather boards for the 2040 both have one built in. // feather boards for the 2040 both have one built in.
@ -124,7 +124,7 @@ async fn main(_spawner: Spawner) {
// For the thing plus, use pin 8 // For the thing plus, use pin 8
// For the feather, use pin 16 // For the feather, use pin 16
let mut ws2812 = Ws2812::new(sm0, p.PIN_8.degrade()); let mut ws2812 = Ws2812::new(common, sm0, p.PIN_8.degrade());
// Loop forever making RGB values and pushing them out to the WS2812. // Loop forever making RGB values and pushing them out to the WS2812.
loop { loop {

View File

@ -26,6 +26,7 @@ nb = "1.0.0"
embedded-storage = "0.3.0" embedded-storage = "0.3.0"
micromath = "2.0.0" micromath = "2.0.0"
static_cell = "1.0" static_cell = "1.0"
chrono = { version = "^0.4", default-features = false}
[profile.release] [profile.release]
debug = 2 debug = 2

View File

@ -0,0 +1,30 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use chrono::{NaiveDate, NaiveDateTime};
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::rtc::{Rtc, RtcConfig};
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
info!("Hello World!");
let now = NaiveDate::from_ymd_opt(2020, 5, 15)
.unwrap()
.and_hms_opt(10, 30, 15)
.unwrap();
let mut rtc = Rtc::new(p.RTC, RtcConfig::default());
rtc.set_datetime(now.into()).expect("datetime not set");
// In reality the delay would be much longer
Timer::after(Duration::from_millis(20000)).await;
let _then: NaiveDateTime = rtc.now().unwrap().into();
}

View File

@ -14,7 +14,7 @@ embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["de
embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt", "external-lora-phy"], optional = true } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true }
lora-phy = { version = "1", optional = true } lora-phy = { version = "1", optional = true }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }

View File

@ -10,7 +10,7 @@ embassy-executor = { version = "0.2.0", path = "../../embassy-executor", feature
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti"] }
embassy-embedded-hal = {version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-embedded-hal = {version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt", "external-lora-phy"] } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
lora-phy = { version = "1" } lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] }

View File

@ -0,0 +1,63 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::{info, unwrap};
use embassy_executor::Executor;
use embassy_executor::_export::StaticCell;
use embassy_rp::gpio::{Input, Level, Output, Pull};
use embassy_rp::multicore::{spawn_core1, Stack};
use embassy_rp::peripherals::{PIN_0, PIN_1};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel;
use {defmt_rtt as _, panic_probe as _};
static mut CORE1_STACK: Stack<1024> = Stack::new();
static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
static CHANNEL0: Channel<CriticalSectionRawMutex, (), 1> = Channel::new();
static CHANNEL1: Channel<CriticalSectionRawMutex, (), 1> = Channel::new();
#[cortex_m_rt::entry]
fn main() -> ! {
let p = embassy_rp::init(Default::default());
spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
let executor1 = EXECUTOR1.init(Executor::new());
executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(p.PIN_1))));
});
let executor0 = EXECUTOR0.init(Executor::new());
executor0.run(|spawner| unwrap!(spawner.spawn(core0_task(p.PIN_0))));
}
#[embassy_executor::task]
async fn core0_task(p: PIN_0) {
info!("CORE0 is running");
let mut pin = Output::new(p, Level::Low);
CHANNEL0.send(()).await;
CHANNEL1.recv().await;
pin.set_high();
CHANNEL1.recv().await;
info!("Test OK");
cortex_m::asm::bkpt();
}
#[embassy_executor::task]
async fn core1_task(p: PIN_1) {
info!("CORE1 is running");
CHANNEL0.recv().await;
let mut pin = Input::new(p, Pull::Down);
let wait = pin.wait_for_rising_edge();
CHANNEL1.send(()).await;
wait.await;
CHANNEL1.send(()).await;
}

View File

@ -4,28 +4,165 @@
use defmt::{assert_eq, *}; use defmt::{assert_eq, *};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::uart::{Config, Uart}; use embassy_rp::gpio::{Level, Output};
use embassy_rp::uart::{Blocking, Config, Error, Instance, Parity, Uart, UartRx};
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
fn read<const N: usize>(uart: &mut Uart<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> {
let mut buf = [255; N];
uart.blocking_read(&mut buf)?;
Ok(buf)
}
fn read1<const N: usize>(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> {
let mut buf = [255; N];
uart.blocking_read(&mut buf)?;
Ok(buf)
}
async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option<bool>) {
pin.set_low();
Timer::after(Duration::from_millis(1)).await;
for i in 0..8 {
if v & (1 << i) == 0 {
pin.set_low();
} else {
pin.set_high();
}
Timer::after(Duration::from_millis(1)).await;
}
if let Some(b) = parity {
if b {
pin.set_high();
} else {
pin.set_low();
}
Timer::after(Duration::from_millis(1)).await;
}
pin.set_high();
Timer::after(Duration::from_millis(1)).await;
}
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
info!("Hello World!"); info!("Hello World!");
let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0); let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0);
{
let config = Config::default(); let config = Config::default();
let mut uart = Uart::new_blocking(uart, tx, rx, config); let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config);
// We can't send too many bytes, they have to fit in the FIFO. // We can't send too many bytes, they have to fit in the FIFO.
// This is because we aren't sending+receiving at the same time. // This is because we aren't sending+receiving at the same time.
let data = [0xC0, 0xDE]; let data = [0xC0, 0xDE];
uart.blocking_write(&data).unwrap(); uart.blocking_write(&data).unwrap();
assert_eq!(read(&mut uart).unwrap(), data);
}
let mut buf = [0; 2]; info!("test overflow detection");
uart.blocking_read(&mut buf).unwrap(); {
assert_eq!(buf, data); let config = Config::default();
let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config);
let data = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32,
];
let overflow = [
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
];
uart.blocking_write(&data).unwrap();
uart.blocking_write(&overflow).unwrap();
while uart.busy() {}
// prefix in fifo is valid
assert_eq!(read(&mut uart).unwrap(), data);
// next received character causes overrun error and is discarded
uart.blocking_write(&[1, 2, 3]).unwrap();
assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Overrun);
assert_eq!(read(&mut uart).unwrap(), [2, 3]);
}
info!("test break detection");
{
let config = Config::default();
let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config);
// break on empty fifo
uart.send_break(20).await;
uart.blocking_write(&[64]).unwrap();
assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Break);
assert_eq!(read(&mut uart).unwrap(), [64]);
// break on partially filled fifo
uart.blocking_write(&[65; 2]).unwrap();
uart.send_break(20).await;
uart.blocking_write(&[66]).unwrap();
assert_eq!(read(&mut uart).unwrap(), [65; 2]);
assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Break);
assert_eq!(read(&mut uart).unwrap(), [66]);
}
// parity detection. here we bitbang to not require two uarts.
info!("test parity error detection");
{
let mut pin = Output::new(&mut tx, Level::High);
let mut config = Config::default();
config.baudrate = 1000;
config.parity = Parity::ParityEven;
let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config);
async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: u8) {
send(pin, v, Some(parity != 0)).await;
}
// first check that we can send correctly
chr(&mut pin, 64, 1).await;
assert_eq!(read1(&mut uart).unwrap(), [64]);
// all good, check real errors
chr(&mut pin, 2, 1).await;
chr(&mut pin, 3, 0).await;
chr(&mut pin, 4, 0).await;
chr(&mut pin, 5, 0).await;
assert_eq!(read1(&mut uart).unwrap(), [2, 3]);
assert_eq!(read1::<1>(&mut uart).unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).unwrap(), [5]);
}
// framing error detection. here we bitbang because there's no other way.
info!("test framing error detection");
{
let mut pin = Output::new(&mut tx, Level::High);
let mut config = Config::default();
config.baudrate = 1000;
let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config);
async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, good: bool) {
if good {
send(pin, v, None).await;
} else {
send(pin, v, Some(false)).await;
}
}
// first check that we can send correctly
chr(&mut pin, 64, true).await;
assert_eq!(read1(&mut uart).unwrap(), [64]);
// all good, check real errors
chr(&mut pin, 2, true).await;
chr(&mut pin, 3, true).await;
chr(&mut pin, 4, false).await;
chr(&mut pin, 5, true).await;
assert_eq!(read1(&mut uart).unwrap(), [2, 3]);
assert_eq!(read1::<1>(&mut uart).unwrap_err(), Error::Framing);
assert_eq!(read1(&mut uart).unwrap(), [5]);
}
info!("Test OK"); info!("Test OK");
cortex_m::asm::bkpt(); cortex_m::asm::bkpt();

View File

@ -2,39 +2,248 @@
#![no_main] #![no_main]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
use defmt::{assert_eq, *}; use defmt::{assert_eq, panic, *};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::interrupt; use embassy_rp::interrupt;
use embassy_rp::uart::{BufferedUart, Config}; use embassy_rp::uart::{BufferedUart, BufferedUartRx, Config, Error, Instance, Parity};
use embedded_io::asynch::{Read, Write}; use embassy_time::{Duration, Timer};
use embedded_io::asynch::{Read, ReadExactError, Write};
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
async fn read<const N: usize>(uart: &mut BufferedUart<'_, impl Instance>) -> Result<[u8; N], Error> {
let mut buf = [255; N];
match uart.read_exact(&mut buf).await {
Ok(()) => Ok(buf),
// we should not ever produce an Eof condition
Err(ReadExactError::UnexpectedEof) => panic!(),
Err(ReadExactError::Other(e)) => Err(e),
}
}
async fn read1<const N: usize>(uart: &mut BufferedUartRx<'_, impl Instance>) -> Result<[u8; N], Error> {
let mut buf = [255; N];
match uart.read_exact(&mut buf).await {
Ok(()) => Ok(buf),
// we should not ever produce an Eof condition
Err(ReadExactError::UnexpectedEof) => panic!(),
Err(ReadExactError::Other(e)) => Err(e),
}
}
async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option<bool>) {
pin.set_low();
Timer::after(Duration::from_millis(1)).await;
for i in 0..8 {
if v & (1 << i) == 0 {
pin.set_low();
} else {
pin.set_high();
}
Timer::after(Duration::from_millis(1)).await;
}
if let Some(b) = parity {
if b {
pin.set_high();
} else {
pin.set_low();
}
Timer::after(Duration::from_millis(1)).await;
}
pin.set_high();
Timer::after(Duration::from_millis(1)).await;
}
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
info!("Hello World!"); info!("Hello World!");
let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0); let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0);
let mut irq = interrupt::take!(UART0_IRQ);
{
let config = Config::default(); let config = Config::default();
let irq = interrupt::take!(UART0_IRQ);
let tx_buf = &mut [0u8; 16]; let tx_buf = &mut [0u8; 16];
let rx_buf = &mut [0u8; 16]; let rx_buf = &mut [0u8; 16];
let mut uart = BufferedUart::new(uart, irq, tx, rx, tx_buf, rx_buf, config); let mut uart = BufferedUart::new(&mut uart, &mut irq, &mut tx, &mut rx, tx_buf, rx_buf, config);
// Make sure we send more bytes than fits in the FIFO, to test the actual // Make sure we send more bytes than fits in the FIFO, to test the actual
// bufferedUart. // bufferedUart.
let data = [ let data = [
1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
]; ];
uart.write_all(&data).await.unwrap(); uart.write_all(&data).await.unwrap();
info!("Done writing"); info!("Done writing");
let mut buf = [0; 31]; assert_eq!(read(&mut uart).await.unwrap(), data);
uart.read_exact(&mut buf).await.unwrap(); }
assert_eq!(buf, data);
info!("test overflow detection");
{
let config = Config::default();
let tx_buf = &mut [0u8; 16];
let rx_buf = &mut [0u8; 16];
let mut uart = BufferedUart::new(&mut uart, &mut irq, &mut tx, &mut rx, tx_buf, rx_buf, config);
// Make sure we send more bytes than fits in the FIFO, to test the actual
// bufferedUart.
let data = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
];
let overflow = [
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
];
// give each block time to settle into the fifo. we want the overrun to occur at a well-defined point.
uart.write_all(&data).await.unwrap();
uart.blocking_flush().unwrap();
while uart.busy() {}
uart.write_all(&overflow).await.unwrap();
uart.blocking_flush().unwrap();
while uart.busy() {}
// already buffered/fifod prefix is valid
assert_eq!(read(&mut uart).await.unwrap(), data);
// next received character causes overrun error and is discarded
uart.write_all(&[1, 2, 3]).await.unwrap();
uart.blocking_flush().unwrap();
assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Overrun);
assert_eq!(read(&mut uart).await.unwrap(), [2, 3]);
}
info!("test break detection");
{
let mut config = Config::default();
config.baudrate = 1000;
let tx_buf = &mut [0u8; 16];
let rx_buf = &mut [0u8; 16];
let mut uart = BufferedUart::new(&mut uart, &mut irq, &mut tx, &mut rx, tx_buf, rx_buf, config);
// break on empty buffer
uart.send_break(20).await;
assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break);
uart.write_all(&[64]).await.unwrap();
assert_eq!(read(&mut uart).await.unwrap(), [64]);
// break on partially filled buffer
uart.write_all(&[65; 2]).await.unwrap();
uart.send_break(20).await;
uart.write_all(&[66]).await.unwrap();
assert_eq!(read(&mut uart).await.unwrap(), [65; 2]);
assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break);
assert_eq!(read(&mut uart).await.unwrap(), [66]);
// break on full buffer
uart.write_all(&[64; 16]).await.unwrap();
uart.send_break(20).await;
uart.write_all(&[65]).await.unwrap();
assert_eq!(read(&mut uart).await.unwrap(), [64; 16]);
assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break);
assert_eq!(read(&mut uart).await.unwrap(), [65]);
}
// parity detection. here we bitbang to not require two uarts.
info!("test parity error detection");
{
let mut pin = Output::new(&mut tx, Level::High);
// choose a very slow baud rate to make tests reliable even with O0
let mut config = Config::default();
config.baudrate = 1000;
config.parity = Parity::ParityEven;
let rx_buf = &mut [0u8; 16];
let mut uart = BufferedUartRx::new(&mut uart, &mut irq, &mut rx, rx_buf, config);
async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: u32) {
send(pin, v, Some(parity != 0)).await;
}
// first check that we can send correctly
chr(&mut pin, 64, 1).await;
assert_eq!(read1(&mut uart).await.unwrap(), [64]);
// parity on empty buffer
chr(&mut pin, 64, 0).await;
chr(&mut pin, 4, 1).await;
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).await.unwrap(), [4]);
// parity on partially filled buffer
chr(&mut pin, 64, 1).await;
chr(&mut pin, 32, 1).await;
chr(&mut pin, 64, 0).await;
chr(&mut pin, 65, 0).await;
assert_eq!(read1(&mut uart).await.unwrap(), [64, 32]);
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).await.unwrap(), [65]);
// parity on full buffer
for i in 0..16 {
chr(&mut pin, i, i.count_ones() % 2).await;
}
chr(&mut pin, 64, 0).await;
chr(&mut pin, 65, 0).await;
assert_eq!(
read1(&mut uart).await.unwrap(),
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
);
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).await.unwrap(), [65]);
}
// framing error detection. here we bitbang because there's no other way.
info!("test framing error detection");
{
let mut pin = Output::new(&mut tx, Level::High);
// choose a very slow baud rate to make tests reliable even with O0
let mut config = Config::default();
config.baudrate = 1000;
let rx_buf = &mut [0u8; 16];
let mut uart = BufferedUartRx::new(&mut uart, &mut irq, &mut rx, rx_buf, config);
async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, good: bool) {
if good {
send(pin, v, None).await;
} else {
send(pin, v, Some(false)).await;
}
}
// first check that we can send correctly
chr(&mut pin, 64, true).await;
assert_eq!(read1(&mut uart).await.unwrap(), [64]);
// framing on empty buffer
chr(&mut pin, 64, false).await;
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing);
chr(&mut pin, 65, true).await;
assert_eq!(read1(&mut uart).await.unwrap(), [65]);
// framing on partially filled buffer
chr(&mut pin, 64, true).await;
chr(&mut pin, 32, true).await;
chr(&mut pin, 64, false).await;
chr(&mut pin, 65, true).await;
assert_eq!(read1(&mut uart).await.unwrap(), [64, 32]);
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing);
assert_eq!(read1(&mut uart).await.unwrap(), [65]);
// framing on full buffer
for i in 0..16 {
chr(&mut pin, i, true).await;
}
chr(&mut pin, 64, false).await;
chr(&mut pin, 65, true).await;
assert_eq!(
read1(&mut uart).await.unwrap(),
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
);
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing);
assert_eq!(read1(&mut uart).await.unwrap(), [65]);
}
info!("Test OK"); info!("Test OK");
cortex_m::asm::bkpt(); cortex_m::asm::bkpt();

View File

@ -4,21 +4,68 @@
use defmt::{assert_eq, *}; use defmt::{assert_eq, *};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::uart::{Config, Uart}; use embassy_rp::gpio::{Level, Output};
use embassy_rp::interrupt;
use embassy_rp::uart::{Async, Config, Error, Instance, Parity, Uart, UartRx};
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
async fn read<const N: usize>(uart: &mut Uart<'_, impl Instance, Async>) -> Result<[u8; N], Error> {
let mut buf = [255; N];
uart.read(&mut buf).await?;
Ok(buf)
}
async fn read1<const N: usize>(uart: &mut UartRx<'_, impl Instance, Async>) -> Result<[u8; N], Error> {
let mut buf = [255; N];
uart.read(&mut buf).await?;
Ok(buf)
}
async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option<bool>) {
pin.set_low();
Timer::after(Duration::from_millis(1)).await;
for i in 0..8 {
if v & (1 << i) == 0 {
pin.set_low();
} else {
pin.set_high();
}
Timer::after(Duration::from_millis(1)).await;
}
if let Some(b) = parity {
if b {
pin.set_high();
} else {
pin.set_low();
}
Timer::after(Duration::from_millis(1)).await;
}
pin.set_high();
Timer::after(Duration::from_millis(1)).await;
}
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let mut p = embassy_rp::init(Default::default());
info!("Hello World!"); info!("Hello World!");
let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0); let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0);
let mut irq = interrupt::take!(UART0_IRQ);
let config = Config::default();
let mut uart = Uart::new(uart, tx, rx, p.DMA_CH0, p.DMA_CH1, config);
// We can't send too many bytes, they have to fit in the FIFO. // We can't send too many bytes, they have to fit in the FIFO.
// This is because we aren't sending+receiving at the same time. // This is because we aren't sending+receiving at the same time.
{
let config = Config::default();
let mut uart = Uart::new(
&mut uart,
&mut tx,
&mut rx,
&mut irq,
&mut p.DMA_CH0,
&mut p.DMA_CH1,
config,
);
let data = [0xC0, 0xDE]; let data = [0xC0, 0xDE];
uart.write(&data).await.unwrap(); uart.write(&data).await.unwrap();
@ -26,6 +73,173 @@ async fn main(_spawner: Spawner) {
let mut buf = [0; 2]; let mut buf = [0; 2];
uart.read(&mut buf).await.unwrap(); uart.read(&mut buf).await.unwrap();
assert_eq!(buf, data); assert_eq!(buf, data);
}
info!("test overflow detection");
{
let config = Config::default();
let mut uart = Uart::new(
&mut uart,
&mut tx,
&mut rx,
&mut irq,
&mut p.DMA_CH0,
&mut p.DMA_CH1,
config,
);
uart.blocking_write(&[42; 32]).unwrap();
uart.blocking_write(&[1, 2, 3]).unwrap();
uart.blocking_flush().unwrap();
// can receive regular fifo contents
assert_eq!(read(&mut uart).await, Ok([42; 16]));
assert_eq!(read(&mut uart).await, Ok([42; 16]));
// receiving the rest fails with overrun
assert_eq!(read::<16>(&mut uart).await, Err(Error::Overrun));
// new data is accepted, latest overrunning byte first
assert_eq!(read(&mut uart).await, Ok([3]));
uart.blocking_write(&[8, 9]).unwrap();
Timer::after(Duration::from_millis(1)).await;
assert_eq!(read(&mut uart).await, Ok([8, 9]));
}
info!("test break detection");
{
let config = Config::default();
let (mut tx, mut rx) = Uart::new(
&mut uart,
&mut tx,
&mut rx,
&mut irq,
&mut p.DMA_CH0,
&mut p.DMA_CH1,
config,
)
.split();
// break before read
tx.send_break(20).await;
tx.write(&[64]).await.unwrap();
assert_eq!(read1::<1>(&mut rx).await.unwrap_err(), Error::Break);
assert_eq!(read1(&mut rx).await.unwrap(), [64]);
// break during read
{
let r = read1::<2>(&mut rx);
tx.write(&[2]).await.unwrap();
tx.send_break(20).await;
tx.write(&[3]).await.unwrap();
assert_eq!(r.await.unwrap_err(), Error::Break);
assert_eq!(read1(&mut rx).await.unwrap(), [3]);
}
// break after read
{
let r = read1(&mut rx);
tx.write(&[2]).await.unwrap();
tx.send_break(20).await;
tx.write(&[3]).await.unwrap();
assert_eq!(r.await.unwrap(), [2]);
assert_eq!(read1::<1>(&mut rx).await.unwrap_err(), Error::Break);
assert_eq!(read1(&mut rx).await.unwrap(), [3]);
}
}
// parity detection. here we bitbang to not require two uarts.
info!("test parity error detection");
{
let mut pin = Output::new(&mut tx, Level::High);
// choose a very slow baud rate to make tests reliable even with O0
let mut config = Config::default();
config.baudrate = 1000;
config.parity = Parity::ParityEven;
let mut uart = UartRx::new(&mut uart, &mut rx, &mut irq, &mut p.DMA_CH0, config);
async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: u32) {
send(pin, v, Some(parity != 0)).await;
}
// first check that we can send correctly
chr(&mut pin, 32, 1).await;
assert_eq!(read1(&mut uart).await.unwrap(), [32]);
// parity error before read
chr(&mut pin, 32, 0).await;
chr(&mut pin, 31, 1).await;
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).await.unwrap(), [31]);
// parity error during read
{
let r = read1::<2>(&mut uart);
chr(&mut pin, 2, 1).await;
chr(&mut pin, 32, 0).await;
chr(&mut pin, 3, 0).await;
assert_eq!(r.await.unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).await.unwrap(), [3]);
}
// parity error after read
{
let r = read1(&mut uart);
chr(&mut pin, 2, 1).await;
chr(&mut pin, 32, 0).await;
chr(&mut pin, 3, 0).await;
assert_eq!(r.await.unwrap(), [2]);
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity);
assert_eq!(read1(&mut uart).await.unwrap(), [3]);
}
}
// framing error detection. here we bitbang because there's no other way.
info!("test framing error detection");
{
let mut pin = Output::new(&mut tx, Level::High);
// choose a very slow baud rate to make tests reliable even with O0
let mut config = Config::default();
config.baudrate = 1000;
let mut uart = UartRx::new(&mut uart, &mut rx, &mut irq, &mut p.DMA_CH0, config);
async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, good: bool) {
if good {
send(pin, v, None).await;
} else {
send(pin, v, Some(false)).await;
}
}
// first check that we can send correctly
chr(&mut pin, 32, true).await;
assert_eq!(read1(&mut uart).await.unwrap(), [32]);
// parity error before read
chr(&mut pin, 32, false).await;
chr(&mut pin, 31, true).await;
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing);
assert_eq!(read1(&mut uart).await.unwrap(), [31]);
// parity error during read
{
let r = read1::<2>(&mut uart);
chr(&mut pin, 2, true).await;
chr(&mut pin, 32, false).await;
chr(&mut pin, 3, true).await;
assert_eq!(r.await.unwrap_err(), Error::Framing);
assert_eq!(read1(&mut uart).await.unwrap(), [3]);
}
// parity error after read
{
let r = read1(&mut uart);
chr(&mut pin, 2, true).await;
chr(&mut pin, 32, false).await;
chr(&mut pin, 3, true).await;
assert_eq!(r.await.unwrap(), [2]);
assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing);
assert_eq!(read1(&mut uart).await.unwrap(), [3]);
}
}
info!("Test OK"); info!("Test OK");
cortex_m::asm::bkpt(); cortex_m::asm::bkpt();

View File

@ -3,7 +3,7 @@ build-std = ["core"]
build-std-features = ["panic_immediate_abort"] build-std-features = ["panic_immediate_abort"]
[target.'cfg(all(target_arch = "arm", target_os = "none"))'] [target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" runner = "teleprobe client run --target nucleo-stm32f429zi --elf"
#runner = "teleprobe local run --chip STM32F103C8 --elf" #runner = "teleprobe local run --chip STM32F103C8 --elf"
rustflags = [ rustflags = [

View File

@ -3,25 +3,29 @@ edition = "2021"
name = "embassy-stm32-tests" name = "embassy-stm32-tests"
version = "0.1.0" version = "0.1.0"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
autobins = false
[features] [features]
stm32f103c8 = ["embassy-stm32/stm32f103c8"] # Blue Pill stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill
stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc"] # Nucleo stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono", "not-gpdma"] # Nucleo
stm32g071rb = ["embassy-stm32/stm32g071rb"] # Nucleo stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo
stm32c031c6 = ["embassy-stm32/stm32c031c6"] # Nucleo stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo
stm32g491re = ["embassy-stm32/stm32g491re"] # Nucleo stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo
stm32h755zi = ["embassy-stm32/stm32h755zi-cm7"] # Nucleo stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
stm32wb55rg = ["embassy-stm32/stm32wb55rg"] # Nucleo stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma"] # Nucleo
stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo
stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board
sdmmc = [] sdmmc = []
chrono = ["embassy-stm32/chrono", "dep:chrono"]
not-gpdma = []
[dependencies] [dependencies]
embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
defmt = "0.3.0" defmt = "0.3.0"
defmt-rtt = "0.4" defmt-rtt = "0.4"
@ -32,6 +36,10 @@ embedded-hal = "0.2.6"
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
embedded-hal-async = { version = "=0.2.0-alpha.1" } embedded-hal-async = { version = "=0.2.0-alpha.1" }
panic-probe = { version = "0.3.0", features = ["print-defmt"] } panic-probe = { version = "0.3.0", features = ["print-defmt"] }
rand_core = { version = "0.6", default-features = false }
rand_chacha = { version = "0.3", default-features = false }
chrono = { version = "^0.4", default-features = false, optional = true}
# BEGIN TESTS # BEGIN TESTS
# Generated by gen_test.py. DO NOT EDIT. # Generated by gen_test.py. DO NOT EDIT.
@ -40,6 +48,11 @@ name = "gpio"
path = "src/bin/gpio.rs" path = "src/bin/gpio.rs"
required-features = [] required-features = []
[[bin]]
name = "rtc"
path = "src/bin/rtc.rs"
required-features = [ "chrono",]
[[bin]] [[bin]]
name = "sdmmc" name = "sdmmc"
path = "src/bin/sdmmc.rs" path = "src/bin/sdmmc.rs"
@ -70,6 +83,11 @@ name = "usart_dma"
path = "src/bin/usart_dma.rs" path = "src/bin/usart_dma.rs"
required-features = [] required-features = []
[[bin]]
name = "usart_rx_ringbuffered"
path = "src/bin/usart_rx_ringbuffered.rs"
required-features = [ "not-gpdma",]
# END TESTS # END TESTS
[profile.dev] [profile.dev]

View File

@ -6,15 +6,20 @@ fn main() -> Result<(), Box<dyn Error>> {
let out = PathBuf::from(env::var("OUT_DIR").unwrap()); let out = PathBuf::from(env::var("OUT_DIR").unwrap());
fs::write(out.join("link_ram.x"), include_bytes!("link_ram.x")).unwrap(); fs::write(out.join("link_ram.x"), include_bytes!("link_ram.x")).unwrap();
println!("cargo:rustc-link-search={}", out.display()); println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=link_ram.x");
println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=--nmagic");
// too little RAM to run from RAM. // too little RAM to run from RAM.
#[cfg(any(feature = "stm32c031c6"))] if cfg!(any(
feature = "stm32f429zi",
feature = "stm32f103c8",
feature = "stm32c031c6"
)) {
println!("cargo:rustc-link-arg-bins=-Tlink.x"); println!("cargo:rustc-link-arg-bins=-Tlink.x");
#[cfg(not(any(feature = "stm32c031c6")))] println!("cargo:rerun-if-changed=link.x");
} else {
println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); println!("cargo:rustc-link-arg-bins=-Tlink_ram.x");
println!("cargo:rerun-if-changed=link_ram.x");
}
println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x");

View File

@ -0,0 +1,52 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
// required-features: chrono
#[path = "../example_common.rs"]
mod example_common;
use chrono::{NaiveDate, NaiveDateTime};
use defmt::assert;
use embassy_executor::Spawner;
use embassy_stm32::pac;
use embassy_stm32::rtc::{Rtc, RtcConfig};
use embassy_time::{Duration, Timer};
use example_common::*;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(config());
info!("Hello World!");
let now = NaiveDate::from_ymd_opt(2020, 5, 15)
.unwrap()
.and_hms_opt(10, 30, 15)
.unwrap();
info!("Starting LSI");
unsafe {
pac::RCC.csr().modify(|w| w.set_lsion(true));
while !pac::RCC.csr().read().lsirdy() {}
}
info!("Started LSI");
let mut rtc = Rtc::new(p.RTC, RtcConfig::default());
rtc.set_datetime(now.into()).expect("datetime not set");
info!("Waiting 5 seconds");
Timer::after(Duration::from_millis(5000)).await;
let then: NaiveDateTime = rtc.now().unwrap().into();
let seconds = (then - now).num_seconds();
defmt::info!("measured = {}", seconds);
assert!(seconds > 3 && seconds < 7);
info!("Test OK");
cortex_m::asm::bkpt();
}

View File

@ -35,7 +35,6 @@ async fn main(_spawner: Spawner) {
#[cfg(feature = "stm32c031c6")] #[cfg(feature = "stm32c031c6")]
let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6);
info!("asdfa;");
let mut spi = Spi::new( let mut spi = Spi::new(
spi, spi,
sck, // Arduino D13 sck, // Arduino D13

View File

@ -9,6 +9,7 @@ use embassy_executor::Spawner;
use embassy_stm32::dma::NoDma; use embassy_stm32::dma::NoDma;
use embassy_stm32::interrupt; use embassy_stm32::interrupt;
use embassy_stm32::usart::{Config, Uart}; use embassy_stm32::usart::{Config, Uart};
use embassy_time::{Duration, Instant};
use example_common::*; use example_common::*;
#[embassy_executor::main] #[embassy_executor::main]
@ -19,26 +20,27 @@ async fn main(_spawner: Spawner) {
// Arduino pins D0 and D1 // Arduino pins D0 and D1
// They're connected together with a 1K resistor. // They're connected together with a 1K resistor.
#[cfg(feature = "stm32f103c8")] #[cfg(feature = "stm32f103c8")]
let (tx, rx, usart, irq) = (p.PA9, p.PA10, p.USART1, interrupt::take!(USART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PA9, p.PA10, p.USART1, interrupt::take!(USART1));
#[cfg(feature = "stm32g491re")] #[cfg(feature = "stm32g491re")]
let (tx, rx, usart, irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1));
#[cfg(feature = "stm32g071rb")] #[cfg(feature = "stm32g071rb")]
let (tx, rx, usart, irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1));
#[cfg(feature = "stm32f429zi")] #[cfg(feature = "stm32f429zi")]
let (tx, rx, usart, irq) = (p.PG14, p.PG9, p.USART6, interrupt::take!(USART6)); let (mut tx, mut rx, mut usart, mut irq) = (p.PG14, p.PG9, p.USART6, interrupt::take!(USART6));
#[cfg(feature = "stm32wb55rg")] #[cfg(feature = "stm32wb55rg")]
let (tx, rx, usart, irq) = (p.PA2, p.PA3, p.LPUART1, interrupt::take!(LPUART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PA2, p.PA3, p.LPUART1, interrupt::take!(LPUART1));
#[cfg(feature = "stm32h755zi")] #[cfg(feature = "stm32h755zi")]
let (tx, rx, usart, irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1));
#[cfg(feature = "stm32u585ai")] #[cfg(feature = "stm32u585ai")]
let (tx, rx, usart, irq) = (p.PD8, p.PD9, p.USART3, interrupt::take!(USART3)); let (mut tx, mut rx, mut usart, mut irq) = (p.PD8, p.PD9, p.USART3, interrupt::take!(USART3));
#[cfg(feature = "stm32h563zi")] #[cfg(feature = "stm32h563zi")]
let (tx, rx, usart, irq) = (p.PB6, p.PB7, p.LPUART1, interrupt::take!(LPUART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PB6, p.PB7, p.LPUART1, interrupt::take!(LPUART1));
#[cfg(feature = "stm32c031c6")] #[cfg(feature = "stm32c031c6")]
let (tx, rx, usart, irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1)); let (mut tx, mut rx, mut usart, mut irq) = (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1));
{
let config = Config::default(); let config = Config::default();
let mut usart = Uart::new(usart, rx, tx, irq, NoDma, NoDma, config); let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, &mut irq, NoDma, NoDma, config);
// We can't send too many bytes, they have to fit in the FIFO. // We can't send too many bytes, they have to fit in the FIFO.
// This is because we aren't sending+receiving at the same time. // This is because we aren't sending+receiving at the same time.
@ -49,6 +51,45 @@ async fn main(_spawner: Spawner) {
let mut buf = [0; 2]; let mut buf = [0; 2];
usart.blocking_read(&mut buf).unwrap(); usart.blocking_read(&mut buf).unwrap();
assert_eq!(buf, data); assert_eq!(buf, data);
}
// Test that baudrate divider is calculated correctly.
// Do it by comparing the time it takes to send a known number of bytes.
for baudrate in [
300,
9600,
115200,
250_000,
337_934,
#[cfg(not(feature = "stm32f103c8"))]
1_000_000,
#[cfg(not(feature = "stm32f103c8"))]
2_000_000,
] {
info!("testing baudrate {}", baudrate);
let mut config = Config::default();
config.baudrate = baudrate;
let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, &mut irq, NoDma, NoDma, config);
let n = (baudrate as usize / 100).max(64);
let start = Instant::now();
for _ in 0..n {
usart.blocking_write(&[0x00]).unwrap();
}
let dur = Instant::now() - start;
let want_dur = Duration::from_micros(n as u64 * 10 * 1_000_000 / (baudrate as u64));
let fuzz = want_dur / 5;
if dur < want_dur - fuzz || dur > want_dur + fuzz {
defmt::panic!(
"bad duration for baudrate {}: got {:?} want {:?}",
baudrate,
dur,
want_dur
);
}
}
info!("Test OK"); info!("Test OK");
cortex_m::asm::bkpt(); cortex_m::asm::bkpt();

View File

@ -6,6 +6,7 @@
mod example_common; mod example_common;
use defmt::assert_eq; use defmt::assert_eq;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_stm32::interrupt; use embassy_stm32::interrupt;
use embassy_stm32::usart::{Config, Uart}; use embassy_stm32::usart::{Config, Uart};
use example_common::*; use example_common::*;
@ -76,18 +77,29 @@ async fn main(_spawner: Spawner) {
(p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
let config = Config::default(); let config = Config::default();
let mut usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config);
// We can't send too many bytes, they have to fit in the FIFO. const LEN: usize = 128;
// This is because we aren't sending+receiving at the same time. let mut tx_buf = [0; LEN];
// For whatever reason, blocking works with 2 bytes but DMA only with 1?? let mut rx_buf = [0; LEN];
for i in 0..LEN {
tx_buf[i] = i as u8;
}
let data = [0x42]; let (mut tx, mut rx) = usart.split();
usart.write(&data).await.unwrap();
let mut buf = [0; 1]; let tx_fut = async {
usart.read(&mut buf).await.unwrap(); tx.write(&tx_buf).await.unwrap();
assert_eq!(buf, data); };
let rx_fut = async {
rx.read(&mut rx_buf).await.unwrap();
};
// note: rx needs to be polled first, to workaround this bug:
// https://github.com/embassy-rs/embassy/issues/1426
join(rx_fut, tx_fut).await;
assert_eq!(tx_buf, rx_buf);
info!("Test OK"); info!("Test OK");
cortex_m::asm::bkpt(); cortex_m::asm::bkpt();

View File

@ -0,0 +1,206 @@
// required-features: not-gpdma
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#[path = "../example_common.rs"]
mod example_common;
use defmt::{assert_eq, panic};
use embassy_executor::Spawner;
use embassy_stm32::interrupt;
use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx};
use embassy_time::{Duration, Timer};
use example_common::*;
use rand_chacha::ChaCha8Rng;
use rand_core::{RngCore, SeedableRng};
#[cfg(feature = "stm32f103c8")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART1;
pub type TxDma = embassy_stm32::peripherals::DMA1_CH4;
pub type RxDma = embassy_stm32::peripherals::DMA1_CH5;
}
#[cfg(feature = "stm32g491re")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART1;
pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
}
#[cfg(feature = "stm32g071rb")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART1;
pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
}
#[cfg(feature = "stm32f429zi")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART6;
pub type TxDma = embassy_stm32::peripherals::DMA2_CH6;
pub type RxDma = embassy_stm32::peripherals::DMA2_CH1;
}
#[cfg(feature = "stm32wb55rg")]
mod board {
pub type Uart = embassy_stm32::peripherals::LPUART1;
pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
}
#[cfg(feature = "stm32h755zi")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART1;
pub type TxDma = embassy_stm32::peripherals::DMA1_CH0;
pub type RxDma = embassy_stm32::peripherals::DMA1_CH1;
}
#[cfg(feature = "stm32u585ai")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART3;
pub type TxDma = embassy_stm32::peripherals::GPDMA1_CH0;
pub type RxDma = embassy_stm32::peripherals::GPDMA1_CH1;
}
#[cfg(feature = "stm32c031c6")]
mod board {
pub type Uart = embassy_stm32::peripherals::USART1;
pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
}
const DMA_BUF_SIZE: usize = 256;
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_stm32::init(config());
info!("Hello World!");
// Arduino pins D0 and D1
// They're connected together with a 1K resistor.
#[cfg(feature = "stm32f103c8")]
let (tx, rx, usart, irq, tx_dma, rx_dma) = (
p.PA9,
p.PA10,
p.USART1,
interrupt::take!(USART1),
p.DMA1_CH4,
p.DMA1_CH5,
);
#[cfg(feature = "stm32g491re")]
let (tx, rx, usart, irq, tx_dma, rx_dma) =
(p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
#[cfg(feature = "stm32g071rb")]
let (tx, rx, usart, irq, tx_dma, rx_dma) =
(p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
#[cfg(feature = "stm32f429zi")]
let (tx, rx, usart, irq, tx_dma, rx_dma) = (
p.PG14,
p.PG9,
p.USART6,
interrupt::take!(USART6),
p.DMA2_CH6,
p.DMA2_CH1,
);
#[cfg(feature = "stm32wb55rg")]
let (tx, rx, usart, irq, tx_dma, rx_dma) = (
p.PA2,
p.PA3,
p.LPUART1,
interrupt::take!(LPUART1),
p.DMA1_CH1,
p.DMA1_CH2,
);
#[cfg(feature = "stm32h755zi")]
let (tx, rx, usart, irq, tx_dma, rx_dma) =
(p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1);
#[cfg(feature = "stm32u585ai")]
let (tx, rx, usart, irq, tx_dma, rx_dma) = (
p.PD8,
p.PD9,
p.USART3,
interrupt::take!(USART3),
p.GPDMA1_CH0,
p.GPDMA1_CH1,
);
#[cfg(feature = "stm32c031c6")]
let (tx, rx, usart, irq, tx_dma, rx_dma) =
(p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
// To run this test, use the saturating_serial test utility to saturate the serial port
let mut config = Config::default();
// this is the fastest we can go without tuning RCC
// some chips have default pclk=8mhz, and uart can run at max pclk/16
config.baudrate = 1_000_000;
config.data_bits = DataBits::DataBits8;
config.stop_bits = StopBits::STOP1;
config.parity = Parity::ParityNone;
let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config);
let (tx, rx) = usart.split();
static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE];
let dma_buf = unsafe { DMA_BUF.as_mut() };
let rx = rx.into_ring_buffered(dma_buf);
info!("Spawning tasks");
spawner.spawn(transmit_task(tx)).unwrap();
spawner.spawn(receive_task(rx)).unwrap();
}
#[embassy_executor::task]
async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) {
// workaround https://github.com/embassy-rs/embassy/issues/1426
Timer::after(Duration::from_millis(100) as _).await;
let mut rng = ChaCha8Rng::seed_from_u64(1337);
info!("Starting random transmissions into void...");
let mut i: u8 = 0;
loop {
let mut buf = [0; 32];
let len = 1 + (rng.next_u32() as usize % buf.len());
for b in &mut buf[..len] {
*b = i;
i = i.wrapping_add(1);
}
tx.write(&buf[..len]).await.unwrap();
info!("sent {=usize}", len);
Timer::after(Duration::from_micros((rng.next_u32() % 500) as _)).await;
}
}
#[embassy_executor::task]
async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::RxDma>) {
info!("Ready to receive...");
let mut rng = ChaCha8Rng::seed_from_u64(1337);
let mut i = 0;
let mut expected = 0;
loop {
let mut buf = [0; 32];
let max_len = 1 + (rng.next_u32() as usize % buf.len());
let received = match rx.read(&mut buf[..max_len]).await {
Ok(r) => r,
Err(e) => {
panic!("Test fail! read error: {:?}", e);
}
};
info!("received {=usize}", received);
for byte in &buf[..received] {
assert_eq!(*byte, expected);
expected = expected.wrapping_add(1);
}
if received < max_len {
Timer::after(Duration::from_micros((rng.next_u32() % 500) as _)).await;
}
i += received;
if i > 1000000 {
info!("Test OK!");
cortex_m::asm::bkpt();
}
}
}

View File

@ -1,22 +1,11 @@
#![macro_use] #![macro_use]
use core::sync::atomic::{AtomicUsize, Ordering};
pub use defmt::*; pub use defmt::*;
#[allow(unused)] #[allow(unused)]
use embassy_stm32::time::Hertz; use embassy_stm32::time::Hertz;
use embassy_stm32::Config; use embassy_stm32::Config;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
defmt::timestamp! {"{=u64}", {
static COUNT: AtomicUsize = AtomicUsize::new(0);
// NOTE(no-CAS) `timestamps` runs with interrupts disabled
let n = COUNT.load(Ordering::Relaxed);
COUNT.store(n + 1, Ordering::Relaxed);
n as u64
}
}
pub fn config() -> Config { pub fn config() -> Config {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut config = Config::default(); let mut config = Config::default();
@ -27,5 +16,10 @@ pub fn config() -> Config {
config.rcc.pll1.q_ck = Some(Hertz(100_000_000)); config.rcc.pll1.q_ck = Some(Hertz(100_000_000));
} }
#[cfg(feature = "stm32u585ai")]
{
config.rcc.mux = embassy_stm32::rcc::ClockSrc::MSI(embassy_stm32::rcc::MSIRange::Range48mhz);
}
config config
} }

10
tests/utils/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "test-utils"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8"
serial = "0.4"

View File

@ -0,0 +1,53 @@
use std::path::Path;
use std::time::Duration;
use std::{env, io, process, thread};
use rand::random;
use serial::SerialPort;
pub fn main() {
if let Some(port_name) = env::args().nth(1) {
let idles = env::args().position(|x| x == "--idles").is_some();
println!("Saturating port {:?} with 115200 8N1", port_name);
println!("Idles: {}", idles);
println!("Process ID: {}", process::id());
let mut port = serial::open(&port_name).unwrap();
if saturate(&mut port, idles).is_err() {
eprintln!("Unable to saturate port");
}
} else {
let path = env::args().next().unwrap();
let basepath = Path::new(&path).with_extension("");
let basename = basepath.file_name().unwrap();
eprintln!("USAGE: {} <port-name>", basename.to_string_lossy());
}
}
fn saturate<T: SerialPort>(port: &mut T, idles: bool) -> io::Result<()> {
port.reconfigure(&|settings| {
settings.set_baud_rate(serial::Baud115200)?;
settings.set_char_size(serial::Bits8);
settings.set_parity(serial::ParityNone);
settings.set_stop_bits(serial::Stop1);
Ok(())
})?;
let mut written = 0;
loop {
let len = random::<usize>() % 0x1000;
let buf: Vec<u8> = (written..written + len).map(|x| x as u8).collect();
port.write_all(&buf)?;
if idles {
let micros = (random::<usize>() % 1000) as u64;
println!("Sleeping {}us", micros);
port.flush().unwrap();
thread::sleep(Duration::from_micros(micros));
}
written += len;
println!("Written: {}", written);
}
}