lora_phy/sx127x/
mod.rs

1mod radio_kind_params;
2mod sx1272;
3pub use sx1272::Sx1272;
4mod sx1276;
5pub use sx1276::Sx1276;
6
7use defmt::debug;
8use embedded_hal_async::delay::DelayNs;
9use embedded_hal_async::spi::*;
10use radio_kind_params::*;
11
12use crate::mod_params::*;
13use crate::mod_traits::IrqState;
14use crate::{InterfaceVariant, RadioKind, SpiInterface};
15
16// Syncwords for public and private networks
17const LORA_MAC_PUBLIC_SYNCWORD: u8 = 0x34; // corresponds to sx126x 0x3444
18const LORA_MAC_PRIVATE_SYNCWORD: u8 = 0x12; // corresponds to sx126x 0x1424
19
20// TCXO flag
21const TCXO_FOR_OSCILLATOR: u8 = 0x10u8;
22
23// Frequency synthesizer step for frequency calculation (Hz)
24const FREQUENCY_SYNTHESIZER_STEP: f64 = 61.03515625; // FXOSC (32 MHz) * 1000000 (Hz/MHz) / 524288 (2^19)
25
26// Limits for preamble detection window in single reception mode
27const SX127X_MIN_LORA_SYMB_NUM_TIMEOUT: u16 = 4;
28const SX127X_MAX_LORA_SYMB_NUM_TIMEOUT: u16 = 1023;
29
30// Constant values need to compute the RSSI value
31const SX1272_RSSI_OFFSET: i16 = -139;
32const SX1276_RSSI_OFFSET_LF: i16 = -164;
33const SX1276_RSSI_OFFSET_HF: i16 = -157;
34const SX1276_RF_MID_BAND_THRESH: u32 = 525_000_000;
35
36/// Configuration for SX127x-based boards
37pub struct Config<C: Sx127xVariant> {
38    /// LoRa chip used on specific board
39    pub chip: C,
40    /// Whether board is using crystal oscillator or external clock
41    pub tcxo_used: bool,
42    /// Whether to use PA_BOOST for transmit instead of RFO (sx1272) or RFO_LF (sx1276).
43    /// NB! Depends on board layout.
44    pub tx_boost: bool,
45    /// Whether to boost receive
46    pub rx_boost: bool,
47}
48
49/// Base for the RadioKind implementation for the LoRa chip kind and board type
50pub struct Sx127x<SPI, IV, C: Sx127xVariant + Sized> {
51    intf: SpiInterface<SPI, IV>,
52    config: Config<C>,
53}
54
55impl<SPI, IV, C> Sx127x<SPI, IV, C>
56where
57    SPI: SpiDevice<u8>,
58    IV: InterfaceVariant,
59    C: Sx127xVariant,
60{
61    /// Create an instance of the RadioKind implementation for the LoRa chip kind and board type
62    pub fn new(spi: SPI, iv: IV, config: Config<C>) -> Self {
63        let intf = SpiInterface::new(spi, iv);
64        Self { intf, config }
65    }
66
67    // Utility functions
68    async fn write_register(&mut self, register: Register, value: u8) -> Result<(), RadioError> {
69        let write_buffer = [register.write_addr(), value];
70        self.intf.write(&write_buffer, false).await
71    }
72
73    async fn read_register(&mut self, register: Register) -> Result<u8, RadioError> {
74        let write_buffer = [register.read_addr()];
75        let mut read_buffer = [0x00u8];
76        self.intf.read(&write_buffer, &mut read_buffer).await?;
77        Ok(read_buffer[0])
78    }
79
80    async fn read_buffer(&mut self, register: Register, buf: &mut [u8]) -> Result<(), RadioError> {
81        self.intf.read(&[register.read_addr()], buf).await
82    }
83
84    async fn write_buffer(&mut self, register: Register, buf: &[u8]) -> Result<(), RadioError> {
85        self.intf.write_with_payload(&[register.write_addr()], buf, false).await
86    }
87
88    // Set the number of symbols the radio will wait to detect a reception (up to 1023 symbols)
89    async fn set_lora_symbol_num_timeout(&mut self, symbol_num: u16) -> Result<(), RadioError> {
90        let val = symbol_num.min(SX127X_MAX_LORA_SYMB_NUM_TIMEOUT);
91
92        let symbol_num_msb = ((val >> 8) & 0x03) as u8;
93        let symbol_num_lsb = (val & 0xff) as u8;
94        let mut config_2 = self.read_register(Register::RegModemConfig2).await?;
95        config_2 = (config_2 & 0xfcu8) | symbol_num_msb;
96        self.write_register(Register::RegModemConfig2, config_2).await?;
97        self.write_register(Register::RegSymbTimeoutLsb, symbol_num_lsb).await
98    }
99
100    // Set the over current protection (mA) on the radio
101    async fn set_ocp(&mut self, ocp_trim: OcpTrim) -> Result<(), RadioError> {
102        self.write_register(Register::RegOcp, ocp_trim.value()).await
103    }
104}
105
106impl<SPI, IV, C> RadioKind for Sx127x<SPI, IV, C>
107where
108    SPI: SpiDevice<u8>,
109    IV: InterfaceVariant,
110    C: Sx127xVariant,
111{
112    async fn init_lora(&mut self, is_public_network: bool) -> Result<(), RadioError> {
113        if self.config.tcxo_used {
114            self.write_register(C::reg_txco(), TCXO_FOR_OSCILLATOR).await?;
115        }
116
117        let syncword = if is_public_network {
118            LORA_MAC_PUBLIC_SYNCWORD
119        } else {
120            LORA_MAC_PRIVATE_SYNCWORD
121        };
122        self.write_register(Register::RegSyncWord, syncword).await?;
123
124        self.set_tx_rx_buffer_base_address(0, 0).await?;
125        Ok(())
126    }
127
128    fn create_modulation_params(
129        &self,
130        spreading_factor: SpreadingFactor,
131        bandwidth: Bandwidth,
132        coding_rate: CodingRate,
133        frequency_in_hz: u32,
134    ) -> Result<ModulationParams, RadioError> {
135        // Parameter validation
136        spreading_factor_value(spreading_factor)?;
137        coding_rate_value(coding_rate)?;
138        C::bandwidth_value(bandwidth)?;
139        if ((bandwidth == Bandwidth::_250KHz) || (bandwidth == Bandwidth::_500KHz)) && (frequency_in_hz < 400_000_000) {
140            return Err(RadioError::InvalidBandwidthForFrequency);
141        }
142
143        // Section 4.1.1.5 and 4.1.1.6
144        let bw_in_hz = u32::from(bandwidth);
145        let symbol_duration = 1000 / (bw_in_hz / (0x01u32 << spreading_factor_value(spreading_factor)?));
146        let mut low_data_rate_optimize = 0x00u8;
147        if symbol_duration > 16 {
148            low_data_rate_optimize = 0x01u8
149        }
150
151        Ok(ModulationParams {
152            spreading_factor,
153            bandwidth,
154            coding_rate,
155            low_data_rate_optimize,
156            frequency_in_hz,
157        })
158    }
159
160    fn create_packet_params(
161        &self,
162        preamble_length: u16,
163        implicit_header: bool,
164        payload_length: u8,
165        crc_on: bool,
166        iq_inverted: bool,
167        modulation_params: &ModulationParams,
168    ) -> Result<PacketParams, RadioError> {
169        // Parameter validation
170        if (modulation_params.spreading_factor == SpreadingFactor::_6) && !implicit_header {
171            return Err(RadioError::InvalidSF6ExplicitHeaderRequest);
172        }
173
174        Ok(PacketParams {
175            preamble_length,
176            implicit_header,
177            payload_length,
178            crc_on,
179            iq_inverted,
180        })
181    }
182
183    async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError> {
184        self.intf.iv.reset(delay).await?;
185        self.set_sleep(false, delay).await?; // ensure sleep mode is entered so that the LoRa mode bit is set
186        Ok(())
187    }
188
189    async fn ensure_ready(&mut self, _mode: RadioMode) -> Result<(), RadioError> {
190        Ok(())
191    }
192
193    async fn set_standby(&mut self) -> Result<(), RadioError> {
194        self.write_register(Register::RegOpMode, LoRaMode::Standby.value())
195            .await?;
196        self.intf.iv.disable_rf_switch().await
197    }
198
199    async fn set_sleep(&mut self, _warm_start_if_possible: bool, _delay: &mut impl DelayNs) -> Result<(), RadioError> {
200        // Warm start is unavailable for sx127x
201        self.intf.iv.disable_rf_switch().await?;
202        let buf = [Register::RegOpMode.write_addr(), LoRaMode::Sleep.value()];
203        // NB! Switching to sleep mode is "sleep" command...
204        self.intf.write(&buf, true).await?;
205
206        Ok(())
207    }
208
209    async fn set_tx_rx_buffer_base_address(
210        &mut self,
211        tx_base_addr: usize,
212        rx_base_addr: usize,
213    ) -> Result<(), RadioError> {
214        if tx_base_addr > 255 || rx_base_addr > 255 {
215            return Err(RadioError::InvalidBaseAddress(tx_base_addr, rx_base_addr));
216        }
217        self.write_register(Register::RegFifoTxBaseAddr, 0x00u8).await?;
218        self.write_register(Register::RegFifoRxBaseAddr, 0x00u8).await
219    }
220
221    // Set parameters associated with power for a send operation.
222    //   p_out                   desired RF output power (dBm)
223    //   mdltn_params            needed for a power vs channel frequency validation
224    //   tx_boosted_if_possible  determine if transmit boost is requested
225    //   is_tx_prep              indicates which ramp up time to use
226    async fn set_tx_power_and_ramp_time(
227        &mut self,
228        p_out: i32,
229        _mdltn_params: Option<&ModulationParams>,
230        is_tx_prep: bool,
231    ) -> Result<(), RadioError> {
232        debug!("tx power = {}", p_out);
233
234        // Configure tx power and boost
235        C::set_tx_power(self, p_out, self.config.tx_boost).await?;
236
237        let ramp_time = match is_tx_prep {
238            true => RampTime::Ramp40Us,   // for instance, prior to TX or CAD
239            false => RampTime::Ramp250Us, // for instance, on initialization
240        };
241
242        let val = C::ramp_value(ramp_time);
243        self.write_register(Register::RegPaRamp, val).await
244    }
245
246    async fn set_modulation_params(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
247        let sf_val = spreading_factor_value(mdltn_params.spreading_factor)?;
248        let bw_val = C::bandwidth_value(mdltn_params.bandwidth)?;
249        let coding_rate_denominator_val = coding_rate_denominator_value(mdltn_params.coding_rate)?;
250        debug!(
251            "sf = {}, bw = {}, cr_denom = {}",
252            sf_val, bw_val, coding_rate_denominator_val
253        );
254        // Configure LoRa optimization (0x31) and detection threshold registers (0x37)
255        let (opt, thr) = match mdltn_params.spreading_factor {
256            SpreadingFactor::_6 => (0x05, 0x0c),
257            _ => (0x03, 0x0a),
258        };
259        let reg_val = self.read_register(Register::RegDetectionOptimize).await?;
260        // Keep reserved bits [6:3] for RegDetectOptimize
261        let val = (reg_val & 0b0111_1000) | opt;
262        self.write_register(Register::RegDetectionOptimize, val).await?;
263        self.write_register(Register::RegDetectionThreshold, thr).await?;
264        // Spreading Factor, Bandwidth, codingrate, ldro
265
266        C::set_modulation_params(self, mdltn_params).await?;
267
268        Ok(())
269    }
270
271    async fn set_packet_params(&mut self, pkt_params: &PacketParams) -> Result<(), RadioError> {
272        self.write_register(
273            Register::RegPreambleMsb,
274            ((pkt_params.preamble_length >> 8) & 0x00ff) as u8,
275        )
276        .await?;
277        self.write_register(Register::RegPreambleLsb, (pkt_params.preamble_length & 0x00ff) as u8)
278            .await?;
279
280        C::set_packet_params(self, pkt_params).await?;
281
282        // IQ inversion:
283        // RegInvertiq - [0x33]
284        // [6] - InvertIQRX
285        // [5:1] - Reserved: 0x13
286        // [0] - InvertIQTX
287        // RegInvertiq2 - [0x3b]
288        // Set to 0x19 when RX, otherwise set 0x1d
289        let (iq1, iq2) = match pkt_params.iq_inverted {
290            true => (1 << 6, 0x19),
291            false => (1 << 0, 0x1d),
292        };
293        // Keep reserved value for InvertIq as well
294        self.write_register(Register::RegInvertiq, (0x13 << 1) | iq1).await?;
295        self.write_register(Register::RegInvertiq2, iq2).await?;
296        Ok(())
297    }
298
299    // Calibrate the image rejection based on the given frequency
300    async fn calibrate_image(&mut self, _frequency_in_hz: u32) -> Result<(), RadioError> {
301        // An automatic process, but can set bit ImageCalStart in RegImageCal, when the device is in Standby mode.
302        Ok(())
303    }
304
305    async fn set_channel(&mut self, frequency_in_hz: u32) -> Result<(), RadioError> {
306        debug!("channel = {}", frequency_in_hz);
307        let frf = (frequency_in_hz as f64 / FREQUENCY_SYNTHESIZER_STEP) as u32;
308        self.write_register(Register::RegFrfMsb, ((frf & 0x00FF0000) >> 16) as u8)
309            .await?;
310        self.write_register(Register::RegFrfMid, ((frf & 0x0000FF00) >> 8) as u8)
311            .await?;
312        self.write_register(Register::RegFrfLsb, (frf & 0x000000FF) as u8).await
313    }
314
315    async fn set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError> {
316        self.write_register(Register::RegFifoAddrPtr, 0x00u8).await?;
317        self.write_register(Register::RegPayloadLength, 0x00u8).await?;
318        self.write_buffer(Register::RegFifo, payload).await?;
319        self.write_register(Register::RegPayloadLength, payload.len() as u8)
320            .await
321    }
322
323    async fn do_tx(&mut self) -> Result<(), RadioError> {
324        self.intf.iv.enable_rf_switch_tx().await?;
325
326        self.write_register(Register::RegOpMode, LoRaMode::Tx.value()).await
327    }
328
329    async fn do_rx(&mut self, rx_mode: RxMode) -> Result<(), RadioError> {
330        let (num_symbols, mode) = match rx_mode {
331            RxMode::DutyCycle(_) => Err(RadioError::DutyCycleUnsupported),
332            RxMode::Single(ns) => Ok((ns.max(SX127X_MIN_LORA_SYMB_NUM_TIMEOUT), LoRaMode::RxSingle)),
333            RxMode::Continuous => Ok((0, LoRaMode::RxContinuous)),
334        }?;
335
336        self.intf.iv.enable_rf_switch_rx().await?;
337
338        self.set_lora_symbol_num_timeout(num_symbols).await?;
339
340        let lna_gain = if self.config.rx_boost {
341            LnaGain::G1.boosted_value()
342        } else {
343            LnaGain::G1.value()
344        };
345        self.write_register(Register::RegLna, lna_gain).await?;
346
347        self.write_register(Register::RegFifoAddrPtr, 0x00u8).await?;
348        self.write_register(Register::RegPayloadLength, 0xffu8).await?;
349
350        self.write_register(Register::RegOpMode, mode.value()).await
351    }
352
353    async fn get_rx_payload(
354        &mut self,
355        _rx_pkt_params: &PacketParams,
356        receiving_buffer: &mut [u8],
357    ) -> Result<u8, RadioError> {
358        let payload_length = self.read_register(Register::RegRxNbBytes).await?;
359        if (payload_length as usize) > receiving_buffer.len() {
360            return Err(RadioError::PayloadSizeMismatch(
361                payload_length as usize,
362                receiving_buffer.len(),
363            ));
364        }
365        let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr).await?;
366        self.write_register(Register::RegFifoAddrPtr, fifo_addr).await?;
367        self.read_buffer(Register::RegFifo, &mut receiving_buffer[0..payload_length as usize])
368            .await?;
369        self.write_register(Register::RegFifoAddrPtr, 0x00u8).await?;
370
371        Ok(payload_length)
372    }
373
374    async fn get_rx_packet_status(&mut self) -> Result<PacketStatus, RadioError> {
375        let snr = {
376            let packet_snr = self.read_register(Register::RegPktSnrValue).await?;
377            packet_snr as i8 as i16 / 4
378        };
379
380        let rssi = {
381            let packet_rssi = self.read_register(Register::RegPktRssiValue).await?;
382
383            let rssi_offset = C::rssi_offset(self).await?;
384
385            if snr >= 0 {
386                rssi_offset + (packet_rssi as f32 * 16.0 / 15.0) as i16
387            } else {
388                rssi_offset + (packet_rssi as i16) + snr
389            }
390        };
391
392        Ok(PacketStatus { rssi, snr })
393    }
394
395    async fn do_cad(&mut self, _mdltn_params: &ModulationParams) -> Result<(), RadioError> {
396        self.intf.iv.enable_rf_switch_rx().await?;
397
398        let mut lna_gain_final = LnaGain::G1.value();
399        if self.config.rx_boost {
400            lna_gain_final = LnaGain::G1.boosted_value();
401        }
402        self.write_register(Register::RegLna, lna_gain_final).await?;
403
404        self.write_register(Register::RegOpMode, LoRaMode::Cad.value()).await
405    }
406
407    // Set the IRQ mask to disable unwanted interrupts,
408    // enable interrupts on DIO pins (sx127x has multiple),
409    // and allow interrupts.
410    async fn set_irq_params(&mut self, radio_mode: Option<RadioMode>) -> Result<(), RadioError> {
411        match radio_mode {
412            Some(RadioMode::Transmit) => {
413                self.write_register(
414                    Register::RegIrqFlagsMask,
415                    IrqMask::All.value() ^ IrqMask::TxDone.value(),
416                )
417                .await?;
418
419                let mut dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
420                dio_mapping_1 = (dio_mapping_1 & DioMapping1Dio0::Mask.value()) | DioMapping1Dio0::TxDone.value();
421                self.write_register(Register::RegDioMapping1, dio_mapping_1).await?;
422
423                self.write_register(Register::RegIrqFlags, 0x00u8).await?;
424            }
425            Some(RadioMode::Receive(_)) => {
426                self.write_register(
427                    Register::RegIrqFlagsMask,
428                    IrqMask::All.value()
429                        ^ (IrqMask::RxDone.value()
430                            | IrqMask::RxTimeout.value()
431                            | IrqMask::CRCError.value()
432                            | IrqMask::HeaderValid.value()),
433                )
434                .await?;
435
436                // HeaderValid and CRCError are mutually exclusive when attempting to
437                // trigger DIO-based interrupt, so our approach is to trigger HeaderValid
438                // as this is required for preamble detection.
439                // TODO: RxTimeout should be configured on DIO1
440                let dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
441                let val = (dio_mapping_1 & DioMapping1Dio0::Mask.value() & DioMapping1Dio3::Mask.value())
442                    | (DioMapping1Dio0::RxDone.value() | DioMapping1Dio3::ValidHeader.value());
443                self.write_register(Register::RegDioMapping1, val).await?;
444
445                self.write_register(Register::RegIrqFlags, 0x00u8).await?;
446            }
447            Some(RadioMode::ChannelActivityDetection) => {
448                self.write_register(
449                    Register::RegIrqFlagsMask,
450                    IrqMask::All.value() ^ (IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value()),
451                )
452                .await?;
453
454                let mut dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
455                dio_mapping_1 = (dio_mapping_1 & DioMapping1Dio0::Mask.value()) | DioMapping1Dio0::CadDone.value();
456                self.write_register(Register::RegDioMapping1, dio_mapping_1).await?;
457
458                self.write_register(Register::RegIrqFlags, 0x00u8).await?;
459            }
460            _ => {
461                self.write_register(Register::RegIrqFlagsMask, IrqMask::All.value())
462                    .await?;
463
464                let mut dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
465                dio_mapping_1 = (dio_mapping_1 & DioMapping1Dio0::Mask.value()) | DioMapping1Dio0::Other.value();
466                self.write_register(Register::RegDioMapping1, dio_mapping_1).await?;
467                self.write_register(Register::RegIrqFlags, 0xffu8).await?;
468            }
469        }
470
471        Ok(())
472    }
473
474    async fn await_irq(&mut self) -> Result<(), RadioError> {
475        self.intf.iv.await_irq().await
476    }
477
478    /// Process the radio IRQ. Log unexpected interrupts. Packets from other
479    /// devices can cause unexpected interrupts.
480    ///
481    /// NB! Do not await this future in a select branch as interrupting it
482    /// mid-flow could cause radio lock up.
483    async fn process_irq_event(
484        &mut self,
485        radio_mode: RadioMode,
486        cad_activity_detected: Option<&mut bool>,
487        clear_interrupts: bool,
488    ) -> Result<Option<IrqState>, RadioError> {
489        let irq_flags = self.read_register(Register::RegIrqFlags).await?;
490        if clear_interrupts {
491            self.write_register(Register::RegIrqFlags, 0xffu8).await?; // clear all interrupts
492        }
493
494        match radio_mode {
495            RadioMode::Transmit => {
496                if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
497                    debug!("TxDone in radio mode {}", radio_mode);
498                    return Ok(Some(IrqState::Done));
499                }
500            }
501            RadioMode::Receive(RxMode::Continuous) | RadioMode::Receive(RxMode::Single(_)) => {
502                if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
503                    debug!("RxDone in radio mode {}", radio_mode);
504                    return Ok(Some(IrqState::Done));
505                }
506                if (irq_flags & IrqMask::RxTimeout.value()) == IrqMask::RxTimeout.value() {
507                    debug!("RxTimeout in radio mode {}", radio_mode);
508                    return Err(RadioError::ReceiveTimeout);
509                }
510                if IrqMask::HeaderValid.is_set_in(irq_flags) {
511                    debug!("HeaderValid in radio mode {}", radio_mode);
512                    return Ok(Some(IrqState::PreambleReceived));
513                }
514            }
515            RadioMode::ChannelActivityDetection => {
516                if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
517                    debug!("CADDone in radio mode {}", radio_mode);
518                    // TODO: don't like how we mutate the cad_activity_detected parameter
519                    if cad_activity_detected.is_some() {
520                        // Check if the CAD (Channel Activity Detection) Activity Detected flag is set in irq_flags and then update the reference
521                        *(cad_activity_detected.unwrap()) =
522                            (irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
523                    }
524                    return Ok(Some(IrqState::Done));
525                }
526            }
527            RadioMode::Sleep | RadioMode::Standby => {
528                defmt::warn!("IRQ during sleep/standby?");
529            }
530            RadioMode::FrequencySynthesis => todo!(),
531            RadioMode::Receive(RxMode::DutyCycle(_)) => todo!(),
532        }
533
534        // If no specific IRQ condition is met, return None
535        Ok(None)
536    }
537    /// Set the LoRa chip into the TxContinuousWave mode
538    async fn set_tx_continuous_wave_mode(&mut self) -> Result<(), RadioError> {
539        C::set_tx_continuous_wave_mode(self).await
540    }
541}