lora_phy/sx126x/
mod.rs

1mod radio_kind_params;
2
3use defmt::debug;
4use embedded_hal_async::delay::DelayNs;
5use embedded_hal_async::spi::*;
6pub use radio_kind_params::TcxoCtrlVoltage;
7use radio_kind_params::*;
8
9use crate::mod_params::*;
10use crate::mod_traits::IrqState;
11use crate::{InterfaceVariant, RadioKind, SpiInterface};
12mod variant;
13pub use variant::*;
14
15// Syncwords for public and private networks
16const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444; // corresponds to sx127x 0x34
17const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424; // corresponds to sx127x 0x12
18
19// Maximum number of registers that can be added to the retention list
20const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4;
21
22// Internal frequency of the radio
23const SX126X_XTAL_FREQ: u32 = 32000000;
24
25// Scaling factor used to perform fixed-point operations
26const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14;
27
28// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT
29const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT);
30
31// Maximum value for parameter symbNum
32const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248;
33
34// Time required for the TCXO to wakeup [ms].
35const BRD_TCXO_WAKEUP_TIME: u32 = 10;
36
37// SetRx timeout argument for enabling continuous mode
38const RX_CONTINUOUS_TIMEOUT: u32 = 0xffffff;
39
40/// Power amplifier selection
41#[repr(u8)]
42pub enum DeviceSel {
43    /// Low power, power amplifier, used by sx1261
44    LowPowerPA = 1,
45    /// High power, power amplifier, used by sx1262
46    HighPowerPA = 0,
47}
48
49/// Configuration for SX126x-based boards
50pub struct Config<C: Sx126xVariant + Sized> {
51    /// LoRa chip variant on this board
52    pub chip: C,
53    /// Board is using TCXO (once enabled DIO3 cannot be used as IRQ)
54    pub tcxo_ctrl: Option<TcxoCtrlVoltage>,
55    /// Whether board is using optional DCDC in addition to LDO
56    pub use_dcdc: bool,
57    /// Whether to boost receive
58    pub rx_boost: bool,
59}
60
61/// Base for the RadioKind implementation for the LoRa chip kind and board type
62pub struct Sx126x<SPI, IV, C: Sx126xVariant + Sized> {
63    intf: SpiInterface<SPI, IV>,
64    config: Config<C>,
65}
66
67impl<SPI, IV, C> Sx126x<SPI, IV, C>
68where
69    SPI: SpiDevice<u8>,
70    IV: InterfaceVariant,
71    C: Sx126xVariant,
72{
73    /// Create an instance of the RadioKind implementation for the LoRa chip kind and board type
74    pub fn new(spi: SPI, iv: IV, config: Config<C>) -> Self {
75        let intf = SpiInterface::new(spi, iv);
76        Self { intf, config }
77    }
78
79    // Utility functions
80    async fn add_register_to_retention_list(&mut self, register: Register) -> Result<(), RadioError> {
81        let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize];
82
83        // Read the address and registers already added to the list
84        self.intf
85            .read(
86                &[
87                    OpCode::ReadRegister.value(),
88                    Register::RetentionList.addr1(),
89                    Register::RetentionList.addr2(),
90                    0x00u8,
91                ],
92                &mut buffer,
93            )
94            .await?;
95
96        let number_of_registers = buffer[0];
97        for i in 0..number_of_registers {
98            if register.addr1() == buffer[(1 + (2 * i)) as usize] && register.addr2() == buffer[(2 + (2 * i)) as usize]
99            {
100                return Ok(()); // register already in list
101            }
102        }
103
104        if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION {
105            buffer[0] += 1; // increment number of registers
106
107            buffer[(1 + (2 * number_of_registers)) as usize] = register.addr1();
108            buffer[(2 + (2 * number_of_registers)) as usize] = register.addr2();
109
110            let register = [
111                OpCode::WriteRegister.value(),
112                Register::RetentionList.addr1(),
113                Register::RetentionList.addr2(),
114            ];
115            self.intf.write_with_payload(&register, &buffer, false).await
116        } else {
117            Err(RadioError::InvalidConfiguration)
118        }
119    }
120
121    async fn update_retention_list(&mut self) -> Result<(), RadioError> {
122        self.add_register_to_retention_list(Register::RxGain).await?;
123        self.add_register_to_retention_list(Register::TxModulation).await
124    }
125
126    // Set the number of symbols the radio will wait to detect a reception
127    async fn set_lora_symbol_num_timeout(&mut self, symbol_num: u16) -> Result<(), RadioError> {
128        let mut exp = 0u8;
129        let mut mant = ((symbol_num.min(SX126X_MAX_LORA_SYMB_NUM_TIMEOUT.into()) + 1) >> 1) as u8;
130        while mant > 31 {
131            mant = (mant + 3) >> 2;
132            exp += 1;
133        }
134        let val: u8 = mant << ((2 * exp) + 1);
135        self.intf
136            .write(&[OpCode::SetLoRaSymbTimeout.value(), val], false)
137            .await?;
138
139        if symbol_num > 0 {
140            let val = exp + (mant << 3);
141            let buf = [
142                OpCode::WriteRegister.value(),
143                Register::SynchTimeout.addr1(),
144                Register::SynchTimeout.addr2(),
145                val,
146            ];
147            self.intf.write(&buf, false).await?;
148        }
149        Ok(())
150    }
151
152    async fn set_pa_config(&mut self, pa_duty_cycle: u8, hp_max: u8, device_sel: DeviceSel) -> Result<(), RadioError> {
153        const PA_LUT_RESERVED: u8 = 0x01;
154        let op_code_and_pa_config = [
155            OpCode::SetPAConfig.value(),
156            pa_duty_cycle,
157            hp_max,
158            device_sel as u8,
159            PA_LUT_RESERVED,
160        ];
161        self.intf.write(&op_code_and_pa_config, false).await
162    }
163
164    fn timeout_1(timeout: u32) -> u8 {
165        ((timeout >> 16) & 0xFF) as u8
166    }
167    fn timeout_2(timeout: u32) -> u8 {
168        ((timeout >> 8) & 0xFF) as u8
169    }
170    fn timeout_3(timeout: u32) -> u8 {
171        (timeout & 0xFF) as u8
172    }
173
174    fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 {
175        // Get integer and fractional parts of the frequency computed with a PLL step scaled value
176        let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED;
177        let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED);
178
179        (steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT)
180            + (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED)
181    }
182}
183
184impl<SPI, IV, C> RadioKind for Sx126x<SPI, IV, C>
185where
186    SPI: SpiDevice<u8>,
187    IV: InterfaceVariant,
188    C: Sx126xVariant,
189{
190    async fn init_lora(&mut self, is_public_network: bool) -> Result<(), RadioError> {
191        // DC-DC regulator setup (default is LDO)
192        if self.config.use_dcdc {
193            let reg_data = [OpCode::SetRegulatorMode.value(), RegulatorMode::UseDCDC.value()];
194            self.intf.write(&reg_data, false).await?;
195        }
196        // DIO2 acting as RF Switch (default is DIO2 as IRQ)
197        if self.config.chip.use_dio2_as_rfswitch() {
198            let cmd = [
199                OpCode::SetDIO2AsRfSwitchCtrl.value(),
200                self.config.chip.use_dio2_as_rfswitch() as u8,
201            ];
202            self.intf.write(&cmd, false).await?;
203        }
204
205        // DIO3 acting as TCXO controller (default is DIO3 as IRQ)
206        if let Some(voltage) = self.config.tcxo_ctrl {
207            // When TCXO is used, XOSC_START_ERR flag is raised at POR or at
208            // wake-up from Sleep mode in cold-start condition. This is an
209            // expected behaviour since chip is not yet aware of being clocked
210            // by TCXO and therefore this should be initially cleared manually.
211            let mut buf = [0u8; 2];
212            let _ = self
213                .intf
214                .read_with_status(&[OpCode::ClearDeviceErrors.value()], &mut buf)
215                .await?;
216
217            // Each unit is 15.625uS (which is 1/64th ms)
218            let timeout = BRD_TCXO_WAKEUP_TIME << 6;
219            let op_code_and_tcxo_control = [
220                OpCode::SetTCXOMode.value(),
221                voltage.value() & 0x07,
222                Self::timeout_1(timeout),
223                Self::timeout_2(timeout),
224                Self::timeout_3(timeout),
225            ];
226            self.intf.write(&op_code_and_tcxo_control, false).await?;
227            // Re-run calibration now that chip knows that it's running from TCXO
228            self.intf
229                .write(&[OpCode::Calibrate.value(), 0b0111_1111], false)
230                .await?;
231            self.intf.iv.wait_on_busy().await?;
232        }
233
234        // Enable LoRa packet engine...
235        self.intf
236            .write(&[OpCode::SetPacketType.value(), PacketType::LoRa.value()], false)
237            .await?;
238        let word = match is_public_network {
239            true => u16::to_be_bytes(LORA_MAC_PUBLIC_SYNCWORD),
240            false => u16::to_be_bytes(LORA_MAC_PRIVATE_SYNCWORD),
241        };
242        // ...and network syncword
243        let lora_syncword_set = [
244            OpCode::WriteRegister.value(),
245            Register::LoRaSyncword.addr1(),
246            Register::LoRaSyncword.addr2(),
247            word[0],
248            word[1],
249        ];
250        self.intf.write(&lora_syncword_set, false).await?;
251
252        self.set_tx_rx_buffer_base_address(0, 0).await?;
253        // Update register list to support warm starts from sleep mode
254        self.update_retention_list().await?;
255        Ok(())
256    }
257
258    fn create_modulation_params(
259        &self,
260        spreading_factor: SpreadingFactor,
261        bandwidth: Bandwidth,
262        coding_rate: CodingRate,
263        frequency_in_hz: u32,
264    ) -> Result<ModulationParams, RadioError> {
265        // Parameter validation
266        spreading_factor_value(spreading_factor)?;
267        bandwidth_value(bandwidth)?;
268        coding_rate_value(coding_rate)?;
269        if ((bandwidth == Bandwidth::_250KHz) || (bandwidth == Bandwidth::_500KHz)) && (frequency_in_hz < 400_000_000) {
270            return Err(RadioError::InvalidBandwidthForFrequency);
271        }
272
273        let mut low_data_rate_optimize = 0x00u8;
274        if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
275            && (bandwidth == Bandwidth::_125KHz))
276            || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
277        {
278            low_data_rate_optimize = 0x01u8;
279        }
280        Ok(ModulationParams {
281            spreading_factor,
282            bandwidth,
283            coding_rate,
284            low_data_rate_optimize,
285            frequency_in_hz,
286        })
287    }
288
289    fn create_packet_params(
290        &self,
291        mut preamble_length: u16,
292        implicit_header: bool,
293        payload_length: u8,
294        crc_on: bool,
295        iq_inverted: bool,
296        modulation_params: &ModulationParams,
297    ) -> Result<PacketParams, RadioError> {
298        if ((modulation_params.spreading_factor == SpreadingFactor::_5)
299            || (modulation_params.spreading_factor == SpreadingFactor::_6))
300            && (preamble_length < 12)
301        {
302            preamble_length = 12;
303        }
304
305        Ok(PacketParams {
306            preamble_length,
307            implicit_header,
308            payload_length,
309            crc_on,
310            iq_inverted,
311        })
312    }
313
314    async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError> {
315        self.intf.iv.reset(delay).await
316    }
317
318    // Wakeup the radio if it is in Sleep or ReceiveDutyCycle mode; otherwise, ensure it is not busy.
319    async fn ensure_ready(&mut self, mode: RadioMode) -> Result<(), RadioError> {
320        match mode {
321            RadioMode::Sleep | RadioMode::Receive(RxMode::DutyCycle(_)) => {
322                let op_code_and_null = [OpCode::GetStatus.value(), 0x00u8];
323                self.intf.write(&op_code_and_null, false).await?;
324            }
325            _ => self.intf.iv.wait_on_busy().await?,
326        }
327        Ok(())
328    }
329
330    // Use standby mode RC (not XOSC).
331    async fn set_standby(&mut self) -> Result<(), RadioError> {
332        let op_code_and_standby_mode = [OpCode::SetStandby.value(), StandbyMode::RC.value()];
333        self.intf.write(&op_code_and_standby_mode, false).await?;
334        self.intf.iv.disable_rf_switch().await
335    }
336
337    async fn set_sleep(&mut self, warm_start_if_possible: bool, delay: &mut impl DelayNs) -> Result<(), RadioError> {
338        self.intf.iv.disable_rf_switch().await?;
339        let sleep_params = SleepParams {
340            wakeup_rtc: false,
341            reset: false,
342            warm_start: warm_start_if_possible,
343        };
344        let op_code_and_sleep_params = [OpCode::SetSleep.value(), sleep_params.value()];
345        self.intf.write(&op_code_and_sleep_params, true).await?;
346        delay.delay_ms(2).await;
347
348        Ok(())
349    }
350
351    async fn set_tx_rx_buffer_base_address(
352        &mut self,
353        tx_base_addr: usize,
354        rx_base_addr: usize,
355    ) -> Result<(), RadioError> {
356        if tx_base_addr > 255 || rx_base_addr > 255 {
357            return Err(RadioError::InvalidBaseAddress(tx_base_addr, rx_base_addr));
358        }
359        let op_code_and_base_addrs = [
360            OpCode::SetBufferBaseAddress.value(),
361            tx_base_addr as u8,
362            rx_base_addr as u8,
363        ];
364        self.intf.write(&op_code_and_base_addrs, false).await
365    }
366
367    // Set parameters associated with power for a send operation. Currently, over current protection (OCP) uses the default set automatically after set_pa_config()
368    //   output_power            desired RF output power (dBm)
369    //   mdltn_params            needed for a power vs channel frequency validation
370    //   tx_boosted_if_possible  not pertinent for sx126x
371    //   is_tx_prep              indicates which ramp up time to use
372    async fn set_tx_power_and_ramp_time(
373        &mut self,
374        output_power: i32,
375        mdltn_params: Option<&ModulationParams>,
376        is_tx_prep: bool,
377    ) -> Result<(), RadioError> {
378        let tx_params_power;
379        let ramp_time = match is_tx_prep {
380            true => RampTime::Ramp40Us,   // for instance, prior to TX or CAD
381            false => RampTime::Ramp200Us, // for instance, on initialization
382        };
383
384        match self.config.chip.get_device_sel() {
385            DeviceSel::LowPowerPA => {
386                const LOW_POWER_MIN: i32 = -17;
387                const LOW_POWER_MAX: i32 = 15;
388                // Clamp power between [-17, 15] dBm
389                let txp = output_power.clamp(LOW_POWER_MIN, LOW_POWER_MAX);
390
391                if txp == 15 {
392                    if let Some(m_p) = mdltn_params {
393                        if m_p.frequency_in_hz < 400_000_000 {
394                            return Err(RadioError::InvalidOutputPowerForFrequency);
395                        }
396                    }
397                }
398
399                // For SX1261:
400                // if f < 400 MHz, paDutyCycle should not be higher than 0x04,
401                // if f > 400 Mhz, paDutyCycle should not be higher than 0x07.
402                // From Table 13-21: PA Operating Modes with Optimal Settings
403                match txp {
404                    LOW_POWER_MAX => {
405                        self.set_pa_config(0x06, 0x00, DeviceSel::LowPowerPA).await?;
406                        tx_params_power = 14;
407                    }
408                    11..=14 => {
409                        self.set_pa_config(0x04, 0x00, DeviceSel::LowPowerPA).await?;
410                        tx_params_power = txp as u8;
411                    }
412                    // 10 and less
413                    LOW_POWER_MIN..=10 => {
414                        self.set_pa_config(0x01, 0x00, DeviceSel::LowPowerPA).await?;
415                        // table indicates 10 dBm => txp = 13, therefore we add 3 to values below 10
416                        tx_params_power = txp as u8 + 3;
417                    }
418                    _ => unreachable!("Invalid output power value for low power PA!"),
419                }
420            }
421            DeviceSel::HighPowerPA => {
422                const HIGH_POWER_MIN: i32 = -9;
423                const HIGH_POWER_MAX: i32 = 22;
424                // Clamp power between [-9, 22] dBm
425                let txp = output_power.clamp(HIGH_POWER_MIN, HIGH_POWER_MAX);
426                // Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
427                let mut tx_clamp_cfg = [0x00u8];
428                self.intf
429                    .read(
430                        &[
431                            OpCode::ReadRegister.value(),
432                            Register::TxClampCfg.addr1(),
433                            Register::TxClampCfg.addr2(),
434                            0x00u8,
435                        ],
436                        &mut tx_clamp_cfg,
437                    )
438                    .await?;
439                tx_clamp_cfg[0] |= 0x0F << 1;
440                let register_and_tx_clamp_cfg = [
441                    OpCode::WriteRegister.value(),
442                    Register::TxClampCfg.addr1(),
443                    Register::TxClampCfg.addr2(),
444                    tx_clamp_cfg[0],
445                ];
446                self.intf.write(&register_and_tx_clamp_cfg, false).await?;
447
448                // From Table 13-21: PA Operating Modes with Optimal Settings
449                match txp {
450                    21..=HIGH_POWER_MAX => {
451                        self.set_pa_config(0x04, 0x07, DeviceSel::HighPowerPA).await?;
452                        tx_params_power = 22;
453                    }
454                    18..=20 => {
455                        self.set_pa_config(0x03, 0x05, DeviceSel::HighPowerPA).await?;
456                        // table indicates 20 dBm => txp = 22, therefore we add 2 to this range
457                        tx_params_power = txp as u8 + 2;
458                    }
459                    15..=17 => {
460                        self.set_pa_config(0x02, 0x03, DeviceSel::HighPowerPA).await?;
461                        // table indicates 17 dBm => txp = 22, therefore we add 5 to this range
462                        tx_params_power = txp as u8 + 5;
463                    }
464                    HIGH_POWER_MIN..=14 => {
465                        self.set_pa_config(0x02, 0x02, DeviceSel::HighPowerPA).await?;
466                        // table indicates 14 dBm => txp = 22, therefore we add 8 to this range
467                        tx_params_power = txp as u8 + 8;
468                    }
469                    _ => {
470                        unreachable!("Invalid output power value for high power PA!")
471                    }
472                }
473            }
474        }
475        let op_code_and_tx_params = [OpCode::SetTxParams.value(), tx_params_power, ramp_time.value()];
476        self.intf.write(&op_code_and_tx_params, false).await
477    }
478
479    async fn set_modulation_params(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
480        let spreading_factor_val = spreading_factor_value(mdltn_params.spreading_factor)?;
481        let bandwidth_val = bandwidth_value(mdltn_params.bandwidth)?;
482        let coding_rate_val = coding_rate_value(mdltn_params.coding_rate)?;
483        debug!(
484            "sf = {}, bw = {}, cr = {}",
485            spreading_factor_val, bandwidth_val, coding_rate_val
486        );
487        let op_code_and_mod_params = [
488            OpCode::SetModulationParams.value(),
489            spreading_factor_val,
490            bandwidth_val,
491            coding_rate_val,
492            mdltn_params.low_data_rate_optimize,
493        ];
494        self.intf.write(&op_code_and_mod_params, false).await?;
495
496        // Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
497        let mut tx_mod = [0x00u8];
498        self.intf
499            .read(
500                &[
501                    OpCode::ReadRegister.value(),
502                    Register::TxModulation.addr1(),
503                    Register::TxModulation.addr2(),
504                    0x00u8,
505                ],
506                &mut tx_mod,
507            )
508            .await?;
509        if mdltn_params.bandwidth == Bandwidth::_500KHz {
510            let register_and_tx_mod_update = [
511                OpCode::WriteRegister.value(),
512                Register::TxModulation.addr1(),
513                Register::TxModulation.addr2(),
514                tx_mod[0] & (!(1 << 2)),
515            ];
516            self.intf.write(&register_and_tx_mod_update, false).await
517        } else {
518            let register_and_tx_mod_update = [
519                OpCode::WriteRegister.value(),
520                Register::TxModulation.addr1(),
521                Register::TxModulation.addr2(),
522                tx_mod[0] | (1 << 2),
523            ];
524            self.intf.write(&register_and_tx_mod_update, false).await
525        }
526    }
527
528    async fn set_packet_params(&mut self, pkt_params: &PacketParams) -> Result<(), RadioError> {
529        let op_code_and_pkt_params = [
530            OpCode::SetPacketParams.value(),
531            ((pkt_params.preamble_length >> 8) & 0xFF) as u8,
532            (pkt_params.preamble_length & 0xFF) as u8,
533            pkt_params.implicit_header as u8,
534            pkt_params.payload_length,
535            pkt_params.crc_on as u8,
536            pkt_params.iq_inverted as u8,
537        ];
538        self.intf.write(&op_code_and_pkt_params, false).await?;
539
540        // Optimize Inverted IQ Operation, otherwise packet loss with longer packets might occur.
541        let mut iq_polarity = [0x00u8];
542        self.intf
543            .read(
544                &[
545                    OpCode::ReadRegister.value(),
546                    Register::IQPolarity.addr1(),
547                    Register::IQPolarity.addr2(),
548                    0x00u8,
549                ],
550                &mut iq_polarity,
551            )
552            .await?;
553
554        let reg = if pkt_params.iq_inverted {
555            iq_polarity[0] & (!(1 << 2))
556        } else {
557            iq_polarity[0] | (1 << 2)
558        };
559
560        let op = [
561            OpCode::WriteRegister.value(),
562            Register::IQPolarity.addr1(),
563            Register::IQPolarity.addr2(),
564            reg,
565        ];
566        self.intf.write(&op, false).await?;
567        Ok(())
568    }
569
570    // Calibrate the image rejection based on the given frequency
571    async fn calibrate_image(&mut self, frequency_in_hz: u32) -> Result<(), RadioError> {
572        let mut cal_freq = [0x00u8, 0x00u8];
573
574        if frequency_in_hz > 900000000 {
575            cal_freq[0] = 0xE1;
576            cal_freq[1] = 0xE9;
577        } else if frequency_in_hz > 850000000 {
578            cal_freq[0] = 0xD7;
579            cal_freq[1] = 0xDB;
580        } else if frequency_in_hz > 770000000 {
581            cal_freq[0] = 0xC1;
582            cal_freq[1] = 0xC5;
583        } else if frequency_in_hz > 460000000 {
584            cal_freq[0] = 0x75;
585            cal_freq[1] = 0x81;
586        } else if frequency_in_hz > 425000000 {
587            cal_freq[0] = 0x6B;
588            cal_freq[1] = 0x6F;
589        }
590
591        let op_code_and_cal_freq = [OpCode::CalibrateImage.value(), cal_freq[0], cal_freq[1]];
592        self.intf.write(&op_code_and_cal_freq, false).await
593    }
594
595    async fn set_channel(&mut self, frequency_in_hz: u32) -> Result<(), RadioError> {
596        debug!("channel = {}", frequency_in_hz);
597        let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency_in_hz);
598        let op_code_and_pll_steps = [
599            OpCode::SetRFFrequency.value(),
600            ((freq_in_pll_steps >> 24) & 0xFF) as u8,
601            ((freq_in_pll_steps >> 16) & 0xFF) as u8,
602            ((freq_in_pll_steps >> 8) & 0xFF) as u8,
603            (freq_in_pll_steps & 0xFF) as u8,
604        ];
605        self.intf.write(&op_code_and_pll_steps, false).await
606    }
607
608    async fn set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError> {
609        let op_code_and_offset = [OpCode::WriteBuffer.value(), 0x00u8];
610        self.intf.write_with_payload(&op_code_and_offset, payload, false).await
611    }
612
613    async fn do_tx(&mut self) -> Result<(), RadioError> {
614        self.intf.iv.enable_rf_switch_tx().await?;
615
616        // Disable timeout
617        let cmd = [
618            OpCode::SetTx.value(),
619            Self::timeout_1(0),
620            Self::timeout_2(0),
621            Self::timeout_3(0),
622        ];
623        self.intf.write(&cmd, false).await
624    }
625
626    async fn do_rx(&mut self, rx_mode: RxMode) -> Result<(), RadioError> {
627        self.intf.iv.enable_rf_switch_rx().await?;
628
629        // Stop the Rx timer on preamble detection
630        let op_code_and_true_flag = [OpCode::SetStopRxTimerOnPreamble.value(), 0x01u8];
631        self.intf.write(&op_code_and_true_flag, false).await?;
632
633        let num_symbols = match rx_mode {
634            RxMode::DutyCycle(_) | RxMode::Continuous => 0,
635            RxMode::Single(n) => n,
636        };
637        self.set_lora_symbol_num_timeout(num_symbols).await?;
638
639        let rx_gain = if self.config.rx_boost { 0x96 } else { 0x94 };
640        let register_and_rx_gain = [
641            OpCode::WriteRegister.value(),
642            Register::RxGain.addr1(),
643            Register::RxGain.addr2(),
644            rx_gain,
645        ];
646        self.intf.write(&register_and_rx_gain, false).await?;
647
648        match rx_mode {
649            RxMode::DutyCycle(args) => {
650                let op = [
651                    OpCode::SetRxDutyCycle.value(),
652                    Self::timeout_1(args.rx_time),
653                    Self::timeout_2(args.rx_time),
654                    Self::timeout_3(args.rx_time),
655                    Self::timeout_1(args.sleep_time),
656                    Self::timeout_2(args.sleep_time),
657                    Self::timeout_3(args.sleep_time),
658                ];
659                self.intf.write(&op, false).await
660            }
661            RxMode::Single(_) => {
662                let op = [
663                    OpCode::SetRx.value(),
664                    Self::timeout_1(0),
665                    Self::timeout_2(0),
666                    Self::timeout_3(0),
667                ];
668                self.intf.write(&op, false).await
669            }
670            RxMode::Continuous => {
671                let op = [
672                    OpCode::SetRx.value(),
673                    Self::timeout_1(RX_CONTINUOUS_TIMEOUT),
674                    Self::timeout_2(RX_CONTINUOUS_TIMEOUT),
675                    Self::timeout_3(RX_CONTINUOUS_TIMEOUT),
676                ];
677                self.intf.write(&op, false).await
678            }
679        }
680    }
681
682    async fn get_rx_payload(
683        &mut self,
684        rx_pkt_params: &PacketParams,
685        receiving_buffer: &mut [u8],
686    ) -> Result<u8, RadioError> {
687        let op_code = [OpCode::GetRxBufferStatus.value()];
688        let mut rx_buffer_status = [0x00u8; 2];
689        let read_status = self.intf.read_with_status(&op_code, &mut rx_buffer_status).await?;
690        if OpStatusErrorMask::is_error(read_status) {
691            return Err(RadioError::OpError(read_status));
692        }
693
694        let mut payload_length_buffer = [0x00u8];
695        if rx_pkt_params.implicit_header {
696            self.intf
697                .read(
698                    &[
699                        OpCode::ReadRegister.value(),
700                        Register::PayloadLength.addr1(),
701                        Register::PayloadLength.addr2(),
702                        0x00u8,
703                    ],
704                    &mut payload_length_buffer,
705                )
706                .await?;
707        } else {
708            payload_length_buffer[0] = rx_buffer_status[0];
709        }
710
711        let payload_length = payload_length_buffer[0];
712        let offset = rx_buffer_status[1];
713
714        if (payload_length as usize) > receiving_buffer.len() {
715            Err(RadioError::PayloadSizeMismatch(
716                payload_length as usize,
717                receiving_buffer.len(),
718            ))
719        } else {
720            self.intf
721                .read(
722                    &[OpCode::ReadBuffer.value(), offset, 0x00u8],
723                    &mut receiving_buffer[..payload_length as usize],
724                )
725                .await?;
726            Ok(payload_length)
727        }
728    }
729
730    async fn get_rx_packet_status(&mut self) -> Result<PacketStatus, RadioError> {
731        let op_code = [OpCode::GetPacketStatus.value()];
732        let mut pkt_status = [0x00u8; 3];
733        let read_status = self.intf.read_with_status(&op_code, &mut pkt_status).await?;
734        if OpStatusErrorMask::is_error(read_status) {
735            return Err(RadioError::OpError(read_status));
736        }
737        // check this ???
738        let rssi = ((-(pkt_status[0] as i32)) >> 1) as i16;
739        let snr = (((pkt_status[1] as i8) + 2) >> 2) as i16;
740        let _signal_rssi = ((-(pkt_status[2] as i32)) >> 1) as i16; // unused currently
741
742        Ok(PacketStatus { rssi, snr })
743    }
744
745    async fn do_cad(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
746        self.intf.iv.enable_rf_switch_rx().await?;
747
748        let mut rx_gain_final = 0x94u8;
749        // if Rx boosted, set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
750        if self.config.rx_boost {
751            rx_gain_final = 0x96u8;
752        }
753
754        let register_and_rx_gain = [
755            OpCode::WriteRegister.value(),
756            Register::RxGain.addr1(),
757            Register::RxGain.addr2(),
758            rx_gain_final,
759        ];
760        self.intf.write(&register_and_rx_gain, false).await?;
761
762        // See:
763        //  https://lora-developers.semtech.com/documentation/tech-papers-and-guides/channel-activity-detection-ensuring-your-lora-packets-are-sent/how-to-ensure-your-lora-packets-are-sent-properly
764        // for default values used here.
765        let spreading_factor_val = spreading_factor_value(mdltn_params.spreading_factor)?;
766        let op_code_and_cad_params = [
767            OpCode::SetCADParams.value(),
768            CADSymbols::_8.value(),      // number of symbols for detection
769            spreading_factor_val + 13u8, // limit for detection of SNR peak
770            10u8,                        // minimum symbol recognition
771            0x00u8,                      // CAD exit mode without listen-before-send or subsequent receive processing
772            0x00u8,                      // no timeout
773            0x00u8,
774            0x00u8,
775        ];
776        self.intf.write(&op_code_and_cad_params, false).await?;
777
778        let op_code_for_set_cad = [OpCode::SetCAD.value()];
779        self.intf.write(&op_code_for_set_cad, false).await
780    }
781
782    // Set the IRQ mask and DIO masks
783    async fn set_irq_params(&mut self, radio_mode: Option<RadioMode>) -> Result<(), RadioError> {
784        let mut irq_mask: u16 = IrqMask::None.value();
785        let mut dio1_mask: u16 = IrqMask::None.value();
786        let dio2_mask: u16 = IrqMask::None.value();
787        let dio3_mask: u16 = IrqMask::None.value();
788
789        match radio_mode {
790            Some(RadioMode::Standby) => {
791                irq_mask = IrqMask::All.value();
792                dio1_mask = IrqMask::All.value();
793            }
794            Some(RadioMode::Transmit) => {
795                irq_mask = IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value();
796                dio1_mask = IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value();
797            }
798            Some(RadioMode::Receive(_)) => {
799                irq_mask = IrqMask::All.value();
800                dio1_mask = IrqMask::All.value();
801            }
802            Some(RadioMode::ChannelActivityDetection) => {
803                irq_mask = IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value();
804                dio1_mask = IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value();
805            }
806            _ => {}
807        }
808
809        let op_code_and_masks = [
810            OpCode::CfgDIOIrq.value(),
811            ((irq_mask >> 8) & 0x00FF) as u8,
812            (irq_mask & 0x00FF) as u8,
813            ((dio1_mask >> 8) & 0x00FF) as u8,
814            (dio1_mask & 0x00FF) as u8,
815            ((dio2_mask >> 8) & 0x00FF) as u8,
816            (dio2_mask & 0x00FF) as u8,
817            ((dio3_mask >> 8) & 0x00FF) as u8,
818            (dio3_mask & 0x00FF) as u8,
819        ];
820        self.intf.write(&op_code_and_masks, false).await
821    }
822
823    async fn set_tx_continuous_wave_mode(&mut self) -> Result<(), RadioError> {
824        self.intf.iv.enable_rf_switch_tx().await?;
825
826        let op_code = [OpCode::SetTxContinuousWave.value()];
827        self.intf.write(&op_code, false).await
828    }
829
830    async fn await_irq(&mut self) -> Result<(), RadioError> {
831        self.intf.iv.await_irq().await
832    }
833
834    /// Process the radio IRQ. Log unexpected interrupts. Packets from other
835    /// devices can cause unexpected interrupts.
836    ///
837    /// NB! Do not await this future in a select branch as interrupting it
838    /// mid-flow could cause radio lock up.
839    async fn process_irq_event(
840        &mut self,
841        radio_mode: RadioMode,
842        cad_activity_detected: Option<&mut bool>,
843        clear_interrupts: bool,
844    ) -> Result<Option<IrqState>, RadioError> {
845        let op_code = [OpCode::GetIrqStatus.value()];
846        let mut irq_status = [0x00u8, 0x00u8];
847        // Assuming intf.read_with_status is an existing async method that reads the IRQ status.
848        let read_status = self.intf.read_with_status(&op_code, &mut irq_status).await?;
849        let irq_flags = ((irq_status[0] as u16) << 8) | (irq_status[1] as u16);
850
851        if clear_interrupts && irq_flags != 0 {
852            let op_code_and_irq_status = [OpCode::ClrIrqStatus.value(), irq_status[0], irq_status[1]];
853            self.intf.write(&op_code_and_irq_status, false).await?;
854        }
855
856        if OpStatusErrorMask::is_error(read_status) {
857            debug!(
858                "process_irq read status error = 0x{:x} in radio mode {}",
859                read_status, radio_mode
860            );
861        }
862
863        debug!(
864            "process_irq satisfied: irq_flags = 0x{:x} in radio mode {}",
865            irq_flags, radio_mode
866        );
867
868        if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() {
869            debug!("HeaderValid in radio mode {}", radio_mode);
870        }
871        if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() {
872            debug!("PreambleDetected in radio mode {}", radio_mode);
873        }
874        if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() {
875            debug!("SyncwordValid in radio mode {}", radio_mode);
876        }
877
878        match radio_mode {
879            RadioMode::Transmit => {
880                if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
881                    return Ok(Some(IrqState::Done));
882                }
883                if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
884                    return Err(RadioError::TransmitTimeout);
885                }
886            }
887            RadioMode::Receive(rx_mode) => {
888                if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() {
889                    debug!("HeaderError in radio mode {}", radio_mode);
890                }
891                if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() {
892                    debug!("CRCError in radio mode {}", radio_mode);
893                }
894                if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
895                    debug!("RxDone in radio mode {}", radio_mode);
896                    if rx_mode != RxMode::Continuous {
897                        // implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3)
898                        let register_and_clear = [
899                            OpCode::WriteRegister.value(),
900                            Register::RTCCtrl.addr1(),
901                            Register::RTCCtrl.addr2(),
902                            0x00u8,
903                        ];
904                        self.intf.write(&register_and_clear, false).await?;
905
906                        let mut evt_clr = [0x00u8];
907                        self.intf
908                            .read(
909                                &[
910                                    OpCode::ReadRegister.value(),
911                                    Register::EvtClr.addr1(),
912                                    Register::EvtClr.addr2(),
913                                    0x00u8,
914                                ],
915                                &mut evt_clr,
916                            )
917                            .await?;
918                        evt_clr[0] |= 1 << 1;
919                        let register_and_evt_clear = [
920                            OpCode::WriteRegister.value(),
921                            Register::EvtClr.addr1(),
922                            Register::EvtClr.addr2(),
923                            evt_clr[0],
924                        ];
925                        self.intf.write(&register_and_evt_clear, false).await?;
926                    }
927                    return Ok(Some(IrqState::Done));
928                }
929                if IrqMask::PreambleDetected.is_set_in(irq_flags) || IrqMask::HeaderValid.is_set_in(irq_flags) {
930                    return Ok(Some(IrqState::PreambleReceived));
931                }
932                if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
933                    return Err(RadioError::ReceiveTimeout);
934                }
935            }
936            RadioMode::ChannelActivityDetection => {
937                if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
938                    if let Some(detected) = cad_activity_detected {
939                        *detected =
940                            (irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
941                    }
942                    return Ok(Some(IrqState::Done));
943                }
944            }
945            RadioMode::Sleep | RadioMode::Standby => {
946                defmt::warn!("IRQ during sleep/standby?");
947            }
948            RadioMode::FrequencySynthesis => todo!(),
949        }
950
951        // If none of the specific conditions are met, return None to indicate no IRQ state change.
952        Ok(None)
953    }
954}
955
956#[cfg(test)]
957mod tests {
958    // use super::*;
959
960    #[test]
961    // -17 (0xEF) to +14 (0x0E) dBm by step of 1 dB if low power PA is selected
962    // -9 (0xF7) to +22 (0x16) dBm by step of 1 dB if high power PA is selected
963    fn power_level_negative_value_conversion() {
964        let mut i32_val: i32 = -17;
965        assert_eq!(i32_val as u8, 0xefu8);
966        i32_val = -9;
967        assert_eq!(i32_val as u8, 0xf7u8);
968    }
969}