Compare commits
80 Commits
embassy-ex
...
stm32-ring
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49bc449d7 | ||
|
|
a21a2901ac | ||
|
|
374c92a4f0 | ||
|
|
433422b9f2 | ||
|
|
a85b34c1fe | ||
|
|
1078f6f4e7 | ||
|
|
2bb6e93e86 | ||
|
|
2afa08c923 | ||
|
|
a61701b756 | ||
|
|
7a36072a15 | ||
|
|
a167c77d39 | ||
|
|
8839f3f62a | ||
|
|
ac111f40d8 | ||
|
|
3229b5e809 | ||
|
|
b2047c4351 | ||
|
|
849011b826 | ||
|
|
6cec6fa09b | ||
|
|
0d224a00e1 | ||
|
|
47ae9b7981 | ||
|
|
8e22d57447 | ||
|
|
5f99ccf54c | ||
|
|
54e695b1b2 | ||
|
|
8fc92fdf62 | ||
|
|
c6424fdc11 | ||
|
|
3c31236c10 | ||
|
|
6096f0cf4b | ||
|
|
a1d45303c3 | ||
|
|
7601779693 | ||
|
|
1806422763 | ||
|
|
00cde67abe | ||
|
|
96e8a7ddb9 | ||
|
|
25864ae4dc | ||
|
|
14e0090cb1 | ||
|
|
45843034ec | ||
|
|
7757405908 | ||
|
|
fc268df6f5 | ||
|
|
4ea6662e55 | ||
|
|
49455792cb | ||
|
|
855c0d1423 | ||
|
|
05c36e05f9 | ||
|
|
b58b9ff390 | ||
|
|
1d5adb8974 | ||
|
|
be66e0f7ce | ||
|
|
861f49cfd4 | ||
|
|
7ab9fe0522 | ||
|
|
1c8492bab2 | ||
|
|
19588a9e6f | ||
|
|
1d2f6667df | ||
|
|
ac0ea406f9 | ||
|
|
7336b8cd88 | ||
|
|
bcbe3040a1 | ||
|
|
f4ade6af8b | ||
|
|
fa1ec29ae6 | ||
|
|
58e727d3b9 | ||
|
|
4cd5ed81aa | ||
|
|
4618b79b22 | ||
|
|
db16b6ff3f | ||
|
|
a9074fd09b | ||
|
|
f2469776f4 | ||
|
|
a10850a6da | ||
|
|
ce04b732d1 | ||
|
|
ff6748a0d8 | ||
|
|
7646f18836 | ||
|
|
41fe718ea8 | ||
|
|
94c6727b3f | ||
|
|
b77794c9a7 | ||
|
|
ba886b45b8 | ||
|
|
2119b8e1ca | ||
|
|
49bed094a3 | ||
|
|
49ecd8d7c5 | ||
|
|
29cc661dca | ||
|
|
91cddd50f6 | ||
|
|
9d610c6866 | ||
|
|
d960bf344a | ||
|
|
3ba73b5ff4 | ||
|
|
8c733c29cc | ||
|
|
0d82ebea29 | ||
|
|
e24421a393 | ||
|
|
4de4039417 | ||
|
|
f589247c1f |
@@ -35,7 +35,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
|
||||
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
|
||||
- **LoRa** -
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
|
||||
|
||||
- **USB** -
|
||||
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
|
||||
@@ -7,22 +7,13 @@ license = "MIT OR Apache-2.0"
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
|
||||
features = ["time", "defmt"]
|
||||
flavors = [
|
||||
{ name = "sx126x", target = "thumbv7em-none-eabihf", features = ["sx126x"] },
|
||||
{ name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x"] },
|
||||
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/time-driver-any"] },
|
||||
]
|
||||
|
||||
[lib]
|
||||
features = ["stm32wl", "time", "defmt"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[features]
|
||||
sx126x = []
|
||||
sx127x = []
|
||||
stm32wl = ["dep:embassy-stm32"]
|
||||
time = []
|
||||
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
|
||||
external-lora-phy = ["dep:lora-phy"]
|
||||
defmt = ["dep:defmt", "lorawan-device/defmt"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -39,6 +30,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
|
||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||
bit_field = { version = "0.10" }
|
||||
|
||||
lora-phy = { version = "1", optional = true }
|
||||
lora-phy = { version = "1" }
|
||||
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
|
||||
lorawan = { version = "0.7.3", default-features = false }
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
#![no_std]
|
||||
#![feature(async_fn_in_trait, impl_trait_projections)]
|
||||
#![allow(incomplete_features)]
|
||||
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
|
||||
//! crate's async LoRaWAN MAC implementation.
|
||||
//! embassy-lora holds LoRa-specific functionality.
|
||||
|
||||
pub(crate) mod fmt;
|
||||
#[cfg(feature = "external-lora-phy")]
|
||||
/// interface variants required by the external lora crate
|
||||
pub mod iv;
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
|
||||
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;
|
||||
/// interface variants required by the external lora physical layer crate (lora-phy)
|
||||
pub mod iv;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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, ®_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, ®_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, ®_ana_lna_buffer_original)
|
||||
.await?;
|
||||
self.brd_write_registers(Register::AnaMixer, ®_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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::pac::common::{Reg, RW};
|
||||
use crate::pac::SIO;
|
||||
use crate::{interrupt, pac, peripherals, Peripheral};
|
||||
use crate::{interrupt, pac, peripherals, Peripheral, RegExt};
|
||||
|
||||
const PIN_COUNT: usize = 30;
|
||||
const NEW_AW: AtomicWaker = AtomicWaker::new();
|
||||
@@ -136,16 +136,11 @@ pub enum InterruptTrigger {
|
||||
AnyEdge,
|
||||
}
|
||||
|
||||
impl InterruptTrigger {
|
||||
fn from_u32(value: u32) -> Option<InterruptTrigger> {
|
||||
match value {
|
||||
1 => Some(InterruptTrigger::LevelLow),
|
||||
2 => Some(InterruptTrigger::LevelHigh),
|
||||
3 => Some(InterruptTrigger::EdgeLow),
|
||||
4 => Some(InterruptTrigger::EdgeHigh),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub(crate) unsafe fn init() {
|
||||
let irq = interrupt::IO_IRQ_BANK0::steal();
|
||||
irq.disable();
|
||||
irq.set_priority(interrupt::Priority::P3);
|
||||
irq.enable();
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
@@ -166,27 +161,15 @@ unsafe fn IO_IRQ_BANK0() {
|
||||
let pin_group = (pin % 8) as usize;
|
||||
let event = (intsx.read().0 >> pin_group * 4) & 0xf as u32;
|
||||
|
||||
if let Some(trigger) = InterruptTrigger::from_u32(event) {
|
||||
critical_section::with(|_| {
|
||||
proc_intx.inte(pin / 8).modify(|w| match trigger {
|
||||
InterruptTrigger::AnyEdge => {
|
||||
w.set_edge_high(pin_group, false);
|
||||
w.set_edge_low(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::LevelHigh => {
|
||||
trace!("IO_IRQ_BANK0 pin {} LevelHigh triggered", pin);
|
||||
w.set_level_high(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::LevelLow => {
|
||||
w.set_level_low(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::EdgeHigh => {
|
||||
w.set_edge_high(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::EdgeLow => {
|
||||
w.set_edge_low(pin_group, false);
|
||||
}
|
||||
});
|
||||
// no more than one event can be awaited per pin at any given time, so
|
||||
// we can just clear all interrupt enables for that pin without having
|
||||
// to check which event was signalled.
|
||||
if event != 0 {
|
||||
proc_intx.inte(pin / 8).write_clear(|w| {
|
||||
w.set_edge_high(pin_group, true);
|
||||
w.set_edge_low(pin_group, true);
|
||||
w.set_level_high(pin_group, true);
|
||||
w.set_level_low(pin_group, true);
|
||||
});
|
||||
INTERRUPT_WAKERS[pin as usize].wake();
|
||||
}
|
||||
@@ -203,16 +186,26 @@ impl<'d, T: Pin> InputFuture<'d, T> {
|
||||
pub fn new(pin: impl Peripheral<P = T> + 'd, level: InterruptTrigger) -> Self {
|
||||
into_ref!(pin);
|
||||
unsafe {
|
||||
let irq = interrupt::IO_IRQ_BANK0::steal();
|
||||
irq.disable();
|
||||
irq.set_priority(interrupt::Priority::P3);
|
||||
let pin_group = (pin.pin() % 8) as usize;
|
||||
// first, clear the INTR register bits. without this INTR will still
|
||||
// contain reports of previous edges, causing the IRQ to fire early
|
||||
// on stale state. clearing these means that we can only detect edges
|
||||
// that occur *after* the clear happened, but since both this and the
|
||||
// alternative are fundamentally racy it's probably fine.
|
||||
// (the alternative being checking the current level and waiting for
|
||||
// its inverse, but that requires reading the current level and thus
|
||||
// missing anything that happened before the level was read.)
|
||||
pac::IO_BANK0.intr(pin.pin() as usize / 8).write(|w| {
|
||||
w.set_edge_high(pin_group, true);
|
||||
w.set_edge_low(pin_group, true);
|
||||
});
|
||||
|
||||
// Each INTR register is divided into 8 groups, one group for each
|
||||
// pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW,
|
||||
// and EGDE_HIGH.
|
||||
let pin_group = (pin.pin() % 8) as usize;
|
||||
critical_section::with(|_| {
|
||||
pin.int_proc().inte((pin.pin() / 8) as usize).modify(|w| match level {
|
||||
pin.int_proc()
|
||||
.inte((pin.pin() / 8) as usize)
|
||||
.write_set(|w| match level {
|
||||
InterruptTrigger::LevelHigh => {
|
||||
trace!("InputFuture::new enable LevelHigh for pin {}", pin.pin());
|
||||
w.set_level_high(pin_group, true);
|
||||
@@ -227,12 +220,10 @@ impl<'d, T: Pin> InputFuture<'d, T> {
|
||||
w.set_edge_low(pin_group, true);
|
||||
}
|
||||
InterruptTrigger::AnyEdge => {
|
||||
// noop
|
||||
w.set_edge_high(pin_group, true);
|
||||
w.set_edge_low(pin_group, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
irq.enable();
|
||||
}
|
||||
|
||||
Self { pin, level }
|
||||
@@ -257,47 +248,21 @@ impl<'d, T: Pin> Future for InputFuture<'d, T> {
|
||||
// LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin.
|
||||
let pin_group = (self.pin.pin() % 8) as usize;
|
||||
|
||||
// This should check the the level of the interrupt trigger level of
|
||||
// the pin and if it has been disabled that means it was done by the
|
||||
// interrupt service routine, so we then know that the event/trigger
|
||||
// happened and Poll::Ready will be returned.
|
||||
trace!("{:?} for pin {}", self.level, self.pin.pin());
|
||||
match self.level {
|
||||
InterruptTrigger::AnyEdge => {
|
||||
if !inte.edge_high(pin_group) && !inte.edge_low(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::LevelHigh => {
|
||||
if !inte.level_high(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::LevelLow => {
|
||||
if !inte.level_low(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::EdgeHigh => {
|
||||
if !inte.edge_high(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::EdgeLow => {
|
||||
if !inte.edge_low(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
// since the interrupt handler clears all INTE flags we'll check that
|
||||
// all have been cleared and unconditionally return Ready(()) if so.
|
||||
// we don't need further handshaking since only a single event wait
|
||||
// is possible for any given pin at any given time.
|
||||
if !inte.edge_high(pin_group)
|
||||
&& !inte.edge_low(pin_group)
|
||||
&& !inte.level_high(pin_group)
|
||||
&& !inte.level_low(pin_group)
|
||||
{
|
||||
trace!(
|
||||
"{:?} for pin {} was cleared, return Poll::Ready",
|
||||
self.level,
|
||||
self.pin.pin()
|
||||
);
|
||||
return Poll::Ready(());
|
||||
}
|
||||
trace!("InputFuture::poll return Poll::Pending");
|
||||
Poll::Pending
|
||||
@@ -644,23 +609,17 @@ impl<'d, T: Pin> Flex<'d, T> {
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_rising_edge(&mut self) {
|
||||
self.wait_for_low().await;
|
||||
self.wait_for_high().await;
|
||||
InputFuture::new(&mut self.pin, InterruptTrigger::EdgeHigh).await;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_falling_edge(&mut self) {
|
||||
self.wait_for_high().await;
|
||||
self.wait_for_low().await;
|
||||
InputFuture::new(&mut self.pin, InterruptTrigger::EdgeLow).await;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_any_edge(&mut self) {
|
||||
if self.is_high() {
|
||||
self.wait_for_low().await;
|
||||
} else {
|
||||
self.wait_for_high().await;
|
||||
}
|
||||
InputFuture::new(&mut self.pin, InterruptTrigger::AnyEdge).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -617,6 +617,28 @@ mod eh02 {
|
||||
self.blocking_write_read(address, bytes, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> {
|
||||
type Error = Error;
|
||||
|
||||
fn exec(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
Self::setup(address.into())?;
|
||||
for i in 0..operations.len() {
|
||||
let last = i == operations.len() - 1;
|
||||
match &mut operations[i] {
|
||||
embedded_hal_02::blocking::i2c::Operation::Read(buf) => {
|
||||
self.read_blocking_internal(buf, false, last)?
|
||||
}
|
||||
embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-traits")]
|
||||
|
||||
@@ -156,6 +156,8 @@ pub fn init(_config: config::Config) -> Peripherals {
|
||||
#[cfg(feature = "time-driver")]
|
||||
timer::init();
|
||||
dma::init();
|
||||
pio::init();
|
||||
gpio::init();
|
||||
}
|
||||
|
||||
peripherals
|
||||
|
||||
@@ -33,7 +33,7 @@ use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
|
||||
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::peripherals::CORE1;
|
||||
use crate::{interrupt, pac};
|
||||
use crate::{gpio, interrupt, pac};
|
||||
|
||||
const PAUSE_TOKEN: u32 = 0xDEADBEEF;
|
||||
const RESUME_TOKEN: u32 = !0xDEADBEEF;
|
||||
@@ -68,6 +68,9 @@ fn install_stack_guard(stack_bottom: *mut usize) {
|
||||
#[inline(always)]
|
||||
fn core1_setup(stack_bottom: *mut usize) {
|
||||
install_stack_guard(stack_bottom);
|
||||
unsafe {
|
||||
gpio::init();
|
||||
}
|
||||
}
|
||||
|
||||
/// Data type for a properly aligned stack of N bytes
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,14 @@ use core::future::{poll_fn, Future};
|
||||
use core::slice;
|
||||
use core::task::Poll;
|
||||
|
||||
use atomic_polyfill::{AtomicU8, Ordering};
|
||||
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
|
||||
use embassy_hal_common::atomic_ring_buffer::RingBuffer;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embassy_time::{Duration, Timer};
|
||||
|
||||
use super::*;
|
||||
use crate::clocks::clk_peri_freq;
|
||||
use crate::RegExt;
|
||||
|
||||
pub struct State {
|
||||
@@ -14,8 +17,15 @@ pub struct State {
|
||||
tx_buf: RingBuffer,
|
||||
rx_waker: AtomicWaker,
|
||||
rx_buf: RingBuffer,
|
||||
rx_error: AtomicU8,
|
||||
}
|
||||
|
||||
// these must match bits 8..11 in UARTDR
|
||||
const RXE_OVERRUN: u8 = 8;
|
||||
const RXE_BREAK: u8 = 4;
|
||||
const RXE_PARITY: u8 = 2;
|
||||
const RXE_FRAMING: u8 = 1;
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
@@ -23,6 +33,7 @@ impl State {
|
||||
tx_buf: RingBuffer::new(),
|
||||
rx_waker: AtomicWaker::new(),
|
||||
tx_waker: AtomicWaker::new(),
|
||||
rx_error: AtomicU8::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +56,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>(
|
||||
tx_buffer: &'d mut [u8],
|
||||
rx_buffer: &'d mut [u8],
|
||||
) {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let len = tx_buffer.len();
|
||||
unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
|
||||
let len = rx_buffer.len();
|
||||
@@ -63,7 +74,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>(
|
||||
// to pend the ISR when we want data transmission to start.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
regs.uartimsc().write(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
w.set_txim(true);
|
||||
@@ -136,6 +147,14 @@ impl<'d, T: Instance> BufferedUart<'d, T> {
|
||||
self.rx.blocking_read(buffer)
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
self.tx.busy()
|
||||
}
|
||||
|
||||
pub async fn send_break(&mut self, bits: u32) {
|
||||
self.tx.send_break(bits).await
|
||||
}
|
||||
|
||||
pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) {
|
||||
(self.rx, self.tx)
|
||||
}
|
||||
@@ -173,90 +192,113 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> {
|
||||
Self { phantom: PhantomData }
|
||||
}
|
||||
|
||||
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a {
|
||||
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a
|
||||
where
|
||||
T: 'd,
|
||||
{
|
||||
poll_fn(move |cx| {
|
||||
if buf.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
if let Poll::Ready(r) = Self::try_read(buf) {
|
||||
return Poll::Ready(r);
|
||||
}
|
||||
|
||||
let state = T::state();
|
||||
let mut rx_reader = unsafe { state.rx_buf.reader() };
|
||||
let n = rx_reader.pop(|data| {
|
||||
let n = data.len().min(buf.len());
|
||||
buf[..n].copy_from_slice(&data[..n]);
|
||||
n
|
||||
});
|
||||
if n == 0 {
|
||||
state.rx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// (Re-)Enable the interrupt to receive more data in case it was
|
||||
// disabled because the buffer was full.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
T::buffered_state().rx_waker.register(cx.waker());
|
||||
Poll::Pending
|
||||
})
|
||||
}
|
||||
|
||||
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
fn get_rx_error() -> Option<Error> {
|
||||
let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed);
|
||||
if errs & RXE_OVERRUN != 0 {
|
||||
Some(Error::Overrun)
|
||||
} else if errs & RXE_BREAK != 0 {
|
||||
Some(Error::Break)
|
||||
} else if errs & RXE_PARITY != 0 {
|
||||
Some(Error::Parity)
|
||||
} else if errs & RXE_FRAMING != 0 {
|
||||
Some(Error::Framing)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_read(buf: &mut [u8]) -> Poll<Result<usize, Error>>
|
||||
where
|
||||
T: 'd,
|
||||
{
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
loop {
|
||||
let state = T::state();
|
||||
let mut rx_reader = unsafe { state.rx_buf.reader() };
|
||||
let n = rx_reader.pop(|data| {
|
||||
let n = data.len().min(buf.len());
|
||||
buf[..n].copy_from_slice(&data[..n]);
|
||||
n
|
||||
let state = T::buffered_state();
|
||||
let mut rx_reader = unsafe { state.rx_buf.reader() };
|
||||
let n = rx_reader.pop(|data| {
|
||||
let n = data.len().min(buf.len());
|
||||
buf[..n].copy_from_slice(&data[..n]);
|
||||
n
|
||||
});
|
||||
|
||||
let result = if n == 0 {
|
||||
match Self::get_rx_error() {
|
||||
None => return Poll::Pending,
|
||||
Some(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Ok(n)
|
||||
};
|
||||
|
||||
// (Re-)Enable the interrupt to receive more data in case it was
|
||||
// disabled because the buffer was full or errors were detected.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
// (Re-)Enable the interrupt to receive more data in case it was
|
||||
// disabled because the buffer was full.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
Poll::Ready(result)
|
||||
}
|
||||
|
||||
return Ok(n);
|
||||
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
loop {
|
||||
match Self::try_read(buf) {
|
||||
Poll::Ready(res) => return res,
|
||||
Poll::Pending => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_buf<'a>() -> impl Future<Output = Result<&'a [u8], Error>> {
|
||||
fn fill_buf<'a>() -> impl Future<Output = Result<&'a [u8], Error>>
|
||||
where
|
||||
T: 'd,
|
||||
{
|
||||
poll_fn(move |cx| {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let mut rx_reader = unsafe { state.rx_buf.reader() };
|
||||
let (p, n) = rx_reader.pop_buf();
|
||||
if n == 0 {
|
||||
state.rx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
let result = if n == 0 {
|
||||
match Self::get_rx_error() {
|
||||
None => {
|
||||
state.rx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
Some(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
let buf = unsafe { slice::from_raw_parts(p, n) };
|
||||
Ok(buf)
|
||||
};
|
||||
|
||||
let buf = unsafe { slice::from_raw_parts(p, n) };
|
||||
Poll::Ready(Ok(buf))
|
||||
Poll::Ready(result)
|
||||
})
|
||||
}
|
||||
|
||||
fn consume(amt: usize) {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let mut rx_reader = unsafe { state.rx_buf.reader() };
|
||||
rx_reader.pop_done(amt);
|
||||
|
||||
// (Re-)Enable the interrupt to receive more data in case it was
|
||||
// disabled because the buffer was full.
|
||||
// disabled because the buffer was full or errors were detected.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
@@ -305,7 +347,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let mut tx_writer = unsafe { state.tx_buf.writer() };
|
||||
let n = tx_writer.push(|data| {
|
||||
let n = data.len().min(buf.len());
|
||||
@@ -328,7 +370,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
|
||||
fn flush() -> impl Future<Output = Result<(), Error>> {
|
||||
poll_fn(move |cx| {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
if !state.tx_buf.is_empty() {
|
||||
state.tx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
@@ -344,7 +386,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
}
|
||||
|
||||
loop {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let mut tx_writer = unsafe { state.tx_buf.writer() };
|
||||
let n = tx_writer.push(|data| {
|
||||
let n = data.len().min(buf.len());
|
||||
@@ -365,17 +407,54 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
|
||||
pub fn blocking_flush(&mut self) -> Result<(), Error> {
|
||||
loop {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
if state.tx_buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
unsafe { T::regs().uartfr().read().busy() }
|
||||
}
|
||||
|
||||
/// Assert a break condition after waiting for the transmit buffers to empty,
|
||||
/// for the specified number of bit times. This condition must be asserted
|
||||
/// for at least two frame times to be effective, `bits` will adjusted
|
||||
/// according to frame size, parity, and stop bit settings to ensure this.
|
||||
///
|
||||
/// This method may block for a long amount of time since it has to wait
|
||||
/// for the transmit fifo to empty, which may take a while on slow links.
|
||||
pub async fn send_break(&mut self, bits: u32) {
|
||||
let regs = T::regs();
|
||||
let bits = bits.max(unsafe {
|
||||
let lcr = regs.uartlcr_h().read();
|
||||
let width = lcr.wlen() as u32 + 5;
|
||||
let parity = lcr.pen() as u32;
|
||||
let stops = 1 + lcr.stp2() as u32;
|
||||
2 * (1 + width + parity + stops)
|
||||
});
|
||||
let divx64 = unsafe {
|
||||
((regs.uartibrd().read().baud_divint() as u32) << 6) + regs.uartfbrd().read().baud_divfrac() as u32
|
||||
} as u64;
|
||||
let div_clk = clk_peri_freq() as u64 * 64;
|
||||
let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk;
|
||||
|
||||
Self::flush().await.unwrap();
|
||||
while self.busy() {}
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_set(|w| w.set_brk(true));
|
||||
}
|
||||
Timer::after(Duration::from_micros(wait_usecs)).await;
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_clear(|w| w.set_brk(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
unsafe {
|
||||
state.rx_buf.deinit();
|
||||
|
||||
@@ -390,7 +469,7 @@ impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
|
||||
|
||||
impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
unsafe {
|
||||
state.tx_buf.deinit();
|
||||
|
||||
@@ -405,7 +484,7 @@ impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
|
||||
|
||||
pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
|
||||
let r = T::regs();
|
||||
let s = T::state();
|
||||
let s = T::buffered_state();
|
||||
|
||||
unsafe {
|
||||
// Clear TX and error interrupt flags
|
||||
@@ -439,19 +518,37 @@ pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
|
||||
let mut rx_writer = s.rx_buf.writer();
|
||||
let rx_buf = rx_writer.push_slice();
|
||||
let mut n_read = 0;
|
||||
let mut error = false;
|
||||
for rx_byte in rx_buf {
|
||||
if r.uartfr().read().rxfe() {
|
||||
break;
|
||||
}
|
||||
*rx_byte = r.uartdr().read().data();
|
||||
let dr = r.uartdr().read();
|
||||
if (dr.0 >> 8) != 0 {
|
||||
s.rx_error.fetch_or((dr.0 >> 8) as u8, Ordering::Relaxed);
|
||||
error = true;
|
||||
// only fill the buffer with valid characters. the current character is fine
|
||||
// if the error is an overrun, but if we add it to the buffer we'll report
|
||||
// the overrun one character too late. drop it instead and pretend we were
|
||||
// a bit slower at draining the rx fifo than we actually were.
|
||||
// this is consistent with blocking uart error reporting.
|
||||
break;
|
||||
}
|
||||
*rx_byte = dr.data();
|
||||
n_read += 1;
|
||||
}
|
||||
if n_read > 0 {
|
||||
rx_writer.push_done(n_read);
|
||||
s.rx_waker.wake();
|
||||
} else if error {
|
||||
s.rx_waker.wake();
|
||||
}
|
||||
// Disable any further RX interrupts when the buffer becomes full.
|
||||
if s.rx_buf.is_full() {
|
||||
// Disable any further RX interrupts when the buffer becomes full or
|
||||
// errors have occured. this lets us buffer additional errors in the
|
||||
// fifo without needing more error storage locations, and most applications
|
||||
// will want to do a full reset of their uart state anyway once an error
|
||||
// has happened.
|
||||
if s.rx_buf.is_full() || error {
|
||||
r.uartimsc().write_clear(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
|
||||
use atomic_polyfill::{AtomicU16, Ordering};
|
||||
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use pac::uart::regs::Uartris;
|
||||
|
||||
use crate::clocks::clk_peri_freq;
|
||||
use crate::dma::{AnyChannel, Channel};
|
||||
use crate::gpio::sealed::Pin;
|
||||
use crate::gpio::AnyPin;
|
||||
use crate::pac::io::vals::{Inover, Outover};
|
||||
use crate::{pac, peripherals, Peripheral};
|
||||
use crate::{pac, peripherals, Peripheral, RegExt};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
mod buffered;
|
||||
@@ -95,6 +104,11 @@ pub enum Error {
|
||||
Framing,
|
||||
}
|
||||
|
||||
pub struct DmaState {
|
||||
rx_err_waker: AtomicWaker,
|
||||
rx_errs: AtomicU16,
|
||||
}
|
||||
|
||||
pub struct Uart<'d, T: Instance, M: Mode> {
|
||||
tx: UartTx<'d, T, M>,
|
||||
rx: UartRx<'d, T, M>,
|
||||
@@ -146,6 +160,43 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> {
|
||||
unsafe { while !r.uartfr().read().txfe() {} }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
unsafe { T::regs().uartfr().read().busy() }
|
||||
}
|
||||
|
||||
/// Assert a break condition after waiting for the transmit buffers to empty,
|
||||
/// for the specified number of bit times. This condition must be asserted
|
||||
/// for at least two frame times to be effective, `bits` will adjusted
|
||||
/// according to frame size, parity, and stop bit settings to ensure this.
|
||||
///
|
||||
/// This method may block for a long amount of time since it has to wait
|
||||
/// for the transmit fifo to empty, which may take a while on slow links.
|
||||
pub async fn send_break(&mut self, bits: u32) {
|
||||
let regs = T::regs();
|
||||
let bits = bits.max(unsafe {
|
||||
let lcr = regs.uartlcr_h().read();
|
||||
let width = lcr.wlen() as u32 + 5;
|
||||
let parity = lcr.pen() as u32;
|
||||
let stops = 1 + lcr.stp2() as u32;
|
||||
2 * (1 + width + parity + stops)
|
||||
});
|
||||
let divx64 = unsafe {
|
||||
((regs.uartibrd().read().baud_divint() as u32) << 6) + regs.uartfbrd().read().baud_divfrac() as u32
|
||||
} as u64;
|
||||
let div_clk = clk_peri_freq() as u64 * 64;
|
||||
let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk;
|
||||
|
||||
self.blocking_flush().unwrap();
|
||||
while self.busy() {}
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_set(|w| w.set_brk(true));
|
||||
}
|
||||
Timer::after(Duration::from_micros(wait_usecs)).await;
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_clear(|w| w.set_brk(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> UartTx<'d, T, Blocking> {
|
||||
@@ -167,7 +218,7 @@ impl<'d, T: Instance> UartTx<'d, T, Async> {
|
||||
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
|
||||
let ch = self.tx_dma.as_mut().unwrap();
|
||||
let transfer = unsafe {
|
||||
T::regs().uartdmacr().modify(|reg| {
|
||||
T::regs().uartdmacr().write_set(|reg| {
|
||||
reg.set_txdmae(true);
|
||||
});
|
||||
// If we don't assign future to a variable, the data register pointer
|
||||
@@ -184,51 +235,86 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> {
|
||||
pub fn new(
|
||||
_uart: impl Peripheral<P = T> + 'd,
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(rx, rx_dma);
|
||||
into_ref!(rx, irq, rx_dma);
|
||||
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 {
|
||||
rx_dma,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
let r = T::regs();
|
||||
unsafe {
|
||||
for b in buffer {
|
||||
*b = loop {
|
||||
if r.uartfr().read().rxfe() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dr = r.uartdr().read();
|
||||
|
||||
if dr.oe() {
|
||||
return Err(Error::Overrun);
|
||||
} else if dr.be() {
|
||||
return Err(Error::Break);
|
||||
} else if dr.pe() {
|
||||
return Err(Error::Parity);
|
||||
} else if dr.fe() {
|
||||
return Err(Error::Framing);
|
||||
} else {
|
||||
break dr.data();
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> {
|
||||
while buffer.len() > 0 {
|
||||
let received = self.drain_fifo(buffer)?;
|
||||
buffer = &mut buffer[received..];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
let r = T::regs();
|
||||
for (i, b) in buffer.iter_mut().enumerate() {
|
||||
if unsafe { r.uartfr().read().rxfe() } {
|
||||
return Ok(i);
|
||||
}
|
||||
|
||||
let dr = unsafe { r.uartdr().read() };
|
||||
|
||||
if dr.oe() {
|
||||
return Err(Error::Overrun);
|
||||
} else if dr.be() {
|
||||
return Err(Error::Break);
|
||||
} else if dr.pe() {
|
||||
return Err(Error::Parity);
|
||||
} else if dr.fe() {
|
||||
return Err(Error::Framing);
|
||||
} else {
|
||||
*b = dr.data();
|
||||
}
|
||||
}
|
||||
Ok(buffer.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(_) = self.rx_dma {
|
||||
unsafe {
|
||||
T::Interrupt::steal().disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> UartRx<'d, T, Blocking> {
|
||||
pub fn new_blocking(
|
||||
_uart: impl Peripheral<P = T> + 'd,
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(rx);
|
||||
Uart::<T, Blocking>::init(None, Some(rx.map_into()), None, None, config);
|
||||
Self::new_inner(None, None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub fn into_buffered(
|
||||
self,
|
||||
@@ -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> {
|
||||
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
// clear error flags before we drain the fifo. errors that have accumulated
|
||||
// in the flags will also be present in the fifo.
|
||||
T::dma_state().rx_errs.store(0, Ordering::Relaxed);
|
||||
unsafe {
|
||||
T::regs().uarticr().write(|w| {
|
||||
w.set_oeic(true);
|
||||
w.set_beic(true);
|
||||
w.set_peic(true);
|
||||
w.set_feic(true);
|
||||
});
|
||||
}
|
||||
|
||||
// then drain the fifo. we need to read at most 32 bytes. errors that apply
|
||||
// to fifo bytes will be reported directly.
|
||||
let buffer = match {
|
||||
let limit = buffer.len().min(32);
|
||||
self.drain_fifo(&mut buffer[0..limit])
|
||||
} {
|
||||
Ok(len) if len < buffer.len() => &mut buffer[len..],
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
// start a dma transfer. if errors have happened in the interim some error
|
||||
// interrupt flags will have been raised, and those will be picked up immediately
|
||||
// by the interrupt handler.
|
||||
let ch = self.rx_dma.as_mut().unwrap();
|
||||
let transfer = unsafe {
|
||||
T::regs().uartdmacr().modify(|reg| {
|
||||
T::regs().uartimsc().write_set(|w| {
|
||||
w.set_oeim(true);
|
||||
w.set_beim(true);
|
||||
w.set_peim(true);
|
||||
w.set_feim(true);
|
||||
});
|
||||
T::regs().uartdmacr().write_set(|reg| {
|
||||
reg.set_rxdmae(true);
|
||||
reg.set_dmaonerr(true);
|
||||
});
|
||||
// If we don't assign future to a variable, the data register pointer
|
||||
// is held across an await and makes the future non-Send.
|
||||
crate::dma::read(ch, T::regs().uartdr().ptr() as *const _, buffer, T::RX_DREQ)
|
||||
};
|
||||
transfer.await;
|
||||
Ok(())
|
||||
|
||||
// wait for either the transfer to complete or an error to happen.
|
||||
let transfer_result = select(
|
||||
transfer,
|
||||
poll_fn(|cx| {
|
||||
T::dma_state().rx_err_waker.register(cx.waker());
|
||||
match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) {
|
||||
0 => Poll::Pending,
|
||||
e => Poll::Ready(Uartris(e as u32)),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let errors = match transfer_result {
|
||||
Either::First(()) => return Ok(()),
|
||||
Either::Second(e) => e,
|
||||
};
|
||||
|
||||
if errors.0 == 0 {
|
||||
return Ok(());
|
||||
} else if errors.oeris() {
|
||||
return Err(Error::Overrun);
|
||||
} else if errors.beris() {
|
||||
return Err(Error::Break);
|
||||
} else if errors.peris() {
|
||||
return Err(Error::Parity);
|
||||
} else if errors.feris() {
|
||||
return Err(Error::Framing);
|
||||
}
|
||||
unreachable!("unrecognized rx error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +428,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(tx, rx);
|
||||
Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, config)
|
||||
Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, None, config)
|
||||
}
|
||||
|
||||
/// Create a new UART with hardware flow control (RTS/CTS)
|
||||
@@ -289,6 +449,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
|
||||
Some(cts.map_into()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
config,
|
||||
)
|
||||
}
|
||||
@@ -317,17 +478,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
|
||||
uart: impl Peripheral<P = T> + 'd,
|
||||
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
tx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(tx, rx, tx_dma, rx_dma);
|
||||
into_ref!(tx, rx, irq, tx_dma, rx_dma);
|
||||
Self::new_inner(
|
||||
uart,
|
||||
tx.map_into(),
|
||||
rx.map_into(),
|
||||
None,
|
||||
None,
|
||||
Some(irq),
|
||||
Some(tx_dma.map_into()),
|
||||
Some(rx_dma.map_into()),
|
||||
config,
|
||||
@@ -341,17 +504,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
|
||||
cts: impl Peripheral<P = impl CtsPin<T>> + 'd,
|
||||
irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
tx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(tx, rx, cts, rts, tx_dma, rx_dma);
|
||||
into_ref!(tx, rx, cts, rts, irq, tx_dma, rx_dma);
|
||||
Self::new_inner(
|
||||
uart,
|
||||
tx.map_into(),
|
||||
rx.map_into(),
|
||||
Some(rts.map_into()),
|
||||
Some(cts.map_into()),
|
||||
Some(irq),
|
||||
Some(tx_dma.map_into()),
|
||||
Some(rx_dma.map_into()),
|
||||
config,
|
||||
@@ -366,6 +531,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
|
||||
mut rx: PeripheralRef<'d, AnyPin>,
|
||||
mut rts: Option<PeripheralRef<'d, AnyPin>>,
|
||||
mut cts: Option<PeripheralRef<'d, AnyPin>>,
|
||||
irq: Option<PeripheralRef<'d, T::Interrupt>>,
|
||||
tx_dma: Option<PeripheralRef<'d, AnyChannel>>,
|
||||
rx_dma: Option<PeripheralRef<'d, AnyChannel>>,
|
||||
config: Config,
|
||||
@@ -380,7 +546,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
|
||||
|
||||
Self {
|
||||
tx: UartTx::new_inner(tx_dma),
|
||||
rx: UartRx::new_inner(rx_dma),
|
||||
rx: UartRx::new_inner(irq, rx_dma),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +682,14 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> {
|
||||
self.rx.blocking_read(buffer)
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
self.tx.busy()
|
||||
}
|
||||
|
||||
pub async fn send_break(&mut self, bits: u32) {
|
||||
self.tx.send_break(bits).await
|
||||
}
|
||||
|
||||
/// Split the Uart into a transmitter and receiver, which is particuarly
|
||||
/// useful when having two tasks correlating to transmitting and receiving.
|
||||
pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) {
|
||||
@@ -706,7 +880,9 @@ mod sealed {
|
||||
fn regs() -> pac::uart::Uart;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
fn state() -> &'static buffered::State;
|
||||
fn buffered_state() -> &'static buffered::State;
|
||||
|
||||
fn dma_state() -> &'static DmaState;
|
||||
}
|
||||
pub trait TxPin<T: Instance> {}
|
||||
pub trait RxPin<T: Instance> {}
|
||||
@@ -744,10 +920,18 @@ macro_rules! impl_instance {
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
fn state() -> &'static buffered::State {
|
||||
fn buffered_state() -> &'static buffered::State {
|
||||
static STATE: buffered::State = buffered::State::new();
|
||||
&STATE
|
||||
}
|
||||
|
||||
fn dma_state() -> &'static DmaState {
|
||||
static STATE: DmaState = DmaState {
|
||||
rx_err_waker: AtomicWaker::new(),
|
||||
rx_errs: AtomicU16::new(0),
|
||||
};
|
||||
&STATE
|
||||
}
|
||||
}
|
||||
impl Instance for peripherals::$inst {}
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ sdio-host = "0.5.0"
|
||||
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
|
||||
critical-section = "1.1"
|
||||
atomic-polyfill = "1.0.1"
|
||||
stm32-metapac = "6"
|
||||
stm32-metapac = "7"
|
||||
vcell = "0.1.3"
|
||||
bxcan = "0.7.0"
|
||||
nb = "1.0.0"
|
||||
@@ -74,7 +74,7 @@ critical-section = { version = "1.1", features = ["std"] }
|
||||
[build-dependencies]
|
||||
proc-macro2 = "1.0.36"
|
||||
quote = "1.0.15"
|
||||
stm32-metapac = { version = "6", default-features = false, features = ["metadata"]}
|
||||
stm32-metapac = { version = "7", default-features = false, features = ["metadata"]}
|
||||
|
||||
[features]
|
||||
default = ["stm32-metapac/rt"]
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
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_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError};
|
||||
use super::word::{Word, WordSize};
|
||||
use super::Dir;
|
||||
use crate::_generated::BDMA_CHANNEL_COUNT;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac;
|
||||
use crate::pac::bdma::vals;
|
||||
use crate::pac::bdma::{regs, vals};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
@@ -48,13 +50,16 @@ impl From<Dir> for vals::Dir {
|
||||
|
||||
struct State {
|
||||
ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT],
|
||||
complete_count: [AtomicUsize; BDMA_CHANNEL_COUNT],
|
||||
}
|
||||
|
||||
impl State {
|
||||
const fn new() -> Self {
|
||||
const ZERO: AtomicUsize = AtomicUsize::new(0);
|
||||
const AW: AtomicWaker = AtomicWaker::new();
|
||||
Self {
|
||||
ch_wakers: [AW; BDMA_CHANNEL_COUNT],
|
||||
complete_count: [ZERO; BDMA_CHANNEL_COUNT],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,8 +110,23 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index
|
||||
if isr.teif(channel_num) {
|
||||
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
|
||||
let mut wake = false;
|
||||
|
||||
if isr.htif(channel_num) && cr.read().htie() {
|
||||
// Acknowledge half transfer complete interrupt
|
||||
dma.ifcr().write(|w| w.set_htif(channel_num, true));
|
||||
wake = true;
|
||||
}
|
||||
|
||||
if isr.tcif(channel_num) && cr.read().tcie() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -252,6 +272,7 @@ impl<'a, C: Channel> Transfer<'a, C> {
|
||||
|
||||
let mut this = Self { channel };
|
||||
this.clear_irqs();
|
||||
STATE.complete_count[this.channel.index()].store(0, Ordering::Release);
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, _request);
|
||||
@@ -299,7 +320,9 @@ impl<'a, C: Channel> Transfer<'a, C> {
|
||||
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,17 @@ use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
use atomic_polyfill::AtomicUsize;
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use pac::dma::regs;
|
||||
|
||||
use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError};
|
||||
use super::word::{Word, WordSize};
|
||||
use super::Dir;
|
||||
use crate::_generated::DMA_CHANNEL_COUNT;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac::dma::vals;
|
||||
use crate::pac::dma::{regs, vals};
|
||||
use crate::{interrupt, pac};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
@@ -128,13 +129,16 @@ impl From<FifoThreshold> for vals::Fth {
|
||||
|
||||
struct State {
|
||||
ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT],
|
||||
complete_count: [AtomicUsize; DMA_CHANNEL_COUNT],
|
||||
}
|
||||
|
||||
impl State {
|
||||
const fn new() -> Self {
|
||||
const ZERO: AtomicUsize = AtomicUsize::new(0);
|
||||
const AW: AtomicWaker = AtomicWaker::new();
|
||||
Self {
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
/* acknowledge transfer complete interrupt */
|
||||
// Acknowledge transfer complete interrupt
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
channel: PeripheralRef<'a, C>,
|
||||
_phantom: PhantomData<W>,
|
||||
@@ -530,6 +546,7 @@ impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> {
|
||||
|
||||
unsafe {
|
||||
dma.ifcr(isrn).write(|w| {
|
||||
w.set_htif(isrbit, true);
|
||||
w.set_tcif(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());
|
||||
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> {
|
||||
@@ -598,3 +606,180 @@ impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub use gpdma::*;
|
||||
#[cfg(dmamux)]
|
||||
mod dmamux;
|
||||
|
||||
pub(crate) mod ringbuffer;
|
||||
pub mod word;
|
||||
|
||||
use core::mem;
|
||||
|
||||
420
embassy-stm32/src/dma/ringbuffer.rs
Normal file
420
embassy-stm32/src/dma/ringbuffer.rs
Normal 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
178
embassy-stm32/src/ipcc.rs
Normal 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));
|
||||
}
|
||||
@@ -44,20 +44,19 @@ pub mod i2c;
|
||||
#[cfg(crc)]
|
||||
pub mod crc;
|
||||
pub mod flash;
|
||||
#[cfg(stm32wb)]
|
||||
pub mod ipcc;
|
||||
pub mod pwm;
|
||||
#[cfg(quadspi)]
|
||||
pub mod qspi;
|
||||
#[cfg(rng)]
|
||||
pub mod rng;
|
||||
#[cfg(all(rtc, not(any(rtc_v1, rtc_v2f0, rtc_v2f7, rtc_v3, rtc_v3u5))))]
|
||||
#[cfg(all(rtc, not(rtc_v1)))]
|
||||
pub mod rtc;
|
||||
#[cfg(sdmmc)]
|
||||
pub mod sdmmc;
|
||||
#[cfg(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)]
|
||||
pub mod usart;
|
||||
#[cfg(all(usb, feature = "time"))]
|
||||
|
||||
@@ -51,7 +51,7 @@ pub struct DateTime {
|
||||
impl From<chrono::NaiveDateTime> for DateTime {
|
||||
fn from(date_time: chrono::NaiveDateTime) -> Self {
|
||||
Self {
|
||||
year: (date_time.year() - 1970) as u16,
|
||||
year: date_time.year() as u16,
|
||||
month: date_time.month() as u8,
|
||||
day: date_time.day() as u8,
|
||||
day_of_week: date_time.weekday().into(),
|
||||
@@ -65,14 +65,10 @@ impl From<chrono::NaiveDateTime> for DateTime {
|
||||
#[cfg(feature = "chrono")]
|
||||
impl From<DateTime> for chrono::NaiveDateTime {
|
||||
fn from(date_time: DateTime) -> Self {
|
||||
NaiveDate::from_ymd_opt(
|
||||
(date_time.year + 1970) as i32,
|
||||
date_time.month as u32,
|
||||
date_time.day as u32,
|
||||
)
|
||||
.unwrap()
|
||||
.and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32)
|
||||
.unwrap()
|
||||
NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32)
|
||||
.unwrap()
|
||||
.and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +155,8 @@ pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) {
|
||||
let (yt, yu) = byte_to_bcd2(yr_offset);
|
||||
|
||||
unsafe {
|
||||
use crate::pac::rtc::vals::Ampm;
|
||||
|
||||
rtc.tr().write(|w| {
|
||||
w.set_ht(ht);
|
||||
w.set_hu(hu);
|
||||
@@ -166,7 +164,7 @@ pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) {
|
||||
w.set_mnu(mnu);
|
||||
w.set_st(st);
|
||||
w.set_su(su);
|
||||
w.set_pm(stm32_metapac::rtc::vals::Ampm::AM);
|
||||
w.set_pm(Ampm::AM);
|
||||
});
|
||||
|
||||
rtc.dr().write(|w| {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -10,12 +10,12 @@ pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
|
||||
any(
|
||||
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")]
|
||||
mod versions;
|
||||
mod _version;
|
||||
pub use _version::*;
|
||||
use embassy_hal_common::Peripheral;
|
||||
pub use versions::*;
|
||||
|
||||
/// Errors that can occur on methods on [RtcClock]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -113,7 +113,7 @@ impl Default for RtcCalibrationCyclePeriod {
|
||||
|
||||
impl<'d, T: Instance> Rtc<'d, T> {
|
||||
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 {
|
||||
phantom: PhantomData,
|
||||
@@ -179,14 +179,14 @@ impl<'d, T: Instance> Rtc<'d, T> {
|
||||
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.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
pub fn read_backup_register(&self, register: usize) -> Option<u32> {
|
||||
read_backup_register(&T::regs(), register)
|
||||
T::read_backup_register(&T::regs(), 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
|
||||
/// 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) {
|
||||
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 {
|
||||
use crate::pac::rtc::Rtc;
|
||||
|
||||
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 {}
|
||||
|
||||
impl sealed::Instance for crate::peripherals::RTC {
|
||||
fn regs() -> crate::pac::rtc::Rtc {
|
||||
crate::pac::RTC
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance for crate::peripherals::RTC {}
|
||||
|
||||
@@ -1,29 +1,78 @@
|
||||
use stm32_metapac::rtc::vals::{Init, Osel, Pol};
|
||||
|
||||
use super::{Instance, RtcConfig};
|
||||
use super::{sealed, Instance, RtcConfig};
|
||||
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> {
|
||||
/// Applies the RTC config
|
||||
/// It this changes the RTC clock source the time will be reset
|
||||
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
|
||||
// Unlock the backup domain
|
||||
unsafe {
|
||||
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 {
|
||||
@@ -148,24 +197,33 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(unsafe { rtc.bkpr(register).read().bkp() })
|
||||
} else {
|
||||
None
|
||||
impl sealed::Instance for crate::peripherals::RTC {
|
||||
const BACKUP_REGISTER_COUNT: usize = 20;
|
||||
|
||||
unsafe fn enable_peripheral_clk() {
|
||||
#[cfg(any(rtc_v2l4, rtc_v2wb))]
|
||||
{
|
||||
// enable peripheral clock for communication
|
||||
crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true));
|
||||
|
||||
// read to allow the pwr clock to enable
|
||||
crate::pac::PWR.cr1().read();
|
||||
}
|
||||
}
|
||||
|
||||
fn read_backup_register(rtc: &Rtc, register: usize) -> Option<u32> {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
Some(unsafe { rtc.bkpr(register).read().bkp() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn write_backup_register(rtc: &Rtc, register: usize, value: u32) {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set content of the backup register.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
pub fn write_backup_register(rtc: &Rtc, register: usize, value: u32) {
|
||||
if register < BACKUP_REGISTER_COUNT {
|
||||
unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) }
|
||||
}
|
||||
}
|
||||
impl Instance for crate::peripherals::RTC {}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
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;
|
||||
|
||||
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) {
|
||||
// Unlock the backup domain
|
||||
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));
|
||||
while !crate::pac::PWR.cr1().read().dbp() {}
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "stm32g0c1ve",
|
||||
feature = "stm32g491re",
|
||||
feature = "stm32u585zi",
|
||||
feature = "stm32g473cc"
|
||||
)))]
|
||||
#[cfg(any(rcc_wl5, rcc_wle))]
|
||||
{
|
||||
crate::pac::PWR
|
||||
.cr1()
|
||||
.modify(|w| w.set_dbp(stm32_metapac::pwr::vals::Dbp::ENABLED));
|
||||
while crate::pac::PWR.cr1().read().dbp() != stm32_metapac::pwr::vals::Dbp::DISABLED {}
|
||||
use crate::pac::pwr::vals::Dbp;
|
||||
|
||||
crate::pac::PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED));
|
||||
while crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED {}
|
||||
}
|
||||
|
||||
let reg = crate::pac::RCC.bdcr().read();
|
||||
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
|
||||
|
||||
let config_rtcsel = rtc_config.clock_config as u8;
|
||||
#[cfg(not(any(
|
||||
feature = "stm32wl54jc-cm0p",
|
||||
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);
|
||||
#[cfg(not(any(rcc_wl5, rcc_wle, rcc_g4)))]
|
||||
let config_rtcsel = Rtcsel(config_rtcsel);
|
||||
|
||||
if !reg.rtcen() || reg.rtcsel() != config_rtcsel {
|
||||
crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true));
|
||||
@@ -195,32 +182,24 @@ impl<'d, T: Instance> super::Rtc<'d, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) unsafe fn enable_peripheral_clk() {
|
||||
// Nothing to do
|
||||
}
|
||||
impl sealed::Instance for crate::peripherals::RTC {
|
||||
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 {
|
||||
//Some(rtc.bkpr()[register].read().bits())
|
||||
None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 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())
|
||||
None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
|
||||
} else {
|
||||
None
|
||||
fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
// RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
|
||||
//unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set content of the backup register.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
pub fn write_backup_register(_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
|
||||
//unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) }
|
||||
}
|
||||
}
|
||||
impl Instance for crate::peripherals::RTC {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ impl<'d, T: BasicInstance> BufferedUart<'d, T> {
|
||||
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(
|
||||
peri: impl Peripheral<P = T> + '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);
|
||||
}
|
||||
|
||||
configure(r, &config, T::frequency(), T::MULTIPLIER, true, true);
|
||||
configure(r, &config, T::frequency(), T::KIND, true, true);
|
||||
|
||||
unsafe {
|
||||
r.cr1().modify(|w| {
|
||||
|
||||
@@ -12,10 +12,11 @@ use futures::future::{select, Either};
|
||||
|
||||
use crate::dma::{NoDma, Transfer};
|
||||
use crate::gpio::sealed::AFType;
|
||||
#[cfg(any(lpuart_v1, lpuart_v2))]
|
||||
use crate::pac::lpuart::{regs, vals, Lpuart as Regs};
|
||||
#[cfg(not(any(lpuart_v1, lpuart_v2)))]
|
||||
use crate::pac::usart::{regs, vals, Usart as Regs};
|
||||
#[cfg(not(any(usart_v1, usart_v2)))]
|
||||
use crate::pac::usart::Lpuart as Regs;
|
||||
#[cfg(any(usart_v1, usart_v2))]
|
||||
use crate::pac::usart::Usart as Regs;
|
||||
use crate::pac::usart::{regs, vals};
|
||||
use crate::time::Hertz;
|
||||
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);
|
||||
}
|
||||
|
||||
configure(r, &config, T::frequency(), T::MULTIPLIER, false, true);
|
||||
configure(r, &config, T::frequency(), T::KIND, false, true);
|
||||
|
||||
// create state once!
|
||||
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);
|
||||
}
|
||||
|
||||
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.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 mut wake = false;
|
||||
let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie());
|
||||
|
||||
if has_errors {
|
||||
// clear all interrupts and DMA Rx Request
|
||||
unsafe {
|
||||
@@ -304,22 +305,30 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
|
||||
});
|
||||
}
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
wake = true;
|
||||
} else {
|
||||
if cr1.idleie() && sr.idle() {
|
||||
// IDLE detected: no more data will come
|
||||
unsafe {
|
||||
r.cr1().modify(|w| {
|
||||
// disable idle line detection
|
||||
w.set_idleie(false);
|
||||
});
|
||||
}
|
||||
|
||||
s.rx_waker.wake();
|
||||
} else if cr1.idleie() && sr.idle() {
|
||||
// IDLE detected: no more data will come
|
||||
unsafe {
|
||||
r.cr1().modify(|w| {
|
||||
// disable idle line detection
|
||||
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);
|
||||
|
||||
s.rx_waker.wake();
|
||||
@@ -497,28 +506,24 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
if !enable_idle_line_detection {
|
||||
transfer.await;
|
||||
if enable_idle_line_detection {
|
||||
// clear idle flag
|
||||
let sr = sr(r).read();
|
||||
// This read also clears the error and idle interrupt flags on v1.
|
||||
rdr(r).read_volatile();
|
||||
clear_interrupt_flags(r, sr);
|
||||
|
||||
return Ok(ReadCompletionEvent::DmaCompleted);
|
||||
// enable idle interrupt
|
||||
r.cr1().modify(|w| {
|
||||
w.set_idleie(true);
|
||||
});
|
||||
}
|
||||
|
||||
// clear idle flag
|
||||
let sr = sr(r).read();
|
||||
// This read also clears the error and idle interrupt flags on v1.
|
||||
rdr(r).read_volatile();
|
||||
clear_interrupt_flags(r, sr);
|
||||
|
||||
// enable idle interrupt
|
||||
r.cr1().modify(|w| {
|
||||
w.set_idleie(true);
|
||||
});
|
||||
}
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
// future which completes when idle line is detected
|
||||
let idle = poll_fn(move |cx| {
|
||||
// future which completes when idle line or error is detected
|
||||
let abort = poll_fn(move |cx| {
|
||||
let s = T::state();
|
||||
|
||||
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
|
||||
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
|
||||
// select consumes its arguments
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(not(usart_v1))]
|
||||
#[cfg(not(any(usart_v1, usart_v2)))]
|
||||
pub fn new_with_de(
|
||||
peri: impl Peripheral<P = 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);
|
||||
}
|
||||
|
||||
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.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 {
|
||||
panic!("USART: At least one of RX or TX should be enabled");
|
||||
}
|
||||
|
||||
// TODO: better calculation, including error checking and OVER8 if possible.
|
||||
let div = (pclk_freq.0 + (config.baudrate / 2)) / config.baudrate * multiplier;
|
||||
#[cfg(not(usart_v4))]
|
||||
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 {
|
||||
r.brr().write_value(regs::Brr(div));
|
||||
r.cr2().write(|w| {
|
||||
w.set_stop(match config.stop_bits {
|
||||
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,
|
||||
_ => vals::Ps::EVEN,
|
||||
});
|
||||
#[cfg(not(usart_v1))]
|
||||
w.set_over8(vals::Over8(over8 as _));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -977,43 +1042,50 @@ pub use buffered::*;
|
||||
#[cfg(feature = "nightly")]
|
||||
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 {
|
||||
r.dr().ptr() as _
|
||||
}
|
||||
|
||||
#[cfg(usart_v1)]
|
||||
#[cfg(any(usart_v1, usart_v2))]
|
||||
fn rdr(r: crate::pac::usart::Usart) -> *mut u8 {
|
||||
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> {
|
||||
r.sr()
|
||||
}
|
||||
|
||||
#[cfg(usart_v1)]
|
||||
#[cfg(any(usart_v1, usart_v2))]
|
||||
#[allow(unused)]
|
||||
unsafe fn clear_interrupt_flags(_r: Regs, _sr: regs::Sr) {
|
||||
// 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 {
|
||||
r.tdr().ptr() as _
|
||||
}
|
||||
|
||||
#[cfg(usart_v2)]
|
||||
#[cfg(any(usart_v3, usart_v4))]
|
||||
fn rdr(r: Regs) -> *mut u8 {
|
||||
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> {
|
||||
r.isr()
|
||||
}
|
||||
|
||||
#[cfg(usart_v2)]
|
||||
#[cfg(any(usart_v3, usart_v4))]
|
||||
#[allow(unused)]
|
||||
unsafe fn clear_interrupt_flags(r: Regs, sr: regs::Isr) {
|
||||
r.icr().write(|w| *w = regs::Icr(sr.0));
|
||||
@@ -1024,6 +1096,13 @@ pub(crate) mod sealed {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Kind {
|
||||
Uart,
|
||||
#[cfg(any(usart_v3, usart_v4))]
|
||||
Lpuart,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub rx_waker: AtomicWaker,
|
||||
pub tx_waker: AtomicWaker,
|
||||
@@ -1039,7 +1118,7 @@ pub(crate) mod sealed {
|
||||
}
|
||||
|
||||
pub trait BasicInstance: crate::rcc::RccPeripheral {
|
||||
const MULTIPLIER: u32;
|
||||
const KIND: Kind;
|
||||
type Interrupt: crate::interrupt::Interrupt;
|
||||
|
||||
fn regs() -> Regs;
|
||||
@@ -1068,10 +1147,10 @@ pin_trait!(DePin, BasicInstance);
|
||||
dma_trait!(TxDma, BasicInstance);
|
||||
dma_trait!(RxDma, BasicInstance);
|
||||
|
||||
macro_rules! impl_lpuart {
|
||||
($inst:ident, $irq:ident, $mul:expr) => {
|
||||
macro_rules! impl_usart {
|
||||
($inst:ident, $irq:ident, $kind:expr) => {
|
||||
impl sealed::BasicInstance for crate::peripherals::$inst {
|
||||
const MULTIPLIER: u32 = $mul;
|
||||
const KIND: Kind = $kind;
|
||||
type Interrupt = crate::interrupt::$irq;
|
||||
|
||||
fn regs() -> Regs {
|
||||
@@ -1095,21 +1174,19 @@ macro_rules! impl_lpuart {
|
||||
}
|
||||
|
||||
foreach_interrupt!(
|
||||
($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => {
|
||||
impl_lpuart!($inst, $irq, 256);
|
||||
($inst:ident, usart, LPUART, $signal_name:ident, $irq:ident) => {
|
||||
impl_usart!($inst, $irq, Kind::Lpuart);
|
||||
};
|
||||
|
||||
($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 {
|
||||
|
||||
fn regs_uart() -> crate::pac::usart::Usart {
|
||||
crate::pac::$inst
|
||||
}
|
||||
}
|
||||
|
||||
impl FullInstance for peripherals::$inst {
|
||||
}
|
||||
impl FullInstance for peripherals::$inst {}
|
||||
};
|
||||
);
|
||||
|
||||
286
embassy-stm32/src/usart/rx_ringbuffered.rs
Normal file
286
embassy-stm32/src/usart/rx_ringbuffered.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true }
|
||||
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 }
|
||||
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 }
|
||||
|
||||
@@ -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-futures = { version = "0.1.0", path = "../../embassy-futures" }
|
||||
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" }
|
||||
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"] }
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use defmt::info;
|
||||
use embassy_executor::Spawner;
|
||||
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::relocate::RelocatedProgram;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn pio_task_sm0(mut sm: PioStateMachineInstance<Pio0, Sm0>, pin: AnyPin) {
|
||||
fn setup_pio_task_sm0(pio: &mut PioCommon<PIO0>, sm: &mut PioStateMachineInstance<PIO0, 0>, pin: AnyPin) {
|
||||
// Setup sm0
|
||||
|
||||
// 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 out_pin = sm.make_pio_pin(pin);
|
||||
let out_pin = pio.make_pio_pin(pin);
|
||||
let pio_pins = [&out_pin];
|
||||
sm.set_out_pins(&pio_pins);
|
||||
sm.write_instr(relocated.origin() as usize, relocated.code());
|
||||
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
|
||||
pio.write_instr(relocated.origin() as usize, relocated.code());
|
||||
pio_instr_util::exec_jmp(sm, relocated.origin());
|
||||
sm.set_clkdiv((125e6 / 20.0 / 2e2 * 256.0) as u32);
|
||||
sm.set_set_range(0, 1);
|
||||
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_out_shift_dir(ShiftDirection::Left);
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn pio_task_sm0(mut sm: PioStateMachineInstance<'static, PIO0, 0>) {
|
||||
sm.set_enable(true);
|
||||
|
||||
let mut v = 0x0f0caffa;
|
||||
@@ -46,16 +49,15 @@ async fn pio_task_sm0(mut sm: PioStateMachineInstance<Pio0, Sm0>, pin: AnyPin) {
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn pio_task_sm1(mut sm: PioStateMachineInstance<Pio0, Sm1>) {
|
||||
fn setup_pio_task_sm1(pio: &mut PioCommon<PIO0>, sm: &mut PioStateMachineInstance<PIO0, 1>) {
|
||||
// Setupm sm1
|
||||
|
||||
// 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 relocated = RelocatedProgram::new(&prg.program);
|
||||
sm.write_instr(relocated.origin() as usize, relocated.code());
|
||||
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
|
||||
pio.write_instr(relocated.origin() as usize, relocated.code());
|
||||
pio_instr_util::exec_jmp(sm, relocated.origin());
|
||||
sm.set_clkdiv((125e6 / 2e3 * 256.0) as u32);
|
||||
sm.set_set_range(0, 0);
|
||||
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_in_shift_dir(ShiftDirection::Right);
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn pio_task_sm1(mut sm: PioStateMachineInstance<'static, PIO0, 1>) {
|
||||
sm.set_enable(true);
|
||||
loop {
|
||||
let rx = sm.wait_pull().await;
|
||||
@@ -70,8 +76,7 @@ async fn pio_task_sm1(mut sm: PioStateMachineInstance<Pio0, Sm1>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn pio_task_sm2(mut sm: PioStateMachineInstance<Pio0, Sm2>) {
|
||||
fn setup_pio_task_sm2(pio: &mut PioCommon<PIO0>, sm: &mut PioStateMachineInstance<PIO0, 2>) {
|
||||
// Setup sm2
|
||||
|
||||
// Repeatedly trigger IRQ 3
|
||||
@@ -85,13 +90,17 @@ async fn pio_task_sm2(mut sm: PioStateMachineInstance<Pio0, Sm2>) {
|
||||
".wrap",
|
||||
);
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn pio_task_sm2(mut sm: PioStateMachineInstance<'static, PIO0, 2>) {
|
||||
sm.set_enable(true);
|
||||
loop {
|
||||
sm.wait_irq(3).await;
|
||||
@@ -104,9 +113,18 @@ async fn main(spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
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_sm2(sm2)).unwrap();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use defmt::info;
|
||||
use embassy_executor::Spawner;
|
||||
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::{pio_instr_util, Peripheral};
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
@@ -19,7 +19,11 @@ fn swap_nibbles(v: u32) -> u32 {
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
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!(
|
||||
".origin 0",
|
||||
@@ -34,7 +38,7 @@ async fn main(_spawner: Spawner) {
|
||||
);
|
||||
|
||||
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());
|
||||
sm.set_clkdiv((125e6 / 10e3 * 256.0) as u32);
|
||||
let pio::Wrap { source, target } = relocated.wrap();
|
||||
|
||||
243
examples/rp/src/bin/pio_hd44780.rs
Normal file
243
examples/rp/src/bin/pio_hd44780.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::interrupt;
|
||||
use embassy_rp::peripherals::UART1;
|
||||
use embassy_rp::uart::{Async, Config, UartRx, UartTx};
|
||||
use embassy_time::{Duration, Timer};
|
||||
@@ -17,7 +18,13 @@ async fn main(spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::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)));
|
||||
|
||||
|
||||
@@ -6,19 +6,19 @@ use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::gpio::{self, Pin};
|
||||
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::relocate::RelocatedProgram;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use smart_leds::RGB8;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
pub struct Ws2812<P: PioInstance, S: SmInstance> {
|
||||
sm: PioStateMachineInstance<P, S>,
|
||||
pub struct Ws2812<'d, P: PioInstance, const S: usize> {
|
||||
sm: PioStateMachineInstance<'d, P, S>,
|
||||
}
|
||||
|
||||
impl<P: PioInstance, S: SmInstance> Ws2812<P, S> {
|
||||
pub fn new(mut sm: PioStateMachineInstance<P, S>, pin: gpio::AnyPin) -> Self {
|
||||
impl<'d, P: PioInstance, const S: usize> Ws2812<'d, P, S> {
|
||||
pub fn new(mut pio: PioCommon<'d, P>, mut sm: PioStateMachineInstance<'d, P, S>, pin: gpio::AnyPin) -> Self {
|
||||
// Setup sm0
|
||||
|
||||
// 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 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());
|
||||
|
||||
// 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_sideset_base_pin(&out_pin);
|
||||
sm.set_sideset_count(1);
|
||||
@@ -115,7 +115,7 @@ async fn main(_spawner: Spawner) {
|
||||
info!("Start");
|
||||
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
|
||||
// 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 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 {
|
||||
|
||||
@@ -26,6 +26,7 @@ nb = "1.0.0"
|
||||
embedded-storage = "0.3.0"
|
||||
micromath = "2.0.0"
|
||||
static_cell = "1.0"
|
||||
chrono = { version = "^0.4", default-features = false}
|
||||
|
||||
[profile.release]
|
||||
debug = 2
|
||||
|
||||
30
examples/stm32f4/src/bin/rtc.rs
Normal file
30
examples/stm32f4/src/bin/rtc.rs
Normal 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();
|
||||
}
|
||||
@@ -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-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-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 }
|
||||
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 }
|
||||
|
||||
@@ -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-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-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" }
|
||||
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"] }
|
||||
|
||||
63
tests/rp/src/bin/gpio_multicore.rs
Normal file
63
tests/rp/src/bin/gpio_multicore.rs
Normal 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;
|
||||
}
|
||||
@@ -4,28 +4,165 @@
|
||||
|
||||
use defmt::{assert_eq, *};
|
||||
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 _};
|
||||
|
||||
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]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
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 mut uart = Uart::new_blocking(uart, tx, rx, config);
|
||||
{
|
||||
let config = Config::default();
|
||||
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.
|
||||
// This is because we aren't sending+receiving at the same time.
|
||||
// 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.
|
||||
|
||||
let data = [0xC0, 0xDE];
|
||||
uart.blocking_write(&data).unwrap();
|
||||
let data = [0xC0, 0xDE];
|
||||
uart.blocking_write(&data).unwrap();
|
||||
assert_eq!(read(&mut uart).unwrap(), data);
|
||||
}
|
||||
|
||||
let mut buf = [0; 2];
|
||||
uart.blocking_read(&mut buf).unwrap();
|
||||
assert_eq!(buf, data);
|
||||
info!("test overflow detection");
|
||||
{
|
||||
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");
|
||||
cortex_m::asm::bkpt();
|
||||
|
||||
@@ -2,39 +2,248 @@
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use defmt::{assert_eq, *};
|
||||
use defmt::{assert_eq, panic, *};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::gpio::{Level, Output};
|
||||
use embassy_rp::interrupt;
|
||||
use embassy_rp::uart::{BufferedUart, Config};
|
||||
use embedded_io::asynch::{Read, Write};
|
||||
use embassy_rp::uart::{BufferedUart, BufferedUartRx, Config, Error, Instance, Parity};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_io::asynch::{Read, ReadExactError, Write};
|
||||
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]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
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 irq = interrupt::take!(UART0_IRQ);
|
||||
let tx_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 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.
|
||||
// Make sure we send more bytes than fits in the FIFO, to test the actual
|
||||
// bufferedUart.
|
||||
|
||||
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,
|
||||
30, 31,
|
||||
];
|
||||
uart.write_all(&data).await.unwrap();
|
||||
info!("Done writing");
|
||||
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,
|
||||
];
|
||||
uart.write_all(&data).await.unwrap();
|
||||
info!("Done writing");
|
||||
|
||||
let mut buf = [0; 31];
|
||||
uart.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
assert_eq!(read(&mut uart).await.unwrap(), 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");
|
||||
cortex_m::asm::bkpt();
|
||||
|
||||
@@ -4,28 +4,242 @@
|
||||
|
||||
use defmt::{assert_eq, *};
|
||||
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 _};
|
||||
|
||||
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]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
let mut p = embassy_rp::init(Default::default());
|
||||
info!("Hello World!");
|
||||
|
||||
let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0);
|
||||
|
||||
let config = Config::default();
|
||||
let mut uart = Uart::new(uart, tx, rx, p.DMA_CH0, p.DMA_CH1, config);
|
||||
let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0);
|
||||
let mut irq = interrupt::take!(UART0_IRQ);
|
||||
|
||||
// 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.
|
||||
{
|
||||
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];
|
||||
uart.write(&data).await.unwrap();
|
||||
let data = [0xC0, 0xDE];
|
||||
uart.write(&data).await.unwrap();
|
||||
|
||||
let mut buf = [0; 2];
|
||||
uart.read(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
let mut buf = [0; 2];
|
||||
uart.read(&mut buf).await.unwrap();
|
||||
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");
|
||||
cortex_m::asm::bkpt();
|
||||
|
||||
@@ -3,7 +3,7 @@ build-std = ["core"]
|
||||
build-std-features = ["panic_immediate_abort"]
|
||||
|
||||
[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"
|
||||
|
||||
rustflags = [
|
||||
|
||||
@@ -3,25 +3,29 @@ edition = "2021"
|
||||
name = "embassy-stm32-tests"
|
||||
version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
autobins = false
|
||||
|
||||
[features]
|
||||
stm32f103c8 = ["embassy-stm32/stm32f103c8"] # Blue Pill
|
||||
stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc"] # Nucleo
|
||||
stm32g071rb = ["embassy-stm32/stm32g071rb"] # Nucleo
|
||||
stm32c031c6 = ["embassy-stm32/stm32c031c6"] # Nucleo
|
||||
stm32g491re = ["embassy-stm32/stm32g491re"] # Nucleo
|
||||
stm32h755zi = ["embassy-stm32/stm32h755zi-cm7"] # Nucleo
|
||||
stm32wb55rg = ["embassy-stm32/stm32wb55rg"] # Nucleo
|
||||
stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill
|
||||
stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono", "not-gpdma"] # Nucleo
|
||||
stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo
|
||||
stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo
|
||||
stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo
|
||||
stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
|
||||
stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma"] # Nucleo
|
||||
stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo
|
||||
stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board
|
||||
|
||||
sdmmc = []
|
||||
chrono = ["embassy-stm32/chrono", "dep:chrono"]
|
||||
not-gpdma = []
|
||||
|
||||
[dependencies]
|
||||
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-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-futures = { version = "0.1.0", path = "../../embassy-futures" }
|
||||
|
||||
defmt = "0.3.0"
|
||||
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-async = { version = "=0.2.0-alpha.1" }
|
||||
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
|
||||
# Generated by gen_test.py. DO NOT EDIT.
|
||||
@@ -40,6 +48,11 @@ name = "gpio"
|
||||
path = "src/bin/gpio.rs"
|
||||
required-features = []
|
||||
|
||||
[[bin]]
|
||||
name = "rtc"
|
||||
path = "src/bin/rtc.rs"
|
||||
required-features = [ "chrono",]
|
||||
|
||||
[[bin]]
|
||||
name = "sdmmc"
|
||||
path = "src/bin/sdmmc.rs"
|
||||
@@ -70,6 +83,11 @@ name = "usart_dma"
|
||||
path = "src/bin/usart_dma.rs"
|
||||
required-features = []
|
||||
|
||||
[[bin]]
|
||||
name = "usart_rx_ringbuffered"
|
||||
path = "src/bin/usart_rx_ringbuffered.rs"
|
||||
required-features = [ "not-gpdma",]
|
||||
|
||||
# END TESTS
|
||||
|
||||
[profile.dev]
|
||||
|
||||
@@ -6,15 +6,20 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
fs::write(out.join("link_ram.x"), include_bytes!("link_ram.x")).unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rerun-if-changed=link_ram.x");
|
||||
|
||||
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||
|
||||
// too little RAM to run from RAM.
|
||||
#[cfg(any(feature = "stm32c031c6"))]
|
||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||
#[cfg(not(any(feature = "stm32c031c6")))]
|
||||
println!("cargo:rustc-link-arg-bins=-Tlink_ram.x");
|
||||
if cfg!(any(
|
||||
feature = "stm32f429zi",
|
||||
feature = "stm32f103c8",
|
||||
feature = "stm32c031c6"
|
||||
)) {
|
||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||
println!("cargo:rerun-if-changed=link.x");
|
||||
} else {
|
||||
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");
|
||||
|
||||
|
||||
52
tests/stm32/src/bin/rtc.rs
Normal file
52
tests/stm32/src/bin/rtc.rs
Normal 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();
|
||||
}
|
||||
@@ -35,7 +35,6 @@ async fn main(_spawner: Spawner) {
|
||||
#[cfg(feature = "stm32c031c6")]
|
||||
let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6);
|
||||
|
||||
info!("asdfa;");
|
||||
let mut spi = Spi::new(
|
||||
spi,
|
||||
sck, // Arduino D13
|
||||
|
||||
@@ -9,6 +9,7 @@ use embassy_executor::Spawner;
|
||||
use embassy_stm32::dma::NoDma;
|
||||
use embassy_stm32::interrupt;
|
||||
use embassy_stm32::usart::{Config, Uart};
|
||||
use embassy_time::{Duration, Instant};
|
||||
use example_common::*;
|
||||
|
||||
#[embassy_executor::main]
|
||||
@@ -19,36 +20,76 @@ async fn main(_spawner: Spawner) {
|
||||
// Arduino pins D0 and D1
|
||||
// They're connected together with a 1K resistor.
|
||||
#[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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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 mut usart = Uart::new(usart, rx, tx, irq, NoDma, NoDma, config);
|
||||
{
|
||||
let config = Config::default();
|
||||
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.
|
||||
// This is because we aren't sending+receiving at the same time.
|
||||
// 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.
|
||||
|
||||
let data = [0xC0, 0xDE];
|
||||
usart.blocking_write(&data).unwrap();
|
||||
let data = [0xC0, 0xDE];
|
||||
usart.blocking_write(&data).unwrap();
|
||||
|
||||
let mut buf = [0; 2];
|
||||
usart.blocking_read(&mut buf).unwrap();
|
||||
assert_eq!(buf, data);
|
||||
let mut buf = [0; 2];
|
||||
usart.blocking_read(&mut buf).unwrap();
|
||||
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");
|
||||
cortex_m::asm::bkpt();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
mod example_common;
|
||||
use defmt::assert_eq;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::join::join;
|
||||
use embassy_stm32::interrupt;
|
||||
use embassy_stm32::usart::{Config, Uart};
|
||||
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);
|
||||
|
||||
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.
|
||||
// This is because we aren't sending+receiving at the same time.
|
||||
// For whatever reason, blocking works with 2 bytes but DMA only with 1??
|
||||
const LEN: usize = 128;
|
||||
let mut tx_buf = [0; LEN];
|
||||
let mut rx_buf = [0; LEN];
|
||||
for i in 0..LEN {
|
||||
tx_buf[i] = i as u8;
|
||||
}
|
||||
|
||||
let data = [0x42];
|
||||
usart.write(&data).await.unwrap();
|
||||
let (mut tx, mut rx) = usart.split();
|
||||
|
||||
let mut buf = [0; 1];
|
||||
usart.read(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
let tx_fut = async {
|
||||
tx.write(&tx_buf).await.unwrap();
|
||||
};
|
||||
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");
|
||||
cortex_m::asm::bkpt();
|
||||
|
||||
206
tests/stm32/src/bin/usart_rx_ringbuffered.rs
Normal file
206
tests/stm32/src/bin/usart_rx_ringbuffered.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,11 @@
|
||||
#![macro_use]
|
||||
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
pub use defmt::*;
|
||||
#[allow(unused)]
|
||||
use embassy_stm32::time::Hertz;
|
||||
use embassy_stm32::Config;
|
||||
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 {
|
||||
#[allow(unused_mut)]
|
||||
let mut config = Config::default();
|
||||
@@ -27,5 +16,10 @@ pub fn config() -> Config {
|
||||
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
|
||||
}
|
||||
|
||||
10
tests/utils/Cargo.toml
Normal file
10
tests/utils/Cargo.toml
Normal 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"
|
||||
53
tests/utils/src/bin/saturate_serial.rs
Normal file
53
tests/utils/src/bin/saturate_serial.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user