Skip to main content

pamoja_radios/sx126x/
driver.rs

1//! The SX126x driven over embedded-hal: reset, configure, transmit, and receive.
2//!
3//! [`Sx126x`] owns the SPI device, the BUSY and NRESET lines, and a delay. It waits for
4//! BUSY to fall before each command, as section 8.3.1 requires, reads the IRQ register to
5//! learn when a frame has gone out or come in, and issues the commands in the order
6//! sections 14.2 and 14.3 give, with the workarounds of chapter 15 applied.
7
8use embedded_hal::delay::DelayNs;
9use embedded_hal::digital::{self, InputPin, OutputPin};
10use embedded_hal::spi::{Operation, SpiDevice};
11use pamoja_lora::budget::Decibels;
12use pamoja_lora::LinkSettings;
13
14use super::command::{self, Command, Query, CALIBRATE_ALL};
15use super::config::{
16    self, frequency_word, image_calibration, iq_polarity, llcc68_supports, register, timeout_steps,
17    tx_clamp, tx_modulation, LoraModulation, LoraPacket, PacketType, PowerAmplifier, RampTime,
18    RegulatorMode, StandbyMode, SyncWord, TcxoVoltage, TxPower,
19};
20use super::irq::Irq;
21use super::status::{rssi_inst_dbm, ChipMode, DeviceErrors, PacketStatus, RxBufferStatus, Status};
22
23/// How long NRESET is held low, in microseconds. Section 8.1 needs typically 100 us; this is
24/// the 20 ms Semtech's LoRaMac-node reference holds it for.
25pub const RESET_HOLD_US: u32 = 20_000;
26
27/// How long the driver waits after releasing NRESET before it watches BUSY, in microseconds,
28/// the 10 ms LoRaMac-node waits.
29pub const RESET_SETTLE_US: u32 = 10_000;
30
31/// How often BUSY is sampled while the chip is busy, in microseconds.
32pub const BUSY_POLL_US: u32 = 100;
33
34/// The longest BUSY may stay high before the driver gives up, in microseconds, on top of a
35/// TCXO's settling time. The slowest transitions the datasheet gives, a cold start from
36/// sleep (Table 8-2) and a full calibration (section 13.1.12), each take 3.5 ms.
37pub const BUSY_LIMIT_US: u32 = 100_000;
38
39/// How often the IRQ register is read while a transmission or a reception runs, in
40/// microseconds.
41pub const IRQ_POLL_US: u32 = 1_000;
42
43/// How much longer than a frame's airtime the chip's timeout, and then the driver's own
44/// wait, may run before the frame is given up on, in microseconds.
45pub const TIMEOUT_MARGIN_US: u64 = 1_000_000;
46
47/// The pause between NSS falling and the first clock edge when waking the chip from sleep,
48/// in nanoseconds: t10 of Table 8-1, 100 us.
49pub const WAKE_SETUP_NS: u32 = 100_000;
50
51/// How long the driver leaves the chip alone after SetSleep, in microseconds: twice the
52/// "around 500 us" section 13.1.1 cautions it is unresponsive for.
53pub const SLEEP_ENTRY_US: u32 = 1_000;
54
55/// How long a TCXO is given to settle when a board description does not say, in
56/// microseconds: the `BOARD_TCXO_WAKEUP_TIME` of 5 ms that Semtech's LoRaMac-node board
57/// files give their TCXO radios.
58pub const DEFAULT_TCXO_SETTLE_US: u32 = 5_000;
59
60/// How a module wires its SX126x: the amplifier, the clock, the antenna switch, and the
61/// regulator.
62///
63/// The SPI interface cannot tell an SX1261 from an SX1262 or an LLCC68, and it cannot see
64/// the parts around the chip, so these come from the module's schematic.
65///
66/// # Examples
67///
68/// ```
69/// use pamoja_radios::sx126x::config::{PowerAmplifier, RegulatorMode, TcxoVoltage};
70/// use pamoja_radios::sx126x::Board;
71///
72/// // An SX1262 clocked by a 1.8 V TCXO that settles in 5 ms, with DIO2 on the antenna switch.
73/// let board = Board::new(PowerAmplifier::HighPower)
74///     .with_tcxo(TcxoVoltage::V1_8, 5_000)
75///     .with_dio2_rf_switch()
76///     .with_dc_dc();
77/// assert_eq!(board.regulator, RegulatorMode::DcDc);
78/// ```
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub struct Board {
81    /// The power amplifier: [`PowerAmplifier::LowPower`] for the SX1261 and
82    /// [`PowerAmplifier::HighPower`] for the SX1262 and the LLCC68.
83    pub amplifier: PowerAmplifier,
84    /// The voltage DIO3 supplies a TCXO with, and how long the TCXO takes to settle in
85    /// microseconds, for a module clocked by a TCXO rather than a crystal.
86    pub tcxo: Option<(TcxoVoltage, u32)>,
87    /// Whether DIO2 drives the antenna switch, high while transmitting (section 13.3.5).
88    pub dio2_rf_switch: bool,
89    /// The regulator: the DC-DC converter where the module fits its inductor, else the LDO.
90    pub regulator: RegulatorMode,
91    /// Whether the chip is an LLCC68, which [`Sx126x::configure`] holds to the spreading
92    /// factors and bandwidths [`llcc68_supports`] allows.
93    pub llcc68: bool,
94}
95
96impl Board {
97    /// A module with a crystal, no switch on DIO2, and the LDO, which are the chip's defaults.
98    ///
99    /// # Arguments
100    ///
101    /// * `amplifier` - the chip's power amplifier.
102    ///
103    /// # Returns
104    ///
105    /// The board.
106    pub const fn new(amplifier: PowerAmplifier) -> Board {
107        Board {
108            amplifier,
109            tcxo: None,
110            dio2_rf_switch: false,
111            regulator: RegulatorMode::Ldo,
112            llcc68: false,
113        }
114    }
115
116    /// Returns the board clocked by a TCXO that DIO3 powers.
117    ///
118    /// # Arguments
119    ///
120    /// * `voltage` - the TCXO supply voltage.
121    /// * `settle_us` - how long the TCXO takes to settle, in microseconds.
122    ///
123    /// # Returns
124    ///
125    /// The board.
126    pub const fn with_tcxo(mut self, voltage: TcxoVoltage, settle_us: u32) -> Board {
127        self.tcxo = Some((voltage, settle_us));
128        self
129    }
130
131    /// Returns the board with DIO2 driving the antenna switch.
132    ///
133    /// # Returns
134    ///
135    /// The board.
136    pub const fn with_dio2_rf_switch(mut self) -> Board {
137        self.dio2_rf_switch = true;
138        self
139    }
140
141    /// Returns the board running on the DC-DC converter.
142    ///
143    /// # Returns
144    ///
145    /// The board.
146    pub const fn with_dc_dc(mut self) -> Board {
147        self.regulator = RegulatorMode::DcDc;
148        self
149    }
150
151    /// Returns the board with an LLCC68, whose high power amplifier it must also name.
152    ///
153    /// # Returns
154    ///
155    /// The board.
156    pub const fn with_llcc68(mut self) -> Board {
157        self.llcc68 = true;
158        self
159    }
160}
161
162/// What a radio sends and listens with: the carrier, the LoRa link, the power, and the
163/// sync word and IQ polarity that keep one network apart from another.
164///
165/// # Examples
166///
167/// ```
168/// use pamoja_lora::LinkSettings;
169/// use pamoja_radios::sx126x::config::{PowerAmplifier, SyncWord, TxPower};
170/// use pamoja_radios::sx126x::RadioConfig;
171///
172/// // A LoRaWAN device on 868.1 MHz at SF9, calibrated for the whole 863 to 870 MHz band.
173/// let config = RadioConfig::new(
174///     868_100_000,
175///     LinkSettings::new(9, 125_000),
176///     TxPower::for_output(PowerAmplifier::HighPower, 14),
177/// )
178/// .with_band(863_000_000, 870_000_000)
179/// .lorawan_device();
180/// assert_eq!(config.sync_word, SyncWord::Public);
181/// assert!(config.invert_iq_receive && !config.invert_iq_transmit);
182/// ```
183#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184pub struct RadioConfig {
185    /// The carrier frequency in hertz.
186    pub frequency_hz: u32,
187    /// The band image calibration covers, as its lower and upper edges in hertz.
188    pub band_hz: (u32, u32),
189    /// The spreading factor, bandwidth, coding rate, preamble, header, and CRC.
190    pub link: LinkSettings,
191    /// The amplifier configuration and power setting.
192    pub power: TxPower,
193    /// How fast the amplifier ramps up.
194    pub ramp: RampTime,
195    /// The sync word written to the registers at 0x0740.
196    pub sync_word: SyncWord,
197    /// Whether frames go out with inverted IQ, as a LoRaWAN gateway sends downlinks.
198    pub invert_iq_transmit: bool,
199    /// Whether the receiver expects inverted IQ, as a LoRaWAN device hears downlinks.
200    pub invert_iq_receive: bool,
201}
202
203impl RadioConfig {
204    /// Builds a configuration with a private sync word and standard IQ both ways.
205    ///
206    /// Image calibration covers just the carrier, and the amplifier ramps in 40 us, the
207    /// time Semtech's LoRaMac-node reference passes to SetTxParams.
208    ///
209    /// # Arguments
210    ///
211    /// * `frequency_hz` - the carrier frequency in hertz.
212    /// * `link` - the LoRa link settings.
213    /// * `power` - the amplifier configuration and power setting.
214    ///
215    /// # Returns
216    ///
217    /// The configuration.
218    pub const fn new(frequency_hz: u32, link: LinkSettings, power: TxPower) -> RadioConfig {
219        RadioConfig {
220            frequency_hz,
221            band_hz: (frequency_hz, frequency_hz),
222            link,
223            power,
224            ramp: RampTime::Us40,
225            sync_word: SyncWord::Private,
226            invert_iq_transmit: false,
227            invert_iq_receive: false,
228        }
229    }
230
231    /// Returns the configuration with image calibration covering a whole band, so moving
232    /// between channels inside it needs no new calibration.
233    ///
234    /// # Arguments
235    ///
236    /// * `low_hz` - the lower edge of the band in hertz.
237    /// * `high_hz` - the upper edge of the band in hertz.
238    ///
239    /// # Returns
240    ///
241    /// The configuration.
242    pub const fn with_band(mut self, low_hz: u32, high_hz: u32) -> RadioConfig {
243        self.band_hz = (low_hz, high_hz);
244        self
245    }
246
247    /// Returns the configuration with another sync word.
248    ///
249    /// # Arguments
250    ///
251    /// * `sync_word` - the sync word.
252    ///
253    /// # Returns
254    ///
255    /// The configuration.
256    pub const fn with_sync_word(mut self, sync_word: SyncWord) -> RadioConfig {
257        self.sync_word = sync_word;
258        self
259    }
260
261    /// Returns the configuration with another amplifier ramp time.
262    ///
263    /// # Arguments
264    ///
265    /// * `ramp` - the ramp time.
266    ///
267    /// # Returns
268    ///
269    /// The configuration.
270    pub const fn with_ramp(mut self, ramp: RampTime) -> RadioConfig {
271        self.ramp = ramp;
272        self
273    }
274
275    /// Returns the configuration with the IQ polarity set for each direction.
276    ///
277    /// # Arguments
278    ///
279    /// * `transmit` - `true` to send with inverted IQ.
280    /// * `receive` - `true` to listen for inverted IQ.
281    ///
282    /// # Returns
283    ///
284    /// The configuration.
285    pub const fn with_inverted_iq(mut self, transmit: bool, receive: bool) -> RadioConfig {
286        self.invert_iq_transmit = transmit;
287        self.invert_iq_receive = receive;
288        self
289    }
290
291    /// Returns the configuration a LoRaWAN end device uses: the public sync word, uplinks
292    /// with standard IQ, and downlinks heard with inverted IQ.
293    ///
294    /// # Returns
295    ///
296    /// The configuration.
297    pub const fn lorawan_device(self) -> RadioConfig {
298        self.with_sync_word(SyncWord::Public)
299            .with_inverted_iq(false, true)
300    }
301}
302
303/// How a reception ended.
304#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
305pub enum Reception {
306    /// A frame whose header and CRC checked.
307    Frame {
308        /// The payload length; the payload is at the start of the buffer.
309        len: usize,
310        /// The signal levels the frame arrived with.
311        status: PacketStatus,
312    },
313    /// No frame arrived before the timeout.
314    Timeout,
315    /// A frame arrived whose header or CRC failed its check, and was dropped.
316    Corrupt,
317}
318
319/// What can go wrong driving an SX126x.
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub enum RadioError<E> {
322    /// The SPI device failed.
323    Spi(E),
324    /// The BUSY or NRESET line could not be read or driven.
325    Pin(digital::ErrorKind),
326    /// BUSY stayed high for longer than [`BUSY_LIMIT_US`] allows, so the chip is stuck,
327    /// unpowered, or not wired to that line.
328    Busy,
329    /// The chip answered GetStatus with a mode it cannot be in after a reset and SetStandby,
330    /// so no SX126x, or a miswired one, is on the bus.
331    Absent(Status),
332    /// The link settings use a bandwidth the SX126x does not offer, in hertz.
333    Bandwidth(u32),
334    /// The board has an LLCC68, which does not support the link's spreading factor at its
335    /// bandwidth.
336    Llcc68 {
337        /// The link's spreading factor.
338        spreading_factor: u8,
339        /// The link's bandwidth in hertz.
340        bandwidth_hz: u32,
341    },
342    /// The power settings are for the other amplifier than the board has.
343    Amplifier,
344    /// A payload longer than the 255 bytes a LoRa frame carries, with its length.
345    PayloadTooLong(usize),
346    /// An answer or a received payload of this many bytes does not fit the buffer given.
347    BufferTooSmall(usize),
348    /// A transmission or a reception was asked for before [`Sx126x::configure`].
349    NotConfigured,
350    /// The chip raised its TIMEOUT interrupt before TxDone.
351    TxTimeout,
352    /// Neither the expected interrupt nor TIMEOUT arrived in the time the driver allows.
353    NoInterrupt,
354}
355
356impl<E: core::fmt::Debug> core::fmt::Display for RadioError<E> {
357    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
358        match self {
359            RadioError::Spi(error) => write!(f, "SPI error: {error:?}"),
360            RadioError::Pin(kind) => write!(f, "BUSY or NRESET line error: {kind:?}"),
361            RadioError::Busy => f.write_str("the radio held BUSY high past the time allowed"),
362            RadioError::Absent(status) => write!(f, "no SX126x answered, status {status:?}"),
363            RadioError::Bandwidth(hz) => write!(f, "the SX126x has no {hz} Hz LoRa bandwidth"),
364            RadioError::Llcc68 {
365                spreading_factor,
366                bandwidth_hz,
367            } => write!(
368                f,
369                "the LLCC68 does not support SF{spreading_factor} at {bandwidth_hz} Hz"
370            ),
371            RadioError::Amplifier => {
372                f.write_str("the power settings are for the other power amplifier")
373            }
374            RadioError::PayloadTooLong(len) => write!(
375                f,
376                "a {len} byte payload is longer than the 255 bytes a LoRa frame carries"
377            ),
378            RadioError::BufferTooSmall(len) => write!(f, "{len} bytes do not fit the buffer"),
379            RadioError::NotConfigured => f.write_str("the radio has not been configured"),
380            RadioError::TxTimeout => f.write_str("the transmission timed out before TxDone"),
381            RadioError::NoInterrupt => {
382                f.write_str("the radio raised no interrupt in the time allowed")
383            }
384        }
385    }
386}
387
388impl<E: core::fmt::Debug> core::error::Error for RadioError<E> {}
389
390fn pin<E, P: digital::Error>(error: P) -> RadioError<E> {
391    RadioError::Pin(error.kind())
392}
393
394/// A Semtech SX1261, SX1262, or LLCC68 on an SPI bus, with its BUSY and NRESET lines.
395///
396/// [`init`](Sx126x::init) resets the chip and sets up what the [`Board`] wires around it,
397/// [`configure`](Sx126x::configure) tunes it to a [`RadioConfig`], and
398/// [`transmit`](Sx126x::transmit) and [`receive`](Sx126x::receive) send and wait for one
399/// frame each. For anything those do not cover, [`command`](Sx126x::command),
400/// [`query`](Sx126x::query), and the register and buffer methods reach the chip directly,
401/// with the BUSY handshake still done for them.
402///
403/// # Examples
404///
405/// The chip's side of initialization, scripted: an SX1262 module with a crystal, which
406/// answers GetStatus in STDBY_RC.
407///
408/// ```
409/// use pamoja_hal::digital::{OutputPin, PinState};
410/// use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
411/// use pamoja_radios::sx126x::config::PowerAmplifier;
412/// use pamoja_radios::sx126x::{Board, Sx126x};
413///
414/// let spi = SpiScript::new([
415///     SpiStep::write([0x80, 0x00]),
416///     SpiStep::write([0xC0]),
417///     SpiStep::read([0x22]),
418///     SpiStep::write([0x96, 0x00]),
419///     SpiStep::write([0x8A, 0x01]),
420///     SpiStep::write([0x1D, 0x08, 0xD8, 0x00]),
421///     SpiStep::read([0x08]),
422///     SpiStep::write([0x0D, 0x08, 0xD8]),
423///     SpiStep::write([0x1E]),
424///     SpiStep::write([0x8F, 0x00, 0x00]),
425/// ]);
426/// let mut busy = PinScript::new([]);
427/// busy.set_low()?;
428/// let board = Board::new(PowerAmplifier::HighPower);
429///
430/// let mut radio = Sx126x::new(spi, busy, PinScript::new([]), DelayLog::new(), board);
431/// radio.init().expect("the scripted SX1262 answers");
432///
433/// let (spi, _, reset, _) = radio.release();
434/// assert!(spi.done());
435/// assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
436/// # Ok::<(), core::convert::Infallible>(())
437/// ```
438pub struct Sx126x<SPI, BUSY, RESET, D> {
439    spi: SPI,
440    busy: BUSY,
441    reset: RESET,
442    delay: D,
443    board: Board,
444    config: Option<RadioConfig>,
445    image: Option<[u8; 2]>,
446    asleep: bool,
447}
448
449impl<SPI, BUSY, RESET, D> Sx126x<SPI, BUSY, RESET, D> {
450    /// Wraps a chip's SPI device and lines. Nothing is sent until [`init`](Sx126x::init).
451    ///
452    /// # Arguments
453    ///
454    /// * `spi` - the SPI device, with NSS as its chip select.
455    /// * `busy` - the BUSY line, as an input.
456    /// * `reset` - the NRESET line, as an output.
457    /// * `delay` - a delay for the reset pulse and the polling.
458    /// * `board` - how the module wires the chip.
459    ///
460    /// # Returns
461    ///
462    /// The driver.
463    pub fn new(spi: SPI, busy: BUSY, reset: RESET, delay: D, board: Board) -> Self {
464        Sx126x {
465            spi,
466            busy,
467            reset,
468            delay,
469            board,
470            config: None,
471            image: None,
472            asleep: false,
473        }
474    }
475
476    /// Returns how the module wires the chip.
477    ///
478    /// # Returns
479    ///
480    /// The board.
481    pub fn board(&self) -> Board {
482        self.board
483    }
484
485    /// Returns the configuration the chip was last tuned to.
486    ///
487    /// # Returns
488    ///
489    /// The configuration, or `None` before [`configure`](Sx126x::configure) or after a
490    /// reset or sleep.
491    pub fn config(&self) -> Option<&RadioConfig> {
492        self.config.as_ref()
493    }
494
495    /// Returns the power settings for an output power on this board's amplifier.
496    ///
497    /// # Arguments
498    ///
499    /// * `output_dbm` - the output power wanted at the antenna port.
500    ///
501    /// # Returns
502    ///
503    /// The amplifier configuration and power setting.
504    pub fn tx_power(&self, output_dbm: i8) -> TxPower {
505        TxPower::for_output(self.board.amplifier, output_dbm)
506    }
507
508    /// Gives back the SPI device, the lines, and the delay.
509    ///
510    /// # Returns
511    ///
512    /// The SPI device, BUSY, NRESET, and the delay.
513    pub fn release(self) -> (SPI, BUSY, RESET, D) {
514        (self.spi, self.busy, self.reset, self.delay)
515    }
516}
517
518impl<SPI, BUSY, RESET, D> Sx126x<SPI, BUSY, RESET, D>
519where
520    SPI: SpiDevice,
521    BUSY: InputPin,
522    RESET: OutputPin,
523    D: DelayNs,
524{
525    /// Resets the chip and sets up the parts the board wires around it.
526    ///
527    /// NRESET is pulsed low, and the chip, which calibrates itself on the way out of reset,
528    /// is put in STDBY_RC and asked for its status. A TCXO is then powered from DIO3 and
529    /// every block calibrated again, since section 9.2.1 says the calibration at power up
530    /// fails on a TCXO, and the XOSC start error that section 13.3.6 expects is cleared.
531    /// The regulator and the DIO2 antenna switch follow, then the LoRa packet type, the
532    /// antenna mismatch workaround of section 15.2 for the high power amplifier, and the
533    /// data buffer base addresses.
534    ///
535    /// # Errors
536    ///
537    /// Returns [`RadioError::Absent`] if the status is not STDBY_RC, [`RadioError::Busy`] if
538    /// BUSY never falls, and [`RadioError::Spi`] or [`RadioError::Pin`] if the bus or a line
539    /// fails.
540    pub fn init(&mut self) -> Result<(), RadioError<SPI::Error>> {
541        self.config = None;
542        self.image = None;
543        self.asleep = false;
544        self.reset.set_low().map_err(pin)?;
545        self.delay.delay_us(RESET_HOLD_US);
546        self.reset.set_high().map_err(pin)?;
547        self.delay.delay_us(RESET_SETTLE_US);
548
549        self.command(command::set_standby(StandbyMode::Rc))?;
550        let status = self.status()?;
551        if !matches!(status.chip_mode, ChipMode::StandbyRc) {
552            return Err(RadioError::Absent(status));
553        }
554        if let Some((voltage, settle_us)) = self.board.tcxo {
555            let settle = timeout_steps(u64::from(settle_us));
556            self.command(command::set_dio3_as_tcxo(voltage, settle))?;
557            self.command(command::calibrate(CALIBRATE_ALL))?;
558            self.command(command::clear_device_errors())?;
559        }
560        self.command(command::set_regulator_mode(self.board.regulator))?;
561        if self.board.dio2_rf_switch {
562            self.command(command::set_dio2_as_rf_switch(true))?;
563        }
564        self.command(command::set_packet_type(PacketType::Lora))?;
565        if self.board.amplifier == PowerAmplifier::HighPower {
566            self.update_register(register::TX_CLAMP_CONFIG, tx_clamp)?;
567        }
568        self.command(command::set_buffer_base_address(0, 0))
569    }
570
571    /// Tunes the chip to a configuration.
572    ///
573    /// The chip goes to STDBY_RC and takes the LoRa packet type, a new image calibration if
574    /// the band moved, the frequency, the amplifier configuration and power, the modulation,
575    /// and the sync word. The packet parameters, which carry the payload length, are sent
576    /// with each transmission and reception, after the modulation as section 14.5 requires.
577    ///
578    /// # Arguments
579    ///
580    /// * `config` - the configuration.
581    ///
582    /// # Errors
583    ///
584    /// Returns [`RadioError::Bandwidth`] if the link's bandwidth is not one the SX126x has,
585    /// [`RadioError::Llcc68`] if the board has an LLCC68 that does not support the link,
586    /// [`RadioError::Amplifier`] if the power settings are for the other amplifier, and the
587    /// bus errors of [`command`](Sx126x::command).
588    pub fn configure(&mut self, config: RadioConfig) -> Result<(), RadioError<SPI::Error>> {
589        let modulation = LoraModulation::from_link(&config.link)
590            .ok_or(RadioError::Bandwidth(config.link.bandwidth_hz()))?;
591        if self.board.llcc68 && !llcc68_supports(modulation.spreading_factor, modulation.bandwidth)
592        {
593            return Err(RadioError::Llcc68 {
594                spreading_factor: modulation.spreading_factor,
595                bandwidth_hz: config.link.bandwidth_hz(),
596            });
597        }
598        let low_power = self.board.amplifier == PowerAmplifier::LowPower;
599        if (config.power.pa.device == 1) != low_power {
600            return Err(RadioError::Amplifier);
601        }
602
603        self.command(command::set_standby(StandbyMode::Rc))?;
604        self.command(command::set_packet_type(PacketType::Lora))?;
605        let (low_hz, high_hz) = config.band_hz;
606        let image = image_calibration(
607            low_hz.min(config.frequency_hz),
608            high_hz.max(config.frequency_hz),
609        );
610        if self.image != Some(image) {
611            self.command(command::calibrate_image(image))?;
612            self.image = Some(image);
613        }
614        self.command(command::set_rf_frequency(frequency_word(
615            config.frequency_hz,
616        )))?;
617        self.command(command::set_pa_config(config.power.pa))?;
618        self.command(command::set_tx_params(
619            config.power.setting_dbm,
620            config.ramp,
621        ))?;
622        self.command(command::set_lora_modulation_params(modulation))?;
623        self.write_register(register::LORA_SYNC_WORD, &config.sync_word.to_bytes())?;
624        self.config = Some(config);
625        Ok(())
626    }
627
628    /// Sends one frame and waits for it to leave.
629    ///
630    /// This is [`start_transmit`](Sx126x::start_transmit), a wait for the frame's airtime,
631    /// and [`finish_transmit`](Sx126x::finish_transmit) read every [`IRQ_POLL_US`] until the
632    /// chip reports the frame sent or timed out.
633    ///
634    /// # Arguments
635    ///
636    /// * `payload` - the frame's payload, at most 255 bytes.
637    ///
638    /// # Returns
639    ///
640    /// The frame's airtime in microseconds, for a [`DutyCycle`](crate::duty::DutyCycle) to
641    /// count.
642    ///
643    /// # Errors
644    ///
645    /// Returns the errors of [`start_transmit`](Sx126x::start_transmit) and
646    /// [`finish_transmit`](Sx126x::finish_transmit), and [`RadioError::NoInterrupt`] if the
647    /// chip reports neither outcome within its own timeout and [`TIMEOUT_MARGIN_US`] more.
648    pub fn transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
649        let airtime_us = self.start_transmit(payload)?;
650        let limit_us = airtime_us.saturating_add(2 * TIMEOUT_MARGIN_US);
651        let mut waited_us = airtime_us;
652        self.pause_us(airtime_us);
653        while !self.finish_transmit()? {
654            if waited_us >= limit_us {
655                return Err(RadioError::NoInterrupt);
656            }
657            self.delay.delay_us(IRQ_POLL_US);
658            waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
659        }
660        Ok(airtime_us)
661    }
662
663    /// Starts sending one frame and returns once the chip is transmitting.
664    ///
665    /// The steps are those of section 14.2 after configuration: the payload into the data
666    /// buffer, the packet parameters, the inverted IQ workaround of section 15.4, TxDone and
667    /// TIMEOUT routed to DIO1, the 500 kHz workaround of section 15.1, and SetTx with a
668    /// timeout of the frame's airtime and [`TIMEOUT_MARGIN_US`]. A caller with its own
669    /// scheduler waits out the airtime and then calls
670    /// [`finish_transmit`](Sx126x::finish_transmit), or watches DIO1.
671    ///
672    /// # Arguments
673    ///
674    /// * `payload` - the frame's payload, at most 255 bytes.
675    ///
676    /// # Returns
677    ///
678    /// The frame's airtime in microseconds.
679    ///
680    /// # Errors
681    ///
682    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure),
683    /// [`RadioError::PayloadTooLong`] past 255 bytes, and the bus errors of
684    /// [`command`](Sx126x::command).
685    pub fn start_transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
686        let settings = self.config.ok_or(RadioError::NotConfigured)?;
687        let len =
688            u8::try_from(payload.len()).map_err(|_| RadioError::PayloadTooLong(payload.len()))?;
689        let bandwidth = LoraModulation::from_link(&settings.link)
690            .ok_or(RadioError::Bandwidth(settings.link.bandwidth_hz()))?
691            .bandwidth;
692
693        self.command(command::set_standby(StandbyMode::Rc))?;
694        if !payload.is_empty() {
695            self.write_buffer(0, payload)?;
696        }
697        let invert = settings.invert_iq_transmit;
698        let packet = LoraPacket::from_link(&settings.link, len, invert);
699        self.command(command::set_lora_packet_params(packet))?;
700        self.update_register(register::IQ_POLARITY, |value| iq_polarity(value, invert))?;
701        let events = Irq::TX_DONE | Irq::TIMEOUT;
702        self.command(command::set_dio_irq_params(
703            events,
704            events,
705            Irq::NONE,
706            Irq::NONE,
707        ))?;
708        self.update_register(register::TX_MODULATION, |value| {
709            tx_modulation(value, bandwidth)
710        })?;
711        self.command(command::clear_irq_status(Irq::ALL))?;
712
713        let airtime_us = settings.link.airtime_us(payload.len());
714        let timeout_us = airtime_us.saturating_add(TIMEOUT_MARGIN_US);
715        self.command(command::set_tx(timeout_steps(timeout_us)))?;
716        Ok(airtime_us)
717    }
718
719    /// Reports whether the frame [`start_transmit`](Sx126x::start_transmit) began has left.
720    ///
721    /// The IRQ register is read once, and on TxDone or TIMEOUT the interrupts are cleared.
722    ///
723    /// # Returns
724    ///
725    /// `true` once the frame has been sent, `false` while it is still going out.
726    ///
727    /// # Errors
728    ///
729    /// Returns [`RadioError::TxTimeout`] if the chip timed out, and the bus errors of
730    /// [`command`](Sx126x::command).
731    pub fn finish_transmit(&mut self) -> Result<bool, RadioError<SPI::Error>> {
732        let irq = self.irq_status()?;
733        if !irq.intersects(Irq::TX_DONE | Irq::TIMEOUT) {
734            return Ok(false);
735        }
736        self.command(command::clear_irq_status(Irq::ALL))?;
737        if irq.contains(Irq::TX_DONE) {
738            Ok(true)
739        } else {
740            Err(RadioError::TxTimeout)
741        }
742    }
743
744    /// Listens for one frame.
745    ///
746    /// The steps are those of section 14.3 after configuration: the packet parameters with
747    /// the buffer's length as the most to accept, the inverted IQ workaround, RxDone,
748    /// TIMEOUT, CrcErr, and HeaderErr routed to DIO1, and SetRx with the timeout. Once an
749    /// interrupt arrives, the timer is stopped and its event cleared as section 15.3 advises
750    /// after any reception with a timeout, the interrupts are cleared, and a frame that
751    /// checked is copied out of the data buffer with its signal levels.
752    ///
753    /// # Arguments
754    ///
755    /// * `buffer` - where the payload goes; its length, up to 255, is the most accepted.
756    /// * `timeout_us` - how long to listen for a frame to start, in microseconds.
757    ///
758    /// # Returns
759    ///
760    /// The frame's length and levels, or that the timeout passed or the frame was corrupt.
761    ///
762    /// # Errors
763    ///
764    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure),
765    /// [`RadioError::BufferTooSmall`] if the chip reports a longer payload than the buffer
766    /// holds, [`RadioError::NoInterrupt`] if it never answers, and the bus errors of
767    /// [`command`](Sx126x::command).
768    pub fn receive(
769        &mut self,
770        buffer: &mut [u8],
771        timeout_us: u64,
772    ) -> Result<Reception, RadioError<SPI::Error>> {
773        let settings = self.config.ok_or(RadioError::NotConfigured)?;
774        let most = u8::try_from(buffer.len()).unwrap_or(u8::MAX);
775        let events = Irq::RX_DONE | Irq::TIMEOUT | Irq::CRC_ERROR | Irq::HEADER_ERROR;
776        self.prepare_reception(&settings, most, events)?;
777
778        let timeout_us = timeout_us.max(1);
779        self.command(command::set_rx(timeout_steps(timeout_us)))?;
780        let frame_us = settings.link.airtime_us(usize::from(most));
781        let limit_us = timeout_us
782            .saturating_add(frame_us)
783            .saturating_add(TIMEOUT_MARGIN_US);
784        let irq = self.wait_for(events, 0, limit_us)?;
785        self.write_register(register::RTC_CONTROL, &[config::RTC_STOP])?;
786        self.update_register(register::EVENT_MASK, config::event_clear)?;
787        self.command(command::clear_irq_status(Irq::ALL))?;
788
789        if irq.intersects(Irq::CRC_ERROR | Irq::HEADER_ERROR) {
790            return Ok(Reception::Corrupt);
791        }
792        if !irq.contains(Irq::RX_DONE) {
793            return Ok(Reception::Timeout);
794        }
795        self.read_frame(buffer)
796    }
797
798    /// Starts listening with no timeout, so the chip receives frame after frame until
799    /// another command stops it: the Rx Continuous mode of Table 13-9.
800    ///
801    /// The setup is that of [`receive`](Sx126x::receive), accepting the 255 bytes a frame
802    /// may carry, with RxDone, CrcErr, and HeaderErr routed to DIO1. Each frame is read with
803    /// [`take_frame`](Sx126x::take_frame).
804    ///
805    /// # Errors
806    ///
807    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure), and the
808    /// bus errors of [`command`](Sx126x::command).
809    pub fn listen(&mut self) -> Result<(), RadioError<SPI::Error>> {
810        let settings = self.config.ok_or(RadioError::NotConfigured)?;
811        let events = Irq::RX_DONE | Irq::CRC_ERROR | Irq::HEADER_ERROR;
812        self.prepare_reception(&settings, u8::MAX, events)?;
813        self.command(command::set_rx(config::RX_CONTINUOUS))
814    }
815
816    /// Takes the frame a [`listen`](Sx126x::listen) has received, if one has arrived.
817    ///
818    /// The IRQ register is read once. On RxDone, CrcErr, or HeaderErr those interrupts are
819    /// cleared, and a frame that checked is copied out with its signal levels while the chip
820    /// goes on listening.
821    ///
822    /// # Arguments
823    ///
824    /// * `buffer` - where the payload goes.
825    ///
826    /// # Returns
827    ///
828    /// The frame, a corrupt frame, or `None` when nothing has arrived.
829    ///
830    /// # Errors
831    ///
832    /// Returns [`RadioError::BufferTooSmall`] if the payload does not fit, and the bus errors
833    /// of [`command`](Sx126x::command).
834    pub fn take_frame(
835        &mut self,
836        buffer: &mut [u8],
837    ) -> Result<Option<Reception>, RadioError<SPI::Error>> {
838        let events = Irq::RX_DONE | Irq::CRC_ERROR | Irq::HEADER_ERROR;
839        let irq = self.irq_status()?;
840        if !irq.intersects(events) {
841            return Ok(None);
842        }
843        self.command(command::clear_irq_status(events))?;
844        if irq.intersects(Irq::CRC_ERROR | Irq::HEADER_ERROR) {
845            return Ok(Some(Reception::Corrupt));
846        }
847        self.read_frame(buffer).map(Some)
848    }
849
850    /// Puts the chip in STDBY_RC, which stops a transmission or a reception.
851    ///
852    /// # Errors
853    ///
854    /// Returns the bus errors of [`command`](Sx126x::command).
855    pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
856        self.command(command::set_standby(StandbyMode::Rc))
857    }
858
859    /// Puts the chip to sleep until the next command wakes it.
860    ///
861    /// A warm start keeps the chip's configuration in retention; a cold start loses it,
862    /// and [`init`](Sx126x::init) must run again. Either way the driver forgets the
863    /// [`RadioConfig`], so [`configure`](Sx126x::configure) runs before the next frame. The
864    /// next command wakes the chip with GetStatus, pausing [`WAKE_SETUP_NS`] after NSS
865    /// falls.
866    ///
867    /// # Arguments
868    ///
869    /// * `warm_start` - `true` to keep the configuration in retention.
870    ///
871    /// # Errors
872    ///
873    /// Returns the bus errors of [`command`](Sx126x::command).
874    pub fn sleep(&mut self, warm_start: bool) -> Result<(), RadioError<SPI::Error>> {
875        self.command(command::set_standby(StandbyMode::Rc))?;
876        self.command(command::set_sleep(warm_start, false))?;
877        self.delay.delay_us(SLEEP_ENTRY_US);
878        self.asleep = true;
879        self.config = None;
880        if !warm_start {
881            self.image = None;
882        }
883        Ok(())
884    }
885
886    /// Reads the status byte.
887    ///
888    /// # Returns
889    ///
890    /// The chip mode and how the last command went.
891    ///
892    /// # Errors
893    ///
894    /// Returns the bus errors of [`command`](Sx126x::command).
895    pub fn status(&mut self) -> Result<Status, RadioError<SPI::Error>> {
896        let mut byte = [0u8; 1];
897        self.query(command::get_status(), &mut byte)?;
898        Ok(Status::from_byte(byte[0]))
899    }
900
901    /// Reads the pending interrupts.
902    ///
903    /// # Returns
904    ///
905    /// The IRQ register.
906    ///
907    /// # Errors
908    ///
909    /// Returns the bus errors of [`command`](Sx126x::command).
910    pub fn irq_status(&mut self) -> Result<Irq, RadioError<SPI::Error>> {
911        let mut bytes = [0u8; 2];
912        self.query(command::get_irq_status(), &mut bytes)?;
913        Ok(Irq::from_bytes(bytes))
914    }
915
916    /// Reads the signal power the receiver hears right now, while it listens.
917    ///
918    /// # Returns
919    ///
920    /// The instantaneous RSSI in dBm.
921    ///
922    /// # Errors
923    ///
924    /// Returns the bus errors of [`command`](Sx126x::command).
925    pub fn instantaneous_rssi(&mut self) -> Result<Decibels, RadioError<SPI::Error>> {
926        let mut byte = [0u8; 1];
927        self.query(command::get_rssi_inst(), &mut byte)?;
928        Ok(rssi_inst_dbm(byte[0]))
929    }
930
931    /// Reads the calibration, oscillator, PLL, and amplifier errors the chip has flagged.
932    ///
933    /// # Returns
934    ///
935    /// The device errors.
936    ///
937    /// # Errors
938    ///
939    /// Returns the bus errors of [`command`](Sx126x::command).
940    pub fn device_errors(&mut self) -> Result<DeviceErrors, RadioError<SPI::Error>> {
941        let mut bytes = [0u8; 2];
942        self.query(command::get_device_errors(), &mut bytes)?;
943        Ok(DeviceErrors::from_bytes(bytes))
944    }
945
946    /// Clears the device errors.
947    ///
948    /// # Errors
949    ///
950    /// Returns the bus errors of [`command`](Sx126x::command).
951    pub fn clear_device_errors(&mut self) -> Result<(), RadioError<SPI::Error>> {
952        self.command(command::clear_device_errors())
953    }
954
955    /// Chooses between the receiver's power saving gain, the chip's default, and its boosted
956    /// gain, which buys sensitivity for current (Table 9-3).
957    ///
958    /// # Arguments
959    ///
960    /// * `boosted` - `true` for boosted gain.
961    ///
962    /// # Errors
963    ///
964    /// Returns the bus errors of [`command`](Sx126x::command).
965    pub fn set_rx_boosted(&mut self, boosted: bool) -> Result<(), RadioError<SPI::Error>> {
966        let gain = if boosted {
967            config::RX_GAIN_BOOSTED
968        } else {
969            config::RX_GAIN_POWER_SAVING
970        };
971        self.write_register(register::RX_GAIN, &[gain])
972    }
973
974    /// Sends one command once BUSY is low, waking the chip first if it sleeps.
975    ///
976    /// # Arguments
977    ///
978    /// * `command` - the command.
979    ///
980    /// # Errors
981    ///
982    /// Returns [`RadioError::Busy`] if BUSY never falls, [`RadioError::Spi`] if the SPI
983    /// device fails, and [`RadioError::Pin`] if BUSY cannot be read.
984    pub fn command(&mut self, command: Command) -> Result<(), RadioError<SPI::Error>> {
985        self.ready()?;
986        self.spi.write(command.as_bytes()).map_err(RadioError::Spi)
987    }
988
989    /// Sends a query and reads its answer in the same transaction, once BUSY is low.
990    ///
991    /// # Arguments
992    ///
993    /// * `query` - the query.
994    /// * `answer` - where the answer goes; its first `query.answer_len` bytes are filled.
995    ///
996    /// # Errors
997    ///
998    /// Returns [`RadioError::BufferTooSmall`] if `answer` is shorter than the answer, and
999    /// the errors of [`command`](Sx126x::command).
1000    pub fn query(&mut self, query: Query, answer: &mut [u8]) -> Result<(), RadioError<SPI::Error>> {
1001        let len = query.answer_len;
1002        let answer = answer
1003            .get_mut(..len)
1004            .ok_or(RadioError::BufferTooSmall(len))?;
1005        self.ready()?;
1006        self.spi
1007            .transaction(&mut [
1008                Operation::Write(query.command.as_bytes()),
1009                Operation::Read(answer),
1010            ])
1011            .map_err(RadioError::Spi)
1012    }
1013
1014    /// Writes consecutive registers.
1015    ///
1016    /// # Arguments
1017    ///
1018    /// * `address` - the first register's address.
1019    /// * `values` - the values, one per register.
1020    ///
1021    /// # Errors
1022    ///
1023    /// Returns the errors of [`command`](Sx126x::command).
1024    pub fn write_register(
1025        &mut self,
1026        address: u16,
1027        values: &[u8],
1028    ) -> Result<(), RadioError<SPI::Error>> {
1029        let header = command::write_register(address);
1030        self.ready()?;
1031        self.spi
1032            .transaction(&mut [
1033                Operation::Write(header.as_bytes()),
1034                Operation::Write(values),
1035            ])
1036            .map_err(RadioError::Spi)
1037    }
1038
1039    /// Reads consecutive registers.
1040    ///
1041    /// # Arguments
1042    ///
1043    /// * `address` - the first register's address.
1044    /// * `values` - where the values go, one per register.
1045    ///
1046    /// # Errors
1047    ///
1048    /// Returns the errors of [`command`](Sx126x::command).
1049    pub fn read_register(
1050        &mut self,
1051        address: u16,
1052        values: &mut [u8],
1053    ) -> Result<(), RadioError<SPI::Error>> {
1054        self.query(command::read_register(address, values.len()), values)
1055    }
1056
1057    /// Writes bytes into the data buffer.
1058    ///
1059    /// # Arguments
1060    ///
1061    /// * `offset` - where in the buffer the first byte goes.
1062    /// * `bytes` - the bytes.
1063    ///
1064    /// # Errors
1065    ///
1066    /// Returns the errors of [`command`](Sx126x::command).
1067    pub fn write_buffer(&mut self, offset: u8, bytes: &[u8]) -> Result<(), RadioError<SPI::Error>> {
1068        let header = command::write_buffer(offset);
1069        self.ready()?;
1070        self.spi
1071            .transaction(&mut [Operation::Write(header.as_bytes()), Operation::Write(bytes)])
1072            .map_err(RadioError::Spi)
1073    }
1074
1075    /// Reads bytes out of the data buffer.
1076    ///
1077    /// # Arguments
1078    ///
1079    /// * `offset` - where in the buffer the first byte is.
1080    /// * `bytes` - where the bytes go.
1081    ///
1082    /// # Errors
1083    ///
1084    /// Returns the errors of [`command`](Sx126x::command).
1085    pub fn read_buffer(
1086        &mut self,
1087        offset: u8,
1088        bytes: &mut [u8],
1089    ) -> Result<(), RadioError<SPI::Error>> {
1090        self.query(command::read_buffer(offset, bytes.len()), bytes)
1091    }
1092
1093    fn prepare_reception(
1094        &mut self,
1095        settings: &RadioConfig,
1096        most: u8,
1097        events: Irq,
1098    ) -> Result<(), RadioError<SPI::Error>> {
1099        self.command(command::set_standby(StandbyMode::Rc))?;
1100        let invert = settings.invert_iq_receive;
1101        let packet = LoraPacket::from_link(&settings.link, most, invert);
1102        self.command(command::set_lora_packet_params(packet))?;
1103        self.update_register(register::IQ_POLARITY, |value| iq_polarity(value, invert))?;
1104        self.command(command::set_dio_irq_params(
1105            events,
1106            events,
1107            Irq::NONE,
1108            Irq::NONE,
1109        ))?;
1110        self.command(command::clear_irq_status(Irq::ALL))
1111    }
1112
1113    fn read_frame(&mut self, buffer: &mut [u8]) -> Result<Reception, RadioError<SPI::Error>> {
1114        let mut position = [0u8; 2];
1115        self.query(command::get_rx_buffer_status(), &mut position)?;
1116        let position = RxBufferStatus::from_bytes(position);
1117        let len = usize::from(position.payload_len);
1118        let frame = buffer
1119            .get_mut(..len)
1120            .ok_or(RadioError::BufferTooSmall(len))?;
1121        if len > 0 {
1122            self.read_buffer(position.start, frame)?;
1123        }
1124        let mut levels = [0u8; 3];
1125        self.query(command::get_packet_status(), &mut levels)?;
1126        Ok(Reception::Frame {
1127            len,
1128            status: PacketStatus::from_bytes(levels),
1129        })
1130    }
1131
1132    fn update_register(
1133        &mut self,
1134        address: u16,
1135        change: impl FnOnce(u8) -> u8,
1136    ) -> Result<(), RadioError<SPI::Error>> {
1137        let mut value = [0u8; 1];
1138        self.read_register(address, &mut value)?;
1139        self.write_register(address, &[change(value[0])])
1140    }
1141
1142    fn ready(&mut self) -> Result<(), RadioError<SPI::Error>> {
1143        if self.asleep {
1144            let wake = command::get_status();
1145            let mut status = [0u8; 1];
1146            self.spi
1147                .transaction(&mut [
1148                    Operation::DelayNs(WAKE_SETUP_NS),
1149                    Operation::Write(wake.command.as_bytes()),
1150                    Operation::Read(&mut status),
1151                ])
1152                .map_err(RadioError::Spi)?;
1153            self.asleep = false;
1154        }
1155        self.wait_busy()
1156    }
1157
1158    fn wait_busy(&mut self) -> Result<(), RadioError<SPI::Error>> {
1159        let settle_us = self.board.tcxo.map_or(0, |(_, settle_us)| settle_us);
1160        let limit_us = BUSY_LIMIT_US.saturating_add(settle_us);
1161        let mut waited_us = 0u32;
1162        while self.busy.is_high().map_err(pin)? {
1163            if waited_us >= limit_us {
1164                return Err(RadioError::Busy);
1165            }
1166            self.delay.delay_us(BUSY_POLL_US);
1167            waited_us = waited_us.saturating_add(BUSY_POLL_US);
1168        }
1169        Ok(())
1170    }
1171
1172    fn wait_for(
1173        &mut self,
1174        events: Irq,
1175        first_us: u64,
1176        limit_us: u64,
1177    ) -> Result<Irq, RadioError<SPI::Error>> {
1178        let mut waited_us = first_us.min(limit_us);
1179        self.pause_us(waited_us);
1180        loop {
1181            let irq = self.irq_status()?;
1182            if irq.intersects(events) {
1183                return Ok(irq);
1184            }
1185            if waited_us >= limit_us {
1186                return Err(RadioError::NoInterrupt);
1187            }
1188            self.delay.delay_us(IRQ_POLL_US);
1189            waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
1190        }
1191    }
1192
1193    fn pause_us(&mut self, micros: u64) {
1194        let mut left = micros;
1195        while left > 0 {
1196            let step = u32::try_from(left).unwrap_or(u32::MAX);
1197            self.delay.delay_us(step);
1198            left -= u64::from(step);
1199        }
1200    }
1201}
1202
1203#[cfg(test)]
1204mod tests {
1205    use super::*;
1206    use crate::sx126x::config::{PaConfig, TcxoVoltage};
1207    use pamoja_hal::digital::PinState;
1208    use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
1209
1210    type Radio = Sx126x<SpiScript, PinScript, PinScript, DelayLog>;
1211
1212    fn idle() -> PinScript {
1213        let mut busy = PinScript::new([]);
1214        busy.set_low().unwrap();
1215        busy
1216    }
1217
1218    fn radio(steps: Vec<SpiStep>, board: Board) -> Radio {
1219        Sx126x::new(
1220            SpiScript::new(steps),
1221            idle(),
1222            PinScript::new([]),
1223            DelayLog::new(),
1224            board,
1225        )
1226    }
1227
1228    fn sent(command: Command) -> SpiStep {
1229        SpiStep::write(command.as_bytes().to_vec())
1230    }
1231
1232    fn asked(query: Query, reply: &[u8]) -> [SpiStep; 2] {
1233        [
1234            SpiStep::write(query.command.as_bytes().to_vec()),
1235            SpiStep::read(reply.to_vec()),
1236        ]
1237    }
1238
1239    fn register_read(address: u16, value: u8) -> [SpiStep; 2] {
1240        let [high, low] = address.to_be_bytes();
1241        [
1242            SpiStep::write([0x1D, high, low, 0x00]),
1243            SpiStep::read([value]),
1244        ]
1245    }
1246
1247    fn register_write(address: u16, values: &[u8]) -> [SpiStep; 2] {
1248        let [high, low] = address.to_be_bytes();
1249        [
1250            SpiStep::write([0x0D, high, low]),
1251            SpiStep::write(values.to_vec()),
1252        ]
1253    }
1254
1255    fn irq(bits: u16) -> [SpiStep; 2] {
1256        [
1257            SpiStep::write([0x12, 0x00]),
1258            SpiStep::read(bits.to_be_bytes()),
1259        ]
1260    }
1261
1262    fn eu868() -> RadioConfig {
1263        RadioConfig::new(
1264            868_100_000,
1265            LinkSettings::new(7, 125_000),
1266            TxPower::for_output(PowerAmplifier::HighPower, 14),
1267        )
1268        .with_band(863_000_000, 870_000_000)
1269        .with_sync_word(SyncWord::Public)
1270    }
1271
1272    fn configured(mut steps: Vec<SpiStep>) -> Radio {
1273        let mut all = vec![
1274            SpiStep::write([0x80, 0x00]),
1275            SpiStep::write([0x8A, 0x01]),
1276            SpiStep::write([0x98, 0xD7, 0xDA]),
1277            SpiStep::write([0x86, 0x36, 0x41, 0x99, 0x9A]),
1278            SpiStep::write([0x95, 0x04, 0x07, 0x00, 0x01]),
1279            SpiStep::write([0x8E, 0x0E, 0x02]),
1280            SpiStep::write([0x8B, 0x07, 0x04, 0x01, 0x00]),
1281        ];
1282        all.extend(register_write(0x0740, &[0x34, 0x44]));
1283        all.append(&mut steps);
1284        let mut radio = radio(all, Board::new(PowerAmplifier::HighPower));
1285        radio.configure(eu868()).expect("configures");
1286        radio
1287    }
1288
1289    #[test]
1290    fn init_resets_then_sets_up_a_tcxo_the_switch_and_the_clamp() {
1291        let mut steps = vec![sent(command::set_standby(StandbyMode::Rc))];
1292        steps.extend(asked(command::get_status(), &[0x22]));
1293        steps.extend([
1294            SpiStep::write([0x97, 0x02, 0x00, 0x01, 0x40]),
1295            SpiStep::write([0x89, 0x7F]),
1296            sent(command::clear_device_errors()),
1297            SpiStep::write([0x96, 0x01]),
1298            SpiStep::write([0x9D, 0x01]),
1299            SpiStep::write([0x8A, 0x01]),
1300        ]);
1301        steps.extend(register_read(0x08D8, 0x08));
1302        steps.extend(register_write(0x08D8, &[0x1E]));
1303        steps.push(SpiStep::write([0x8F, 0x00, 0x00]));
1304        let board = Board::new(PowerAmplifier::HighPower)
1305            .with_tcxo(TcxoVoltage::V1_8, 5_000)
1306            .with_dio2_rf_switch()
1307            .with_dc_dc();
1308
1309        let mut radio = radio(steps, board);
1310        radio.init().expect("initializes");
1311
1312        let (spi, _, reset, delay) = radio.release();
1313        assert!(spi.done(), "{} steps left", spi.remaining());
1314        assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
1315        assert_eq!(delay.waits_ns(), [20_000_000, 10_000_000]);
1316    }
1317
1318    #[test]
1319    fn init_without_an_sx126x_on_the_bus_says_so() {
1320        let mut steps = vec![sent(command::set_standby(StandbyMode::Rc))];
1321        steps.extend(asked(command::get_status(), &[0x00]));
1322        let mut radio = radio(steps, Board::new(PowerAmplifier::HighPower));
1323        assert_eq!(
1324            radio.init(),
1325            Err(RadioError::Absent(Status::from_byte(0x00)))
1326        );
1327    }
1328
1329    #[test]
1330    fn a_busy_line_that_never_falls_times_out() {
1331        let mut radio = Sx126x::new(
1332            SpiScript::new([]),
1333            PinScript::new([]),
1334            PinScript::new([]),
1335            DelayLog::new(),
1336            Board::new(PowerAmplifier::HighPower),
1337        );
1338        assert_eq!(radio.standby(), Err(RadioError::Busy));
1339        let (_, _, _, delay) = radio.release();
1340        assert_eq!(delay.total_micros(), u64::from(BUSY_LIMIT_US));
1341    }
1342
1343    #[test]
1344    fn configure_calibrates_the_band_once_and_tunes_each_channel() {
1345        let mut steps = vec![
1346            SpiStep::write([0x80, 0x00]),
1347            SpiStep::write([0x8A, 0x01]),
1348            sent(command::set_rf_frequency(frequency_word(868_300_000))),
1349            SpiStep::write([0x95, 0x04, 0x07, 0x00, 0x01]),
1350            SpiStep::write([0x8E, 0x0E, 0x02]),
1351            SpiStep::write([0x8B, 0x07, 0x04, 0x01, 0x00]),
1352        ];
1353        steps.extend(register_write(0x0740, &[0x34, 0x44]));
1354        let mut radio = configured(steps);
1355
1356        let next_channel = RadioConfig {
1357            frequency_hz: 868_300_000,
1358            ..eu868()
1359        };
1360        radio.configure(next_channel).expect("retunes");
1361        assert_eq!(radio.config(), Some(&next_channel));
1362        assert!(radio.release().0.done());
1363    }
1364
1365    #[test]
1366    fn configure_refuses_a_bandwidth_or_an_amplifier_the_chip_lacks() {
1367        let mut radio = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1368        let narrow = RadioConfig {
1369            link: LinkSettings::new(7, 203_125),
1370            ..eu868()
1371        };
1372        assert_eq!(radio.configure(narrow), Err(RadioError::Bandwidth(203_125)));
1373
1374        let sx1261 = RadioConfig {
1375            power: TxPower::for_output(PowerAmplifier::LowPower, 14),
1376            ..eu868()
1377        };
1378        assert_eq!(radio.configure(sx1261), Err(RadioError::Amplifier));
1379        assert_eq!(radio.tx_power(14).pa, PaConfig::SX1262_22_DBM);
1380    }
1381
1382    #[test]
1383    fn configure_holds_an_llcc68_to_the_rates_it_supports() {
1384        let board = Board::new(PowerAmplifier::HighPower).with_llcc68();
1385        let mut radio = radio(Vec::new(), board);
1386        let sf10 = RadioConfig {
1387            link: LinkSettings::new(10, 125_000),
1388            ..eu868()
1389        };
1390        assert_eq!(
1391            radio.configure(sf10),
1392            Err(RadioError::Llcc68 {
1393                spreading_factor: 10,
1394                bandwidth_hz: 125_000
1395            })
1396        );
1397        let (spi, _, _, _) = radio.release();
1398        assert_eq!(spi.consumed(), 0, "nothing reaches the bus");
1399    }
1400
1401    #[test]
1402    fn transmit_follows_section_14_2_and_returns_the_airtime() {
1403        let link = LinkSettings::new(7, 125_000);
1404        let airtime_us = link.airtime_us(5);
1405        let mut steps = vec![
1406            SpiStep::write([0x80, 0x00]),
1407            SpiStep::write([0x0E, 0x00]),
1408            SpiStep::write(*b"hello"),
1409            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x05, 0x01, 0x00]),
1410        ];
1411        steps.extend(register_read(0x0736, 0x09));
1412        steps.extend(register_write(0x0736, &[0x0D]));
1413        steps.push(SpiStep::write([
1414            0x08, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
1415        ]));
1416        steps.extend(register_read(0x0889, 0x00));
1417        steps.extend(register_write(0x0889, &[0x04]));
1418        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1419        steps.push(sent(command::set_tx(timeout_steps(
1420            airtime_us + TIMEOUT_MARGIN_US,
1421        ))));
1422        steps.extend(irq(0x0001));
1423        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1424        let mut radio = configured(steps);
1425
1426        assert_eq!(radio.transmit(b"hello"), Ok(airtime_us));
1427        let (spi, _, _, delay) = radio.release();
1428        assert!(spi.done(), "{} steps left", spi.remaining());
1429        assert_eq!(delay.total_micros(), airtime_us);
1430    }
1431
1432    #[test]
1433    fn a_transmission_the_chip_times_out_is_an_error() {
1434        let mut steps = vec![
1435            SpiStep::write([0x80, 0x00]),
1436            SpiStep::write([0x0E, 0x00]),
1437            SpiStep::write([0xAA]),
1438            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x01, 0x01, 0x00]),
1439        ];
1440        steps.extend(register_read(0x0736, 0x0D));
1441        steps.extend(register_write(0x0736, &[0x0D]));
1442        steps.push(SpiStep::write([
1443            0x08, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
1444        ]));
1445        steps.extend(register_read(0x0889, 0x04));
1446        steps.extend(register_write(0x0889, &[0x04]));
1447        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1448        let airtime_us = LinkSettings::new(7, 125_000).airtime_us(1);
1449        steps.push(sent(command::set_tx(timeout_steps(
1450            airtime_us + TIMEOUT_MARGIN_US,
1451        ))));
1452        steps.extend(irq(0x0000));
1453        steps.extend(irq(0x0200));
1454        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1455        let mut radio = configured(steps);
1456
1457        assert_eq!(radio.transmit(&[0xAA]), Err(RadioError::TxTimeout));
1458        let (spi, _, _, delay) = radio.release();
1459        assert!(spi.done());
1460        assert_eq!(delay.total_micros(), airtime_us + u64::from(IRQ_POLL_US));
1461    }
1462
1463    #[test]
1464    fn transmit_refuses_before_configure_and_past_255_bytes() {
1465        let mut radio = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1466        assert_eq!(radio.transmit(b"early"), Err(RadioError::NotConfigured));
1467
1468        let mut radio = configured(Vec::new());
1469        assert_eq!(
1470            radio.transmit(&[0u8; 256]),
1471            Err(RadioError::PayloadTooLong(256))
1472        );
1473        assert!(radio.release().0.done());
1474    }
1475
1476    fn listening(irq_bits: u16) -> Vec<SpiStep> {
1477        let mut steps = vec![
1478            SpiStep::write([0x80, 0x00]),
1479            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x10, 0x01, 0x00]),
1480        ];
1481        steps.extend(register_read(0x0736, 0x0D));
1482        steps.extend(register_write(0x0736, &[0x0D]));
1483        steps.push(SpiStep::write([
1484            0x08, 0x02, 0x62, 0x02, 0x62, 0x00, 0x00, 0x00, 0x00,
1485        ]));
1486        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1487        steps.push(SpiStep::write([0x82, 0x01, 0xF4, 0x00]));
1488        steps.extend(irq(irq_bits));
1489        steps.extend(register_write(0x0902, &[0x00]));
1490        steps.extend(register_read(0x0944, 0x00));
1491        steps.extend(register_write(0x0944, &[0x02]));
1492        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1493        steps
1494    }
1495
1496    #[test]
1497    fn receive_follows_section_14_3_and_copies_out_a_good_frame() {
1498        let mut steps = listening(0x0002);
1499        steps.extend(asked(command::get_rx_buffer_status(), &[0x03, 0x80]));
1500        steps.push(SpiStep::write([0x1E, 0x80, 0x00]));
1501        steps.push(SpiStep::read(*b"hi!"));
1502        steps.extend(asked(command::get_packet_status(), &[0xDB, 0xF6, 0xE0]));
1503        let mut radio = configured(steps);
1504
1505        let mut buffer = [0u8; 16];
1506        let reception = radio.receive(&mut buffer, 2_000_000).expect("receives");
1507        assert_eq!(
1508            reception,
1509            Reception::Frame {
1510                len: 3,
1511                status: PacketStatus::from_bytes([0xDB, 0xF6, 0xE0]),
1512            }
1513        );
1514        assert_eq!(&buffer[..3], b"hi!");
1515        assert!(radio.release().0.done());
1516    }
1517
1518    #[test]
1519    fn a_corrupt_frame_or_a_timeout_carries_no_payload() {
1520        let mut radio = configured(listening(0x0042));
1521        let mut buffer = [0u8; 16];
1522        assert_eq!(
1523            radio.receive(&mut buffer, 2_000_000),
1524            Ok(Reception::Corrupt)
1525        );
1526        assert!(radio.release().0.done());
1527
1528        let mut radio = configured(listening(0x0200));
1529        assert_eq!(
1530            radio.receive(&mut buffer, 2_000_000),
1531            Ok(Reception::Timeout)
1532        );
1533        assert!(radio.release().0.done());
1534    }
1535
1536    #[test]
1537    fn a_payload_longer_than_the_buffer_is_refused() {
1538        let mut steps = listening(0x0002);
1539        steps.extend(asked(command::get_rx_buffer_status(), &[0x20, 0x00]));
1540        let mut radio = configured(steps);
1541        let mut buffer = [0u8; 16];
1542        assert_eq!(
1543            radio.receive(&mut buffer, 2_000_000),
1544            Err(RadioError::BufferTooSmall(32))
1545        );
1546    }
1547
1548    #[test]
1549    fn sleep_forgets_the_configuration_and_the_next_command_wakes_the_chip() {
1550        let mut steps = vec![
1551            SpiStep::write([0x80, 0x00]),
1552            SpiStep::write([0x84, 0x04]),
1553            SpiStep::write([0xC0]),
1554            SpiStep::read([0x00]),
1555        ];
1556        steps.extend(asked(command::get_status(), &[0x22]));
1557        let mut radio = configured(steps);
1558
1559        radio.sleep(true).expect("sleeps");
1560        assert_eq!(radio.config(), None);
1561        assert_eq!(
1562            radio.status().expect("wakes").chip_mode,
1563            ChipMode::StandbyRc
1564        );
1565        assert!(radio.release().0.done());
1566    }
1567
1568    #[test]
1569    fn listen_keeps_receiving_and_take_frame_reads_each_frame_as_it_lands() {
1570        let mut steps = vec![
1571            SpiStep::write([0x80, 0x00]),
1572            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0xFF, 0x01, 0x00]),
1573        ];
1574        steps.extend(register_read(0x0736, 0x0D));
1575        steps.extend(register_write(0x0736, &[0x0D]));
1576        steps.push(SpiStep::write([
1577            0x08, 0x00, 0x62, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00,
1578        ]));
1579        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1580        steps.push(SpiStep::write([0x82, 0xFF, 0xFF, 0xFF]));
1581        steps.extend(irq(0x0000));
1582        steps.extend(irq(0x0002));
1583        steps.push(SpiStep::write([0x02, 0x00, 0x62]));
1584        steps.extend(asked(command::get_rx_buffer_status(), &[0x02, 0x00]));
1585        steps.push(SpiStep::write([0x1E, 0x00, 0x00]));
1586        steps.push(SpiStep::read(*b"ok"));
1587        steps.extend(asked(command::get_packet_status(), &[0x80, 0x1C, 0x82]));
1588        let mut radio = configured(steps);
1589
1590        radio.listen().expect("listens");
1591        let mut buffer = [0u8; 255];
1592        assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1593        let frame = radio.take_frame(&mut buffer).expect("takes the frame");
1594        assert!(matches!(frame, Some(Reception::Frame { len: 2, .. })));
1595        assert_eq!(&buffer[..2], b"ok");
1596        assert!(radio.release().0.done());
1597    }
1598}