embassy/embassy-stm32/src/subghz/mod.rs
2022-06-14 16:27:42 +02:00

1008 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Sub-GHz radio operating in the 150 - 960 MHz ISM band
//!
//! The main radio type is [`SubGhz`].
//!
//! ## LoRa user notice
//!
//! The Sub-GHz radio may have an undocumented erratum, see this ST community
//! post for more information: [link]
//!
//! [link]: https://community.st.com/s/question/0D53W00000hR8kpSAC/stm32wl55-erratum-clairification
//!
//! NOTE: This HAL is based on https://github.com/newAM/stm32wl-hal, but adopted for use with the stm32-metapac
//! and SPI HALs.
mod bit_sync;
mod cad_params;
mod calibrate;
mod fallback_mode;
mod hse_trim;
mod irq;
mod lora_sync_word;
mod mod_params;
mod ocp;
mod op_error;
mod pa_config;
mod packet_params;
mod packet_status;
mod packet_type;
mod pkt_ctrl;
mod pmode;
mod pwr_ctrl;
mod reg_mode;
mod rf_frequency;
mod rx_timeout_stop;
mod sleep_cfg;
mod smps;
mod standby_clk;
mod stats;
mod status;
mod tcxo_mode;
mod timeout;
mod tx_params;
mod value_error;
pub use bit_sync::BitSync;
pub use cad_params::{CadParams, ExitMode, NbCadSymbol};
pub use calibrate::{Calibrate, CalibrateImage};
use embassy_hal_common::ratio::Ratio;
pub use fallback_mode::FallbackMode;
pub use hse_trim::HseTrim;
pub use irq::{CfgIrq, Irq, IrqLine};
pub use lora_sync_word::LoRaSyncWord;
pub use mod_params::{
BpskModParams, CodingRate, FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape, LoRaBandwidth,
LoRaModParams, SpreadingFactor,
};
pub use ocp::Ocp;
pub use op_error::OpError;
pub use pa_config::{PaConfig, PaSel};
pub use packet_params::{
AddrComp, BpskPacketParams, CrcType, GenericPacketParams, HeaderType, LoRaPacketParams, PreambleDetection,
};
pub use packet_status::{FskPacketStatus, LoRaPacketStatus};
pub use packet_type::PacketType;
pub use pkt_ctrl::{InfSeqSel, PktCtrl};
pub use pmode::PMode;
pub use pwr_ctrl::{CurrentLim, PwrCtrl};
pub use reg_mode::RegMode;
pub use rf_frequency::RfFreq;
pub use rx_timeout_stop::RxTimeoutStop;
pub use sleep_cfg::{SleepCfg, Startup};
pub use smps::SmpsDrv;
pub use standby_clk::StandbyClk;
pub use stats::{FskStats, LoRaStats, Stats};
pub use status::{CmdStatus, Status, StatusMode};
pub use tcxo_mode::{TcxoMode, TcxoTrim};
pub use timeout::Timeout;
pub use tx_params::{RampTime, TxParams};
pub use value_error::ValueError;
use crate::dma::NoDma;
use crate::peripherals::SUBGHZSPI;
use crate::rcc::sealed::RccPeripheral;
use crate::spi::{BitOrder, Config as SpiConfig, MisoPin, MosiPin, SckPin, Spi, MODE_0};
use crate::time::Hertz;
use crate::{pac, Unborrow};
/// Passthrough for SPI errors (for now)
pub type Error = crate::spi::Error;
struct Nss {
_priv: (),
}
impl Nss {
#[inline(always)]
pub fn new() -> Nss {
Self::clear();
Nss { _priv: () }
}
/// Clear NSS, enabling SPI transactions
#[inline(always)]
fn clear() {
let pwr = pac::PWR;
unsafe {
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW));
}
}
/// Set NSS, disabling SPI transactions
#[inline(always)]
fn set() {
let pwr = pac::PWR;
unsafe {
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH));
}
}
}
impl Drop for Nss {
fn drop(&mut self) {
Self::set()
}
}
/// Wakeup the radio from sleep mode.
///
/// # Safety
///
/// 1. This must not be called when the SubGHz radio is in use.
/// 2. This must not be called when the SubGHz SPI bus is in use.
///
/// # Example
///
/// See [`SubGhz::set_sleep`]
#[inline]
unsafe fn wakeup() {
Nss::clear();
// RM0453 rev 2 page 171 section 5.7.2 "Sleep mode"
// on a firmware request via the sub-GHz radio SPI NSS signal
// (keeping sub-GHz radio SPI NSS low for at least 20 μs)
//
// I have found this to be a more reliable mechanism for ensuring NSS is
// pulled low for long enough to wake the radio.
while rfbusys() {}
Nss::set();
}
/// Returns `true` if the radio is busy.
///
/// This may not be set immediately after NSS going low.
///
/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more
/// details.
#[inline]
fn rfbusys() -> bool {
// safety: atmoic read with no side-effects
//unsafe { (*pac::PWR::ptr()).sr2.read().rfbusys().is_busy() }
let pwr = pac::PWR;
unsafe { pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY }
}
/*
/// Returns `true` if the radio is busy or NSS is low.
///
/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more
/// details.
#[inline]
fn rfbusyms() -> bool {
let pwr = pac::PWR;
unsafe { pwr.sr2().read().rfbusyms() == pac::pwr::vals::Rfbusyms::BUSY }
}
*/
/// Sub-GHz radio peripheral
pub struct SubGhz<'d, Tx, Rx> {
spi: Spi<'d, SUBGHZSPI, Tx, Rx>,
}
impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
fn pulse_radio_reset() {
let rcc = pac::RCC;
unsafe {
rcc.csr().modify(|w| w.set_rfrst(true));
rcc.csr().modify(|w| w.set_rfrst(false));
}
}
// TODO: This should be replaced with async handling based on IRQ
fn poll_not_busy(&self) {
let mut count: u32 = 1_000_000;
while rfbusys() {
count -= 1;
if count == 0 {
let pwr = pac::PWR;
unsafe {
panic!(
"rfbusys timeout pwr.sr2=0x{:X} pwr.subghzspicr=0x{:X} pwr.cr1=0x{:X}",
pwr.sr2().read().0,
pwr.subghzspicr().read().0,
pwr.cr1().read().0
);
}
}
}
}
/// Create a new sub-GHz radio driver from a peripheral.
///
/// This will reset the radio and the SPI bus, and enable the peripheral
/// clock.
pub fn new(
peri: impl Unborrow<Target = SUBGHZSPI> + 'd,
sck: impl Unborrow<Target = impl SckPin<SUBGHZSPI>> + 'd,
mosi: impl Unborrow<Target = impl MosiPin<SUBGHZSPI>> + 'd,
miso: impl Unborrow<Target = impl MisoPin<SUBGHZSPI>> + 'd,
txdma: impl Unborrow<Target = Tx> + 'd,
rxdma: impl Unborrow<Target = Rx> + 'd,
) -> Self {
Self::pulse_radio_reset();
// see RM0453 rev 1 section 7.2.13 page 291
// The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two.
// The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz.
let clk = Hertz(core::cmp::min(SUBGHZSPI::frequency().0 / 2, 16_000_000));
let mut config = SpiConfig::default();
config.mode = MODE_0;
config.bit_order = BitOrder::MsbFirst;
let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config);
unsafe { wakeup() };
SubGhz { spi }
}
pub fn is_busy(&mut self) -> bool {
rfbusys()
}
pub fn reset(&mut self) {
Self::pulse_radio_reset();
}
}
impl<'d> SubGhz<'d, NoDma, NoDma> {
fn read(&mut self, opcode: OpCode, data: &mut [u8]) -> Result<(), Error> {
self.poll_not_busy();
{
let _nss: Nss = Nss::new();
self.spi.blocking_write(&[opcode as u8])?;
self.spi.blocking_transfer_in_place(data)?;
}
self.poll_not_busy();
Ok(())
}
/// Read one byte from the sub-Ghz radio.
fn read_1(&mut self, opcode: OpCode) -> Result<u8, Error> {
let mut buf: [u8; 1] = [0; 1];
self.read(opcode, &mut buf)?;
Ok(buf[0])
}
/// Read a fixed number of bytes from the sub-Ghz radio.
fn read_n<const N: usize>(&mut self, opcode: OpCode) -> Result<[u8; N], Error> {
let mut buf: [u8; N] = [0; N];
self.read(opcode, &mut buf)?;
Ok(buf)
}
fn write(&mut self, data: &[u8]) -> Result<(), Error> {
self.poll_not_busy();
{
let _nss: Nss = Nss::new();
self.spi.blocking_write(data)?;
}
self.poll_not_busy();
Ok(())
}
pub fn write_buffer(&mut self, offset: u8, data: &[u8]) -> Result<(), Error> {
self.poll_not_busy();
{
let _nss: Nss = Nss::new();
self.spi.blocking_write(&[OpCode::WriteBuffer as u8, offset])?;
self.spi.blocking_write(data)?;
}
self.poll_not_busy();
Ok(())
}
/// Read the radio buffer at the given offset.
///
/// The offset and length of a received packet is provided by
/// [`rx_buffer_status`](Self::rx_buffer_status).
pub fn read_buffer(&mut self, offset: u8, buf: &mut [u8]) -> Result<Status, Error> {
let mut status_buf: [u8; 1] = [0];
self.poll_not_busy();
{
let _nss: Nss = Nss::new();
self.spi.blocking_write(&[OpCode::ReadBuffer as u8, offset])?;
self.spi.blocking_transfer_in_place(&mut status_buf)?;
self.spi.blocking_transfer_in_place(buf)?;
}
self.poll_not_busy();
Ok(status_buf[0].into())
}
}
// helper to pack register writes into a single buffer to avoid multiple DMA
// transfers
macro_rules! wr_reg {
[$reg:ident, $($data:expr),+] => {
&[
OpCode::WriteRegister as u8,
Register::$reg.address().to_be_bytes()[0],
Register::$reg.address().to_be_bytes()[1],
$($data),+
]
};
}
// 5.8.2
/// Register access
impl<'d> SubGhz<'d, NoDma, NoDma> {
// register write with variable length data
fn write_register(&mut self, register: Register, data: &[u8]) -> Result<(), Error> {
let addr: [u8; 2] = register.address().to_be_bytes();
self.poll_not_busy();
{
let _nss: Nss = Nss::new();
self.spi
.blocking_write(&[OpCode::WriteRegister as u8, addr[0], addr[1]])?;
self.spi.blocking_write(data)?;
}
self.poll_not_busy();
Ok(())
}
/// Set the LoRa bit synchronization.
pub fn set_bit_sync(&mut self, bs: BitSync) -> Result<(), Error> {
self.write(wr_reg![GBSYNC, bs.as_bits()])
}
/// Set the generic packet control register.
pub fn set_pkt_ctrl(&mut self, pkt_ctrl: PktCtrl) -> Result<(), Error> {
self.write(wr_reg![GPKTCTL1A, pkt_ctrl.as_bits()])
}
/// Set the initial value for generic packet whitening.
///
/// This sets the first 8 bits, the 9th bit is set with
/// [`set_pkt_ctrl`](Self::set_pkt_ctrl).
pub fn set_init_whitening(&mut self, init: u8) -> Result<(), Error> {
self.write(wr_reg![GWHITEINIRL, init])
}
/// Set the initial value for generic packet CRC polynomial.
pub fn set_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> {
let bytes: [u8; 2] = polynomial.to_be_bytes();
self.write(wr_reg![GCRCINIRH, bytes[0], bytes[1]])
}
/// Set the generic packet CRC polynomial.
pub fn set_initial_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> {
let bytes: [u8; 2] = polynomial.to_be_bytes();
self.write(wr_reg![GCRCPOLRH, bytes[0], bytes[1]])
}
/// Set the synchronization word registers.
pub fn set_sync_word(&mut self, sync_word: &[u8; 8]) -> Result<(), Error> {
self.write_register(Register::GSYNC7, sync_word)
}
/// Set the LoRa synchronization word registers.
pub fn set_lora_sync_word(&mut self, sync_word: LoRaSyncWord) -> Result<(), Error> {
let bytes: [u8; 2] = sync_word.bytes();
self.write(wr_reg![LSYNCH, bytes[0], bytes[1]])
}
/// Set the RX gain control.
pub fn set_rx_gain(&mut self, pmode: PMode) -> Result<(), Error> {
self.write(wr_reg![RXGAINC, pmode as u8])
}
/// Set the power amplifier over current protection.
pub fn set_pa_ocp(&mut self, ocp: Ocp) -> Result<(), Error> {
self.write(wr_reg![PAOCP, ocp as u8])
}
/// Restart the radio RTC.
///
/// This is used to workaround an erratum for [`set_rx_duty_cycle`].
///
/// [`set_rx_duty_cycle`]: crate::subghz::SubGhz::set_rx_duty_cycle
pub fn restart_rtc(&mut self) -> Result<(), Error> {
self.write(wr_reg![RTCCTLR, 0b1])
}
/// Set the radio real-time-clock period.
///
/// This is used to workaround an erratum for [`set_rx_duty_cycle`].
///
/// [`set_rx_duty_cycle`]: crate::subghz::SubGhz::set_rx_duty_cycle
pub fn set_rtc_period(&mut self, period: Timeout) -> Result<(), Error> {
let tobits: u32 = period.into_bits();
self.write(wr_reg![
RTCPRDR2,
(tobits >> 16) as u8,
(tobits >> 8) as u8,
tobits as u8
])
}
/// Set the HSE32 crystal OSC_IN load capacitor trimming.
pub fn set_hse_in_trim(&mut self, trim: HseTrim) -> Result<(), Error> {
self.write(wr_reg![HSEINTRIM, trim.into()])
}
/// Set the HSE32 crystal OSC_OUT load capacitor trimming.
pub fn set_hse_out_trim(&mut self, trim: HseTrim) -> Result<(), Error> {
self.write(wr_reg![HSEOUTTRIM, trim.into()])
}
/// Set the SMPS clock detection enabled.
///
/// SMPS clock detection must be enabled fore enabling the SMPS.
pub fn set_smps_clock_det_en(&mut self, en: bool) -> Result<(), Error> {
self.write(wr_reg![SMPSC0, (en as u8) << 6])
}
/// Set the power current limiting.
pub fn set_pwr_ctrl(&mut self, pwr_ctrl: PwrCtrl) -> Result<(), Error> {
self.write(wr_reg![PC, pwr_ctrl.as_bits()])
}
/// Set the maximum SMPS drive capability.
pub fn set_smps_drv(&mut self, drv: SmpsDrv) -> Result<(), Error> {
self.write(wr_reg![SMPSC2, (drv as u8) << 1])
}
/// Set the node address.
///
/// Used with [`GenericPacketParams::set_addr_comp`] to filter packets based
/// on node address.
pub fn set_node_addr(&mut self, addr: u8) -> Result<(), Error> {
self.write(wr_reg![NODE, addr])
}
/// Set the broadcast address.
///
/// Used with [`GenericPacketParams::set_addr_comp`] to filter packets based
/// on broadcast address.
pub fn set_broadcast_addr(&mut self, addr: u8) -> Result<(), Error> {
self.write(wr_reg![BROADCAST, addr])
}
/// Set both the broadcast address and node address.
///
/// This is a combination of [`set_node_addr`] and [`set_broadcast_addr`]
/// in a single SPI transfer.
///
/// [`set_node_addr`]: Self::set_node_addr
/// [`set_broadcast_addr`]: Self::set_broadcast_addr
pub fn set_addrs(&mut self, node: u8, broadcast: u8) -> Result<(), Error> {
self.write(wr_reg![NODE, node, broadcast])
}
}
// 5.8.3
/// Operating mode commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
/// Put the radio into sleep mode.
///
/// This command is only accepted in standby mode.
/// The cfg argument allows some optional functions to be maintained
/// in sleep mode.
///
/// # Safety
///
/// 1. After the `set_sleep` command, the sub-GHz radio NSS must not go low
/// for 500 μs.
/// No reason is provided, the reference manual (RM0453 rev 2) simply
/// says "you must".
/// 2. The radio cannot be used while in sleep mode.
/// 3. The radio must be woken up with [`wakeup`] before resuming use.
///
/// # Example
///
/// Put the radio into sleep mode.
///
/// ```no_run
/// # let dp = unsafe { embassy_stm32::pac::Peripherals::steal() };
/// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
/// use embassy_stm32::{
/// subghz::{wakeup, SleepCfg, StandbyClk},
/// };
///
/// sg.set_standby(StandbyClk::Rc)?;
/// unsafe { sg.set_sleep(SleepCfg::default())? };
/// embassy::time::Timer::after(embassy::time::Duration::from_micros(500)).await;
/// unsafe { wakeup() };
/// # Ok::<(), embassy_stm32::subghz::Error>(())
/// ```
pub unsafe fn set_sleep(&mut self, cfg: SleepCfg) -> Result<(), Error> {
// poll for busy before, but not after
// radio idles with busy high while in sleep mode
self.poll_not_busy();
{
let _nss: Nss = Nss::new();
self.spi.blocking_write(&[OpCode::SetSleep as u8, u8::from(cfg)])?;
}
Ok(())
}
/// Put the radio into standby mode.
pub fn set_standby(&mut self, standby_clk: StandbyClk) -> Result<(), Error> {
self.write(&[OpCode::SetStandby as u8, u8::from(standby_clk)])
}
/// Put the subghz radio into frequency synthesis mode.
///
/// The RF-PLL frequency must be set with [`set_rf_frequency`] before using
/// this command.
///
/// Check the datasheet for more information, this is a test command but
/// I honestly do not see any use for it. Please update this description
/// if you know more than I do.
///
/// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
pub fn set_fs(&mut self) -> Result<(), Error> {
self.write(&[OpCode::SetFs.into()])
}
/// Setup the sub-GHz radio for TX.
pub fn set_tx(&mut self, timeout: Timeout) -> Result<(), Error> {
let tobits: u32 = timeout.into_bits();
self.write(&[
OpCode::SetTx.into(),
(tobits >> 16) as u8,
(tobits >> 8) as u8,
tobits as u8,
])
}
/// Setup the sub-GHz radio for RX.
pub fn set_rx(&mut self, timeout: Timeout) -> Result<(), Error> {
let tobits: u32 = timeout.into_bits();
self.write(&[
OpCode::SetRx.into(),
(tobits >> 16) as u8,
(tobits >> 8) as u8,
tobits as u8,
])
}
/// Allows selection of the receiver event which stops the RX timeout timer.
pub fn set_rx_timeout_stop(&mut self, rx_timeout_stop: RxTimeoutStop) -> Result<(), Error> {
self.write(&[OpCode::SetStopRxTimerOnPreamble.into(), rx_timeout_stop.into()])
}
/// Put the radio in non-continuous RX mode.
///
/// This command must be sent in Standby mode.
/// This command is only functional with FSK and LoRa packet type.
///
/// The following steps are performed:
/// 1. Save sub-GHz radio configuration.
/// 2. Enter Receive mode and listen for a preamble for the specified `rx_period`.
/// 3. Upon the detection of a preamble, the `rx_period` timeout is stopped
/// and restarted with the value 2 x `rx_period` + `sleep_period`.
/// During this new period, the sub-GHz radio looks for the detection of
/// a synchronization word when in (G)FSK modulation mode,
/// or a header when in LoRa modulation mode.
/// 4. If no packet is received during the listen period defined by
/// 2 x `rx_period` + `sleep_period`, the sleep mode is entered for a
/// duration of `sleep_period`. At the end of the receive period,
/// the sub-GHz radio takes some time to save the context before starting
/// the sleep period.
/// 5. After the sleep period, a new listening period is automatically
/// started. The sub-GHz radio restores the sub-GHz radio configuration
/// and continuous with step 2.
///
/// The listening mode is terminated in one of the following cases:
/// * if a packet is received during the listening period: the sub-GHz radio
/// issues a [`RxDone`] interrupt and enters standby mode.
/// * if [`set_standby`] is sent during the listening period or after the
/// sub-GHz has been requested to exit sleep mode by sub-GHz radio SPI NSS
///
/// # Erratum
///
/// When a preamble is detected the radio should restart the RX timeout
/// with a value of 2 × `rx_period` + `sleep_period`.
/// Instead the radio erroneously uses `sleep_period`.
///
/// To workaround this use [`restart_rtc`] and [`set_rtc_period`] to
/// reprogram the radio timeout to 2 × `rx_period` + `sleep_period`.
///
/// Use code similar to this in the [`PreambleDetected`] interrupt handler.
///
/// ```no_run
/// # let rx_period: Timeout = Timeout::from_millis_sat(100);
/// # let sleep_period: Timeout = Timeout::from_millis_sat(100);
/// # let mut sg = unsafe { stm32wlxx_hal::subghz::SubGhz::steal() };
/// use stm32wlxx_hal::subghz::Timeout;
///
/// let period: Timeout = rx_period
/// .saturating_add(rx_period)
/// .saturating_add(sleep_period);
///
/// sg.set_rtc_period(period)?;
/// sg.restart_rtc()?;
/// # Ok::<(), stm32wlxx_hal::subghz::Error>(())
/// ```
///
/// Please read the erratum for more details.
///
/// [`PreambleDetected`]: crate::subghz::Irq::PreambleDetected
/// [`restart_rtc`]: crate::subghz::SubGhz::restart_rtc
/// [`RxDone`]: crate::subghz::Irq::RxDone
/// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
/// [`set_rtc_period`]: crate::subghz::SubGhz::set_rtc_period
/// [`set_standby`]: crate::subghz::SubGhz::set_standby
pub fn set_rx_duty_cycle(&mut self, rx_period: Timeout, sleep_period: Timeout) -> Result<(), Error> {
let rx_period_bits: u32 = rx_period.into_bits();
let sleep_period_bits: u32 = sleep_period.into_bits();
self.write(&[
OpCode::SetRxDutyCycle.into(),
(rx_period_bits >> 16) as u8,
(rx_period_bits >> 8) as u8,
rx_period_bits as u8,
(sleep_period_bits >> 16) as u8,
(sleep_period_bits >> 8) as u8,
sleep_period_bits as u8,
])
}
/// Channel Activity Detection (CAD) with LoRa packets.
///
/// The channel activity detection (CAD) is a specific LoRa operation mode,
/// where the sub-GHz radio searches for a LoRa radio signal.
/// After the search is completed, the Standby mode is automatically
/// entered, CAD is done and IRQ is generated.
/// When a LoRa radio signal is detected, the CAD detected IRQ is also
/// generated.
///
/// The length of the search must be configured with [`set_cad_params`]
/// prior to calling `set_cad`.
///
/// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
pub fn set_cad(&mut self) -> Result<(), Error> {
self.write(&[OpCode::SetCad.into()])
}
/// Generate a continuous transmit tone at the RF-PLL frequency.
///
/// The sub-GHz radio remains in continuous transmit tone mode until a mode
/// configuration command is received.
pub fn set_tx_continuous_wave(&mut self) -> Result<(), Error> {
self.write(&[OpCode::SetTxContinuousWave as u8])
}
/// Generate an infinite preamble at the RF-PLL frequency.
///
/// The preamble is an alternating 0s and 1s sequence in generic (G)FSK and
/// (G)MSK modulations.
/// The preamble is symbol 0 in LoRa modulation.
/// The sub-GHz radio remains in infinite preamble mode until a mode
/// configuration command is received.
pub fn set_tx_continuous_preamble(&mut self) -> Result<(), Error> {
self.write(&[OpCode::SetTxContinuousPreamble as u8])
}
}
// 5.8.4
/// Radio configuration commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
/// Set the packet type (modulation scheme).
pub fn set_packet_type(&mut self, packet_type: PacketType) -> Result<(), Error> {
self.write(&[OpCode::SetPacketType as u8, packet_type as u8])
}
/// Get the packet type.
pub fn packet_type(&mut self) -> Result<Result<PacketType, u8>, Error> {
let pkt_type: [u8; 2] = self.read_n(OpCode::GetPacketType)?;
Ok(PacketType::from_raw(pkt_type[1]))
}
/// Set the radio carrier frequency.
pub fn set_rf_frequency(&mut self, freq: &RfFreq) -> Result<(), Error> {
self.write(freq.as_slice())
}
/// Set the transmit output power and the PA ramp-up time.
pub fn set_tx_params(&mut self, params: &TxParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Power amplifier configuration.
///
/// Used to customize the maximum output power and efficiency.
pub fn set_pa_config(&mut self, pa_config: &PaConfig) -> Result<(), Error> {
self.write(pa_config.as_slice())
}
/// Operating mode to enter after a successful packet transmission or
/// packet reception.
pub fn set_tx_rx_fallback_mode(&mut self, fm: FallbackMode) -> Result<(), Error> {
self.write(&[OpCode::SetTxRxFallbackMode as u8, fm as u8])
}
/// Set channel activity detection (CAD) parameters.
pub fn set_cad_params(&mut self, params: &CadParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the data buffer base address for the packet handling in TX and RX.
///
/// There is a single buffer for both TX and RX.
/// The buffer is not memory mapped, it is accessed via the
/// [`read_buffer`](SubGhz::read_buffer) and
/// [`write_buffer`](SubGhz::write_buffer) methods.
pub fn set_buffer_base_address(&mut self, tx: u8, rx: u8) -> Result<(), Error> {
self.write(&[OpCode::SetBufferBaseAddress as u8, tx, rx])
}
/// Set the (G)FSK modulation parameters.
pub fn set_fsk_mod_params(&mut self, params: &FskModParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the LoRa modulation parameters.
pub fn set_lora_mod_params(&mut self, params: &LoRaModParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the BPSK modulation parameters.
pub fn set_bpsk_mod_params(&mut self, params: &BpskModParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the generic (FSK) packet parameters.
pub fn set_packet_params(&mut self, params: &GenericPacketParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the BPSK packet parameters.
pub fn set_bpsk_packet_params(&mut self, params: &BpskPacketParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the LoRa packet parameters.
pub fn set_lora_packet_params(&mut self, params: &LoRaPacketParams) -> Result<(), Error> {
self.write(params.as_slice())
}
/// Set the number of LoRa symbols to be received before starting the
/// reception of a LoRa packet.
///
/// Packet reception is started after `n` + 1 symbols are detected.
pub fn set_lora_symb_timeout(&mut self, n: u8) -> Result<(), Error> {
self.write(&[OpCode::SetLoRaSymbTimeout.into(), n])
}
}
// 5.8.5
/// Communication status and information commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
/// Get the radio status.
///
/// The hardware (or documentation) appears to have many bugs where this
/// will return reserved values.
/// See this thread in the ST community for details: [link]
///
/// [link]: https://community.st.com/s/question/0D53W00000hR9GQSA0/stm32wl55-getstatus-command-returns-reserved-cmdstatus
pub fn status(&mut self) -> Result<Status, Error> {
Ok(self.read_1(OpCode::GetStatus)?.into())
}
/// Get the RX buffer status.
///
/// The return tuple is (status, payload_length, buffer_pointer).
pub fn rx_buffer_status(&mut self) -> Result<(Status, u8, u8), Error> {
let data: [u8; 3] = self.read_n(OpCode::GetRxBufferStatus)?;
Ok((data[0].into(), data[1], data[2]))
}
/// Returns information on the last received (G)FSK packet.
pub fn fsk_packet_status(&mut self) -> Result<FskPacketStatus, Error> {
Ok(FskPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?))
}
/// Returns information on the last received LoRa packet.
pub fn lora_packet_status(&mut self) -> Result<LoRaPacketStatus, Error> {
Ok(LoRaPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?))
}
/// Get the instantaneous signal strength during packet reception.
///
/// The units are in dbm.
pub fn rssi_inst(&mut self) -> Result<(Status, Ratio<i16>), Error> {
let data: [u8; 2] = self.read_n(OpCode::GetRssiInst)?;
let status: Status = data[0].into();
let rssi: Ratio<i16> = Ratio::new_raw(i16::from(data[1]), -2);
Ok((status, rssi))
}
/// (G)FSK packet stats.
pub fn fsk_stats(&mut self) -> Result<Stats<FskStats>, Error> {
let data: [u8; 7] = self.read_n(OpCode::GetStats)?;
Ok(Stats::from_raw_fsk(data))
}
/// LoRa packet stats.
pub fn lora_stats(&mut self) -> Result<Stats<LoRaStats>, Error> {
let data: [u8; 7] = self.read_n(OpCode::GetStats)?;
Ok(Stats::from_raw_lora(data))
}
/// Reset the stats as reported in [`lora_stats`](SubGhz::lora_stats) and
/// [`fsk_stats`](SubGhz::fsk_stats).
pub fn reset_stats(&mut self) -> Result<(), Error> {
const RESET_STATS: [u8; 7] = [0x00; 7];
self.write(&RESET_STATS)
}
}
// 5.8.6
/// IRQ commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
/// Set the interrupt configuration.
pub fn set_irq_cfg(&mut self, cfg: &CfgIrq) -> Result<(), Error> {
self.write(cfg.as_slice())
}
/// Get the IRQ status.
pub fn irq_status(&mut self) -> Result<(Status, u16), Error> {
let data: [u8; 3] = self.read_n(OpCode::GetIrqStatus)?;
let irq_status: u16 = u16::from_be_bytes([data[1], data[2]]);
Ok((data[0].into(), irq_status))
}
/// Clear the IRQ status.
pub fn clear_irq_status(&mut self, mask: u16) -> Result<(), Error> {
self.write(&[OpCode::ClrIrqStatus as u8, (mask >> 8) as u8, mask as u8])
}
}
// 5.8.7
/// Miscellaneous commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
/// Calibrate one or several blocks at any time when in standby mode.
pub fn calibrate(&mut self, cal: u8) -> Result<(), Error> {
// bit 7 is reserved and must be kept at reset value.
self.write(&[OpCode::Calibrate as u8, cal & 0x7F])
}
/// Calibrate the image at the given frequencies.
///
/// Requires the radio to be in standby mode.
pub fn calibrate_image(&mut self, cal: CalibrateImage) -> Result<(), Error> {
self.write(&[OpCode::CalibrateImage as u8, cal.0, cal.1])
}
/// Set the radio power supply.
pub fn set_regulator_mode(&mut self, reg_mode: RegMode) -> Result<(), Error> {
self.write(&[OpCode::SetRegulatorMode as u8, reg_mode as u8])
}
/// Get the radio operational errors.
pub fn op_error(&mut self) -> Result<(Status, u16), Error> {
let data: [u8; 3] = self.read_n(OpCode::GetError)?;
Ok((data[0].into(), u16::from_be_bytes([data[1], data[2]])))
}
/// Clear all errors as reported by [`op_error`](SubGhz::op_error).
pub fn clear_error(&mut self) -> Result<(), Error> {
self.write(&[OpCode::ClrError as u8, 0x00])
}
}
// 5.8.8
/// Set TCXO mode command
impl<'d> SubGhz<'d, NoDma, NoDma> {
/// Set the TCXO trim and HSE32 ready timeout.
pub fn set_tcxo_mode(&mut self, tcxo_mode: &TcxoMode) -> Result<(), Error> {
self.write(tcxo_mode.as_slice())
}
}
/// sub-GHz radio opcodes.
///
/// See Table 41 "Sub-GHz radio SPI commands overview"
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub(crate) enum OpCode {
Calibrate = 0x89,
CalibrateImage = 0x98,
CfgDioIrq = 0x08,
ClrError = 0x07,
ClrIrqStatus = 0x02,
GetError = 0x17,
GetIrqStatus = 0x12,
GetPacketStatus = 0x14,
GetPacketType = 0x11,
GetRssiInst = 0x15,
GetRxBufferStatus = 0x13,
GetStats = 0x10,
GetStatus = 0xC0,
ReadBuffer = 0x1E,
RegRegister = 0x1D,
ResetStats = 0x00,
SetBufferBaseAddress = 0x8F,
SetCad = 0xC5,
SetCadParams = 0x88,
SetFs = 0xC1,
SetLoRaSymbTimeout = 0xA0,
SetModulationParams = 0x8B,
SetPacketParams = 0x8C,
SetPacketType = 0x8A,
SetPaConfig = 0x95,
SetRegulatorMode = 0x96,
SetRfFrequency = 0x86,
SetRx = 0x82,
SetRxDutyCycle = 0x94,
SetSleep = 0x84,
SetStandby = 0x80,
SetStopRxTimerOnPreamble = 0x9F,
SetTcxoMode = 0x97,
SetTx = 0x83,
SetTxContinuousPreamble = 0xD2,
SetTxContinuousWave = 0xD1,
SetTxParams = 0x8E,
SetTxRxFallbackMode = 0x93,
WriteBuffer = 0x0E,
WriteRegister = 0x0D,
}
impl From<OpCode> for u8 {
fn from(opcode: OpCode) -> Self {
opcode as u8
}
}
#[repr(u16)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum Register {
/// Generic bit synchronization.
GBSYNC = 0x06AC,
/// Generic packet control.
GPKTCTL1A = 0x06B8,
/// Generic whitening.
GWHITEINIRL = 0x06B9,
/// Generic CRC initial.
GCRCINIRH = 0x06BC,
/// Generic CRC polynomial.
GCRCPOLRH = 0x06BE,
/// Generic synchronization word 7.
GSYNC7 = 0x06C0,
/// Node address.
NODE = 0x06CD,
/// Broadcast address.
BROADCAST = 0x06CE,
/// LoRa synchronization word MSB.
LSYNCH = 0x0740,
/// LoRa synchronization word LSB.
#[allow(dead_code)]
LSYNCL = 0x0741,
/// Receiver gain control.
RXGAINC = 0x08AC,
/// PA over current protection.
PAOCP = 0x08E7,
/// RTC control.
RTCCTLR = 0x0902,
/// RTC period MSB.
RTCPRDR2 = 0x0906,
/// RTC period mid-byte.
#[allow(dead_code)]
RTCPRDR1 = 0x0907,
/// RTC period LSB.
#[allow(dead_code)]
RTCPRDR0 = 0x0908,
/// HSE32 OSC_IN capacitor trim.
HSEINTRIM = 0x0911,
/// HSE32 OSC_OUT capacitor trim.
HSEOUTTRIM = 0x0912,
/// SMPS control 0.
SMPSC0 = 0x0916,
/// Power control.
PC = 0x091A,
/// SMPS control 2.
SMPSC2 = 0x0923,
}
impl Register {
pub const fn address(self) -> u16 {
self as u16
}
}