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 from the same commands the driver sends: an
406/// SX1262 module with a crystal and the LDO. It stands by on its RC oscillator, answers
407/// GetStatus there, takes the regulator and the LoRa packet type, has its transmit clamp
408/// raised as section 15.2 of the datasheet asks, and places both buffers at zero.
409///
410/// ```
411/// use pamoja_hal::digital::{OutputPin, PinState};
412/// use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
413/// use pamoja_radios::sx126x::command;
414/// use pamoja_radios::sx126x::config::{
415///     register, tx_clamp, PacketType, PowerAmplifier, RegulatorMode, StandbyMode,
416/// };
417/// use pamoja_radios::sx126x::{Board, Sx126x};
418///
419/// // GetStatus in STDBY_RC, and the clamp register as the chip holds it before the fix.
420/// const STATUS_IN_STDBY_RC: u8 = 0x22;
421/// const CLAMP_BEFORE: u8 = 0x08;
422/// let status = command::get_status();
423/// let clamp = command::read_register(register::TX_CLAMP_CONFIG, 1);
424/// let spi = SpiScript::new([
425///     SpiStep::write(command::set_standby(StandbyMode::Rc).as_bytes()),
426///     SpiStep::write(status.command.as_bytes()),
427///     SpiStep::read([STATUS_IN_STDBY_RC]),
428///     SpiStep::write(command::set_regulator_mode(RegulatorMode::Ldo).as_bytes()),
429///     SpiStep::write(command::set_packet_type(PacketType::Lora).as_bytes()),
430///     SpiStep::write(clamp.command.as_bytes()),
431///     SpiStep::read([CLAMP_BEFORE]),
432///     SpiStep::write(command::write_register(register::TX_CLAMP_CONFIG).as_bytes()),
433///     SpiStep::write([tx_clamp(CLAMP_BEFORE)]),
434///     SpiStep::write(command::set_buffer_base_address(0, 0).as_bytes()),
435/// ]);
436/// let mut busy = PinScript::new([]);
437/// busy.set_low()?;
438/// let board = Board::new(PowerAmplifier::HighPower);
439///
440/// let mut radio = Sx126x::new(spi, busy, PinScript::new([]), DelayLog::new(), board);
441/// radio.init().expect("the scripted SX1262 answers");
442///
443/// // Every step was taken in order, after a reset pulse.
444/// let (spi, _, reset, _) = radio.release();
445/// assert!(spi.done());
446/// assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
447/// # Ok::<(), core::convert::Infallible>(())
448/// ```
449pub struct Sx126x<SPI, BUSY, RESET, D> {
450    spi: SPI,
451    busy: BUSY,
452    reset: RESET,
453    delay: D,
454    board: Board,
455    config: Option<RadioConfig>,
456    image: Option<[u8; 2]>,
457    asleep: bool,
458}
459
460impl<SPI, BUSY, RESET, D> Sx126x<SPI, BUSY, RESET, D> {
461    /// Wraps a chip's SPI device and lines. Nothing is sent until [`init`](Sx126x::init).
462    ///
463    /// # Arguments
464    ///
465    /// * `spi` - the SPI device, with NSS as its chip select.
466    /// * `busy` - the BUSY line, as an input.
467    /// * `reset` - the NRESET line, as an output.
468    /// * `delay` - a delay for the reset pulse and the polling.
469    /// * `board` - how the module wires the chip.
470    ///
471    /// # Returns
472    ///
473    /// The driver.
474    pub fn new(spi: SPI, busy: BUSY, reset: RESET, delay: D, board: Board) -> Self {
475        Sx126x {
476            spi,
477            busy,
478            reset,
479            delay,
480            board,
481            config: None,
482            image: None,
483            asleep: false,
484        }
485    }
486
487    /// Returns how the module wires the chip.
488    ///
489    /// # Returns
490    ///
491    /// The board.
492    pub fn board(&self) -> Board {
493        self.board
494    }
495
496    /// Returns the configuration the chip was last tuned to.
497    ///
498    /// # Returns
499    ///
500    /// The configuration, or `None` before [`configure`](Sx126x::configure) or after a
501    /// reset or sleep.
502    pub fn config(&self) -> Option<&RadioConfig> {
503        self.config.as_ref()
504    }
505
506    /// Returns the power settings for an output power on this board's amplifier.
507    ///
508    /// # Arguments
509    ///
510    /// * `output_dbm` - the output power wanted at the antenna port.
511    ///
512    /// # Returns
513    ///
514    /// The amplifier configuration and power setting.
515    pub fn tx_power(&self, output_dbm: i8) -> TxPower {
516        TxPower::for_output(self.board.amplifier, output_dbm)
517    }
518
519    /// Gives back the SPI device, the lines, and the delay.
520    ///
521    /// # Returns
522    ///
523    /// The SPI device, BUSY, NRESET, and the delay.
524    pub fn release(self) -> (SPI, BUSY, RESET, D) {
525        (self.spi, self.busy, self.reset, self.delay)
526    }
527}
528
529impl<SPI, BUSY, RESET, D> Sx126x<SPI, BUSY, RESET, D>
530where
531    SPI: SpiDevice,
532    BUSY: InputPin,
533    RESET: OutputPin,
534    D: DelayNs,
535{
536    /// Resets the chip and sets up the parts the board wires around it.
537    ///
538    /// NRESET is pulsed low, and the chip, which calibrates itself on the way out of reset,
539    /// is put in STDBY_RC and asked for its status. A TCXO is then powered from DIO3 and
540    /// every block calibrated again, since section 9.2.1 says the calibration at power up
541    /// fails on a TCXO, and the XOSC start error that section 13.3.6 expects is cleared.
542    /// The regulator and the DIO2 antenna switch follow, then the LoRa packet type, the
543    /// antenna mismatch workaround of section 15.2 for the high power amplifier, and the
544    /// data buffer base addresses.
545    ///
546    /// # Errors
547    ///
548    /// Returns [`RadioError::Absent`] if the status is not STDBY_RC, [`RadioError::Busy`] if
549    /// BUSY never falls, and [`RadioError::Spi`] or [`RadioError::Pin`] if the bus or a line
550    /// fails.
551    pub fn init(&mut self) -> Result<(), RadioError<SPI::Error>> {
552        self.config = None;
553        self.image = None;
554        self.asleep = false;
555        self.reset.set_low().map_err(pin)?;
556        self.delay.delay_us(RESET_HOLD_US);
557        self.reset.set_high().map_err(pin)?;
558        self.delay.delay_us(RESET_SETTLE_US);
559
560        self.command(command::set_standby(StandbyMode::Rc))?;
561        let status = self.status()?;
562        if !matches!(status.chip_mode, ChipMode::StandbyRc) {
563            return Err(RadioError::Absent(status));
564        }
565        if let Some((voltage, settle_us)) = self.board.tcxo {
566            let settle = timeout_steps(u64::from(settle_us));
567            self.command(command::set_dio3_as_tcxo(voltage, settle))?;
568            self.command(command::calibrate(CALIBRATE_ALL))?;
569            self.command(command::clear_device_errors())?;
570        }
571        self.command(command::set_regulator_mode(self.board.regulator))?;
572        if self.board.dio2_rf_switch {
573            self.command(command::set_dio2_as_rf_switch(true))?;
574        }
575        self.command(command::set_packet_type(PacketType::Lora))?;
576        if self.board.amplifier == PowerAmplifier::HighPower {
577            self.update_register(register::TX_CLAMP_CONFIG, tx_clamp)?;
578        }
579        self.command(command::set_buffer_base_address(0, 0))
580    }
581
582    /// Tunes the chip to a configuration.
583    ///
584    /// The chip goes to STDBY_RC and takes the LoRa packet type, a new image calibration if
585    /// the band moved, the frequency, the amplifier configuration and power, the modulation,
586    /// and the sync word. The packet parameters, which carry the payload length, are sent
587    /// with each transmission and reception, after the modulation as section 14.5 requires.
588    ///
589    /// # Arguments
590    ///
591    /// * `config` - the configuration.
592    ///
593    /// # Errors
594    ///
595    /// Returns [`RadioError::Bandwidth`] if the link's bandwidth is not one the SX126x has,
596    /// [`RadioError::Llcc68`] if the board has an LLCC68 that does not support the link,
597    /// [`RadioError::Amplifier`] if the power settings are for the other amplifier, and the
598    /// bus errors of [`command`](Sx126x::command).
599    pub fn configure(&mut self, config: RadioConfig) -> Result<(), RadioError<SPI::Error>> {
600        let modulation = LoraModulation::from_link(&config.link)
601            .ok_or(RadioError::Bandwidth(config.link.bandwidth_hz()))?;
602        if self.board.llcc68 && !llcc68_supports(modulation.spreading_factor, modulation.bandwidth)
603        {
604            return Err(RadioError::Llcc68 {
605                spreading_factor: modulation.spreading_factor,
606                bandwidth_hz: config.link.bandwidth_hz(),
607            });
608        }
609        let low_power = self.board.amplifier == PowerAmplifier::LowPower;
610        if (config.power.pa.device == 1) != low_power {
611            return Err(RadioError::Amplifier);
612        }
613
614        self.command(command::set_standby(StandbyMode::Rc))?;
615        self.command(command::set_packet_type(PacketType::Lora))?;
616        let (low_hz, high_hz) = config.band_hz;
617        let image = image_calibration(
618            low_hz.min(config.frequency_hz),
619            high_hz.max(config.frequency_hz),
620        );
621        if self.image != Some(image) {
622            self.command(command::calibrate_image(image))?;
623            self.image = Some(image);
624        }
625        self.command(command::set_rf_frequency(frequency_word(
626            config.frequency_hz,
627        )))?;
628        self.command(command::set_pa_config(config.power.pa))?;
629        self.command(command::set_tx_params(
630            config.power.setting_dbm,
631            config.ramp,
632        ))?;
633        self.command(command::set_lora_modulation_params(modulation))?;
634        self.write_register(register::LORA_SYNC_WORD, &config.sync_word.to_bytes())?;
635        self.config = Some(config);
636        Ok(())
637    }
638
639    /// Sends one frame and waits for it to leave.
640    ///
641    /// This is [`start_transmit`](Sx126x::start_transmit), a wait for the frame's airtime,
642    /// and [`finish_transmit`](Sx126x::finish_transmit) read every [`IRQ_POLL_US`] until the
643    /// chip reports the frame sent or timed out.
644    ///
645    /// # Arguments
646    ///
647    /// * `payload` - the frame's payload, at most 255 bytes.
648    ///
649    /// # Returns
650    ///
651    /// The frame's airtime in microseconds, for a [`DutyCycle`](crate::duty::DutyCycle) to
652    /// count.
653    ///
654    /// # Errors
655    ///
656    /// Returns the errors of [`start_transmit`](Sx126x::start_transmit) and
657    /// [`finish_transmit`](Sx126x::finish_transmit), and [`RadioError::NoInterrupt`] if the
658    /// chip reports neither outcome within its own timeout and [`TIMEOUT_MARGIN_US`] more.
659    pub fn transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
660        let airtime_us = self.start_transmit(payload)?;
661        let limit_us = airtime_us.saturating_add(2 * TIMEOUT_MARGIN_US);
662        let mut waited_us = airtime_us;
663        self.pause_us(airtime_us);
664        while !self.finish_transmit()? {
665            if waited_us >= limit_us {
666                return Err(RadioError::NoInterrupt);
667            }
668            self.delay.delay_us(IRQ_POLL_US);
669            waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
670        }
671        Ok(airtime_us)
672    }
673
674    /// Starts sending one frame and returns once the chip is transmitting.
675    ///
676    /// The steps are those of section 14.2 after configuration: the payload into the data
677    /// buffer, the packet parameters, the inverted IQ workaround of section 15.4, TxDone and
678    /// TIMEOUT routed to DIO1, the 500 kHz workaround of section 15.1, and SetTx with a
679    /// timeout of the frame's airtime and [`TIMEOUT_MARGIN_US`]. A caller with its own
680    /// scheduler waits out the airtime and then calls
681    /// [`finish_transmit`](Sx126x::finish_transmit), or watches DIO1.
682    ///
683    /// # Arguments
684    ///
685    /// * `payload` - the frame's payload, at most 255 bytes.
686    ///
687    /// # Returns
688    ///
689    /// The frame's airtime in microseconds.
690    ///
691    /// # Errors
692    ///
693    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure),
694    /// [`RadioError::PayloadTooLong`] past 255 bytes, and the bus errors of
695    /// [`command`](Sx126x::command).
696    pub fn start_transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
697        let settings = self.config.ok_or(RadioError::NotConfigured)?;
698        let len =
699            u8::try_from(payload.len()).map_err(|_| RadioError::PayloadTooLong(payload.len()))?;
700        let bandwidth = LoraModulation::from_link(&settings.link)
701            .ok_or(RadioError::Bandwidth(settings.link.bandwidth_hz()))?
702            .bandwidth;
703
704        self.command(command::set_standby(StandbyMode::Rc))?;
705        if !payload.is_empty() {
706            self.write_buffer(0, payload)?;
707        }
708        let invert = settings.invert_iq_transmit;
709        let packet = LoraPacket::from_link(&settings.link, len, invert);
710        self.command(command::set_lora_packet_params(packet))?;
711        self.update_register(register::IQ_POLARITY, |value| iq_polarity(value, invert))?;
712        let events = Irq::TX_DONE | Irq::TIMEOUT;
713        self.command(command::set_dio_irq_params(
714            events,
715            events,
716            Irq::NONE,
717            Irq::NONE,
718        ))?;
719        self.update_register(register::TX_MODULATION, |value| {
720            tx_modulation(value, bandwidth)
721        })?;
722        self.command(command::clear_irq_status(Irq::ALL))?;
723
724        let airtime_us = settings.link.airtime_us(payload.len());
725        let timeout_us = airtime_us.saturating_add(TIMEOUT_MARGIN_US);
726        self.command(command::set_tx(timeout_steps(timeout_us)))?;
727        Ok(airtime_us)
728    }
729
730    /// Reports whether the frame [`start_transmit`](Sx126x::start_transmit) began has left.
731    ///
732    /// The IRQ register is read once, and on TxDone or TIMEOUT the interrupts are cleared.
733    ///
734    /// # Returns
735    ///
736    /// `true` once the frame has been sent, `false` while it is still going out.
737    ///
738    /// # Errors
739    ///
740    /// Returns [`RadioError::TxTimeout`] if the chip timed out, and the bus errors of
741    /// [`command`](Sx126x::command).
742    pub fn finish_transmit(&mut self) -> Result<bool, RadioError<SPI::Error>> {
743        let irq = self.irq_status()?;
744        if !irq.intersects(Irq::TX_DONE | Irq::TIMEOUT) {
745            return Ok(false);
746        }
747        self.command(command::clear_irq_status(Irq::ALL))?;
748        if irq.contains(Irq::TX_DONE) {
749            Ok(true)
750        } else {
751            Err(RadioError::TxTimeout)
752        }
753    }
754
755    /// Listens for one frame.
756    ///
757    /// The steps are those of section 14.3 after configuration: the packet parameters with
758    /// the buffer's length as the most to accept, the inverted IQ workaround, RxDone,
759    /// TIMEOUT, CrcErr, and HeaderErr routed to DIO1, and SetRx with the timeout. Once an
760    /// interrupt arrives, the timer is stopped and its event cleared as section 15.3 advises
761    /// after any reception with a timeout, the interrupts are cleared, and a frame that
762    /// checked is copied out of the data buffer with its signal levels.
763    ///
764    /// # Arguments
765    ///
766    /// * `buffer` - where the payload goes; its length, up to 255, is the most accepted.
767    /// * `timeout_us` - how long to listen for a frame to start, in microseconds.
768    ///
769    /// # Returns
770    ///
771    /// The frame's length and levels, or that the timeout passed or the frame was corrupt.
772    ///
773    /// # Errors
774    ///
775    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure),
776    /// [`RadioError::BufferTooSmall`] if the chip reports a longer payload than the buffer
777    /// holds, [`RadioError::NoInterrupt`] if it never answers, and the bus errors of
778    /// [`command`](Sx126x::command).
779    pub fn receive(
780        &mut self,
781        buffer: &mut [u8],
782        timeout_us: u64,
783    ) -> Result<Reception, RadioError<SPI::Error>> {
784        let settings = self.config.ok_or(RadioError::NotConfigured)?;
785        let most = u8::try_from(buffer.len()).unwrap_or(u8::MAX);
786        let events = Irq::RX_DONE | Irq::TIMEOUT | Irq::CRC_ERROR | Irq::HEADER_ERROR;
787        self.prepare_reception(&settings, most, events)?;
788
789        let timeout_us = timeout_us.max(1);
790        self.command(command::set_rx(timeout_steps(timeout_us)))?;
791        let frame_us = settings.link.airtime_us(usize::from(most));
792        let limit_us = timeout_us
793            .saturating_add(frame_us)
794            .saturating_add(TIMEOUT_MARGIN_US);
795        let irq = self.wait_for(events, 0, limit_us)?;
796        self.write_register(register::RTC_CONTROL, &[config::RTC_STOP])?;
797        self.update_register(register::EVENT_MASK, config::event_clear)?;
798        self.command(command::clear_irq_status(Irq::ALL))?;
799
800        if irq.intersects(Irq::CRC_ERROR | Irq::HEADER_ERROR) {
801            return Ok(Reception::Corrupt);
802        }
803        if !irq.contains(Irq::RX_DONE) {
804            return Ok(Reception::Timeout);
805        }
806        self.read_frame(buffer)
807    }
808
809    /// Starts listening with no timeout, so the chip receives frame after frame until
810    /// another command stops it: the Rx Continuous mode of Table 13-9.
811    ///
812    /// The setup is that of [`receive`](Sx126x::receive), accepting the 255 bytes a frame
813    /// may carry, with RxDone, CrcErr, and HeaderErr routed to DIO1. Each frame is read with
814    /// [`take_frame`](Sx126x::take_frame).
815    ///
816    /// # Errors
817    ///
818    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure), and the
819    /// bus errors of [`command`](Sx126x::command).
820    pub fn listen(&mut self) -> Result<(), RadioError<SPI::Error>> {
821        let settings = self.config.ok_or(RadioError::NotConfigured)?;
822        let events = Irq::RX_DONE | Irq::CRC_ERROR | Irq::HEADER_ERROR;
823        self.prepare_reception(&settings, u8::MAX, events)?;
824        self.command(command::set_rx(config::RX_CONTINUOUS))
825    }
826
827    /// Takes the frame a [`listen`](Sx126x::listen) has received, if one has arrived.
828    ///
829    /// The IRQ register is read once. On RxDone, CrcErr, or HeaderErr those interrupts are
830    /// cleared, and a frame that checked is copied out with its signal levels while the chip
831    /// goes on listening.
832    ///
833    /// # Arguments
834    ///
835    /// * `buffer` - where the payload goes.
836    ///
837    /// # Returns
838    ///
839    /// The frame, a corrupt frame, or `None` when nothing has arrived.
840    ///
841    /// # Errors
842    ///
843    /// Returns [`RadioError::BufferTooSmall`] if the payload does not fit, and the bus errors
844    /// of [`command`](Sx126x::command).
845    pub fn take_frame(
846        &mut self,
847        buffer: &mut [u8],
848    ) -> Result<Option<Reception>, RadioError<SPI::Error>> {
849        let events = Irq::RX_DONE | Irq::CRC_ERROR | Irq::HEADER_ERROR;
850        let irq = self.irq_status()?;
851        if !irq.intersects(events) {
852            return Ok(None);
853        }
854        self.command(command::clear_irq_status(events))?;
855        if irq.intersects(Irq::CRC_ERROR | Irq::HEADER_ERROR) {
856            return Ok(Some(Reception::Corrupt));
857        }
858        self.read_frame(buffer).map(Some)
859    }
860
861    /// Listens for a preamble over a few symbols and reports whether one is there: the
862    /// channel activity detection of section 13.4.7.
863    ///
864    /// The chip listens over `symbols` symbols, compares what it hears against the
865    /// thresholds Semtech's own radio layer uses for the configured spreading factor and
866    /// bandwidth, and returns to standby either way. A relay scanning for wake-on-radio
867    /// frames calls this once a scan period, and listens only when it says something is
868    /// there.
869    ///
870    /// # Arguments
871    ///
872    /// * `symbols` - how many symbols to listen over: 1, 2, 4, 8 or 16, rounded down to one
873    ///   of them.
874    ///
875    /// # Returns
876    ///
877    /// `true` when the chip detected activity.
878    ///
879    /// # Errors
880    ///
881    /// Returns [`RadioError::NotConfigured`] before [`configure`](Sx126x::configure),
882    /// [`RadioError::NoInterrupt`] if the chip never reports the detection done, and the bus
883    /// errors of [`command`](Sx126x::command).
884    pub fn detect(&mut self, symbols: u8) -> Result<bool, RadioError<SPI::Error>> {
885        let settings = self.config.ok_or(RadioError::NotConfigured)?;
886        let code = config::cad_symbols(symbols);
887        let peak = config::cad_detect_peak(
888            settings.link.spreading_factor(),
889            settings.link.bandwidth_hz(),
890            code,
891        );
892        let events = Irq::CAD_DONE | Irq::CAD_DETECTED;
893        self.command(command::set_standby(StandbyMode::Rc))?;
894        self.command(command::set_cad_params(
895            code,
896            peak,
897            config::CAD_DETECT_MIN,
898            config::CAD_ONLY,
899            0,
900        ))?;
901        self.command(command::set_dio_irq_params(
902            events,
903            events,
904            Irq::NONE,
905            Irq::NONE,
906        ))?;
907        self.command(command::clear_irq_status(Irq::ALL))?;
908        self.command(command::set_cad())?;
909
910        // The detection itself takes its symbols plus the one the chip spends settling,
911        // and the margin covers a bus that answers slowly.
912        let symbol_us = settings.link.symbol_time_us();
913        let listening_us = symbol_us.saturating_mul(u64::from(symbols).saturating_add(2));
914        let irq = self.wait_for(events, listening_us, listening_us + TIMEOUT_MARGIN_US)?;
915        self.command(command::clear_irq_status(Irq::ALL))?;
916        Ok(irq.contains(Irq::CAD_DETECTED))
917    }
918
919    /// Puts the chip in STDBY_RC, which stops a transmission or a reception.
920    ///
921    /// # Errors
922    ///
923    /// Returns the bus errors of [`command`](Sx126x::command).
924    pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
925        self.command(command::set_standby(StandbyMode::Rc))
926    }
927
928    /// Draws a random number from the noise the receiver hears.
929    ///
930    /// The procedure is the one both of Semtech's drivers for this chip follow, LoRaMac-node
931    /// and `sx126x_driver`: clear bit 0 of the LNA control and bit 7 of the mixer control,
932    /// listen continuously, read the four bytes the chip fills from its receiver noise, go
933    /// back to STDBY_RC, and restore both controls. The chip is left in standby, and the
934    /// next frame goes out as configured.
935    ///
936    /// # Returns
937    ///
938    /// Thirty-two bits of noise, the four bytes read low byte first.
939    ///
940    /// # Errors
941    ///
942    /// Returns the errors of [`command`](Sx126x::command).
943    pub fn random(&mut self) -> Result<u32, RadioError<SPI::Error>> {
944        let mut lna = [0u8];
945        self.read_register(register::ANA_LNA, &mut lna)?;
946        self.write_register(register::ANA_LNA, &[lna[0] & !0x01])?;
947        let mut mixer = [0u8];
948        self.read_register(register::ANA_MIXER, &mut mixer)?;
949        self.write_register(register::ANA_MIXER, &[mixer[0] & !0x80])?;
950
951        self.command(command::set_rx(0x00FF_FFFF))?;
952        let mut bytes = [0u8; 4];
953        self.read_register(register::RANDOM_NUMBER, &mut bytes)?;
954        self.command(command::set_standby(StandbyMode::Rc))?;
955
956        self.write_register(register::ANA_LNA, &lna)?;
957        self.write_register(register::ANA_MIXER, &mixer)?;
958        Ok(u32::from_le_bytes(bytes))
959    }
960
961    /// Puts the chip to sleep until the next command wakes it.
962    ///
963    /// A warm start keeps the chip's configuration in retention; a cold start loses it,
964    /// and [`init`](Sx126x::init) must run again. Either way the driver forgets the
965    /// [`RadioConfig`], so [`configure`](Sx126x::configure) runs before the next frame. The
966    /// next command wakes the chip with GetStatus, pausing [`WAKE_SETUP_NS`] after NSS
967    /// falls.
968    ///
969    /// # Arguments
970    ///
971    /// * `warm_start` - `true` to keep the configuration in retention.
972    ///
973    /// # Errors
974    ///
975    /// Returns the bus errors of [`command`](Sx126x::command).
976    pub fn sleep(&mut self, warm_start: bool) -> Result<(), RadioError<SPI::Error>> {
977        self.command(command::set_standby(StandbyMode::Rc))?;
978        self.command(command::set_sleep(warm_start, false))?;
979        self.delay.delay_us(SLEEP_ENTRY_US);
980        self.asleep = true;
981        self.config = None;
982        if !warm_start {
983            self.image = None;
984        }
985        Ok(())
986    }
987
988    /// Reads the status byte.
989    ///
990    /// # Returns
991    ///
992    /// The chip mode and how the last command went.
993    ///
994    /// # Errors
995    ///
996    /// Returns the bus errors of [`command`](Sx126x::command).
997    pub fn status(&mut self) -> Result<Status, RadioError<SPI::Error>> {
998        let mut byte = [0u8; 1];
999        self.query(command::get_status(), &mut byte)?;
1000        Ok(Status::from_byte(byte[0]))
1001    }
1002
1003    /// Reads the pending interrupts.
1004    ///
1005    /// # Returns
1006    ///
1007    /// The IRQ register.
1008    ///
1009    /// # Errors
1010    ///
1011    /// Returns the bus errors of [`command`](Sx126x::command).
1012    pub fn irq_status(&mut self) -> Result<Irq, RadioError<SPI::Error>> {
1013        let mut bytes = [0u8; 2];
1014        self.query(command::get_irq_status(), &mut bytes)?;
1015        Ok(Irq::from_bytes(bytes))
1016    }
1017
1018    /// Reads the signal power the receiver hears right now, while it listens.
1019    ///
1020    /// # Returns
1021    ///
1022    /// The instantaneous RSSI in dBm.
1023    ///
1024    /// # Errors
1025    ///
1026    /// Returns the bus errors of [`command`](Sx126x::command).
1027    pub fn instantaneous_rssi(&mut self) -> Result<Decibels, RadioError<SPI::Error>> {
1028        let mut byte = [0u8; 1];
1029        self.query(command::get_rssi_inst(), &mut byte)?;
1030        Ok(rssi_inst_dbm(byte[0]))
1031    }
1032
1033    /// Reads the calibration, oscillator, PLL, and amplifier errors the chip has flagged.
1034    ///
1035    /// # Returns
1036    ///
1037    /// The device errors.
1038    ///
1039    /// # Errors
1040    ///
1041    /// Returns the bus errors of [`command`](Sx126x::command).
1042    pub fn device_errors(&mut self) -> Result<DeviceErrors, RadioError<SPI::Error>> {
1043        let mut bytes = [0u8; 2];
1044        self.query(command::get_device_errors(), &mut bytes)?;
1045        Ok(DeviceErrors::from_bytes(bytes))
1046    }
1047
1048    /// Clears the device errors.
1049    ///
1050    /// # Errors
1051    ///
1052    /// Returns the bus errors of [`command`](Sx126x::command).
1053    pub fn clear_device_errors(&mut self) -> Result<(), RadioError<SPI::Error>> {
1054        self.command(command::clear_device_errors())
1055    }
1056
1057    /// Chooses between the receiver's power saving gain, the chip's default, and its boosted
1058    /// gain, which buys sensitivity for current (Table 9-3).
1059    ///
1060    /// # Arguments
1061    ///
1062    /// * `boosted` - `true` for boosted gain.
1063    ///
1064    /// # Errors
1065    ///
1066    /// Returns the bus errors of [`command`](Sx126x::command).
1067    pub fn set_rx_boosted(&mut self, boosted: bool) -> Result<(), RadioError<SPI::Error>> {
1068        let gain = if boosted {
1069            config::RX_GAIN_BOOSTED
1070        } else {
1071            config::RX_GAIN_POWER_SAVING
1072        };
1073        self.write_register(register::RX_GAIN, &[gain])
1074    }
1075
1076    /// Sends one command once BUSY is low, waking the chip first if it sleeps.
1077    ///
1078    /// # Arguments
1079    ///
1080    /// * `command` - the command.
1081    ///
1082    /// # Errors
1083    ///
1084    /// Returns [`RadioError::Busy`] if BUSY never falls, [`RadioError::Spi`] if the SPI
1085    /// device fails, and [`RadioError::Pin`] if BUSY cannot be read.
1086    pub fn command(&mut self, command: Command) -> Result<(), RadioError<SPI::Error>> {
1087        self.ready()?;
1088        self.spi.write(command.as_bytes()).map_err(RadioError::Spi)
1089    }
1090
1091    /// Sends a query and reads its answer in the same transaction, once BUSY is low.
1092    ///
1093    /// # Arguments
1094    ///
1095    /// * `query` - the query.
1096    /// * `answer` - where the answer goes; its first `query.answer_len` bytes are filled.
1097    ///
1098    /// # Errors
1099    ///
1100    /// Returns [`RadioError::BufferTooSmall`] if `answer` is shorter than the answer, and
1101    /// the errors of [`command`](Sx126x::command).
1102    pub fn query(&mut self, query: Query, answer: &mut [u8]) -> Result<(), RadioError<SPI::Error>> {
1103        let len = query.answer_len;
1104        let answer = answer
1105            .get_mut(..len)
1106            .ok_or(RadioError::BufferTooSmall(len))?;
1107        self.ready()?;
1108        self.spi
1109            .transaction(&mut [
1110                Operation::Write(query.command.as_bytes()),
1111                Operation::Read(answer),
1112            ])
1113            .map_err(RadioError::Spi)
1114    }
1115
1116    /// Writes consecutive registers.
1117    ///
1118    /// # Arguments
1119    ///
1120    /// * `address` - the first register's address.
1121    /// * `values` - the values, one per register.
1122    ///
1123    /// # Errors
1124    ///
1125    /// Returns the errors of [`command`](Sx126x::command).
1126    pub fn write_register(
1127        &mut self,
1128        address: u16,
1129        values: &[u8],
1130    ) -> Result<(), RadioError<SPI::Error>> {
1131        let header = command::write_register(address);
1132        self.ready()?;
1133        self.spi
1134            .transaction(&mut [
1135                Operation::Write(header.as_bytes()),
1136                Operation::Write(values),
1137            ])
1138            .map_err(RadioError::Spi)
1139    }
1140
1141    /// Reads consecutive registers.
1142    ///
1143    /// # Arguments
1144    ///
1145    /// * `address` - the first register's address.
1146    /// * `values` - where the values go, one per register.
1147    ///
1148    /// # Errors
1149    ///
1150    /// Returns the errors of [`command`](Sx126x::command).
1151    pub fn read_register(
1152        &mut self,
1153        address: u16,
1154        values: &mut [u8],
1155    ) -> Result<(), RadioError<SPI::Error>> {
1156        self.query(command::read_register(address, values.len()), values)
1157    }
1158
1159    /// Writes bytes into the data buffer.
1160    ///
1161    /// # Arguments
1162    ///
1163    /// * `offset` - where in the buffer the first byte goes.
1164    /// * `bytes` - the bytes.
1165    ///
1166    /// # Errors
1167    ///
1168    /// Returns the errors of [`command`](Sx126x::command).
1169    pub fn write_buffer(&mut self, offset: u8, bytes: &[u8]) -> Result<(), RadioError<SPI::Error>> {
1170        let header = command::write_buffer(offset);
1171        self.ready()?;
1172        self.spi
1173            .transaction(&mut [Operation::Write(header.as_bytes()), Operation::Write(bytes)])
1174            .map_err(RadioError::Spi)
1175    }
1176
1177    /// Reads bytes out of the data buffer.
1178    ///
1179    /// # Arguments
1180    ///
1181    /// * `offset` - where in the buffer the first byte is.
1182    /// * `bytes` - where the bytes go.
1183    ///
1184    /// # Errors
1185    ///
1186    /// Returns the errors of [`command`](Sx126x::command).
1187    pub fn read_buffer(
1188        &mut self,
1189        offset: u8,
1190        bytes: &mut [u8],
1191    ) -> Result<(), RadioError<SPI::Error>> {
1192        self.query(command::read_buffer(offset, bytes.len()), bytes)
1193    }
1194
1195    fn prepare_reception(
1196        &mut self,
1197        settings: &RadioConfig,
1198        most: u8,
1199        events: Irq,
1200    ) -> Result<(), RadioError<SPI::Error>> {
1201        self.command(command::set_standby(StandbyMode::Rc))?;
1202        let invert = settings.invert_iq_receive;
1203        let packet = LoraPacket::from_link(&settings.link, most, invert);
1204        self.command(command::set_lora_packet_params(packet))?;
1205        self.update_register(register::IQ_POLARITY, |value| iq_polarity(value, invert))?;
1206        self.command(command::set_dio_irq_params(
1207            events,
1208            events,
1209            Irq::NONE,
1210            Irq::NONE,
1211        ))?;
1212        self.command(command::clear_irq_status(Irq::ALL))
1213    }
1214
1215    fn read_frame(&mut self, buffer: &mut [u8]) -> Result<Reception, RadioError<SPI::Error>> {
1216        let mut position = [0u8; 2];
1217        self.query(command::get_rx_buffer_status(), &mut position)?;
1218        let position = RxBufferStatus::from_bytes(position);
1219        let len = usize::from(position.payload_len);
1220        let frame = buffer
1221            .get_mut(..len)
1222            .ok_or(RadioError::BufferTooSmall(len))?;
1223        if len > 0 {
1224            self.read_buffer(position.start, frame)?;
1225        }
1226        let mut levels = [0u8; 3];
1227        self.query(command::get_packet_status(), &mut levels)?;
1228        Ok(Reception::Frame {
1229            len,
1230            status: PacketStatus::from_bytes(levels),
1231        })
1232    }
1233
1234    fn update_register(
1235        &mut self,
1236        address: u16,
1237        change: impl FnOnce(u8) -> u8,
1238    ) -> Result<(), RadioError<SPI::Error>> {
1239        let mut value = [0u8; 1];
1240        self.read_register(address, &mut value)?;
1241        self.write_register(address, &[change(value[0])])
1242    }
1243
1244    fn ready(&mut self) -> Result<(), RadioError<SPI::Error>> {
1245        if self.asleep {
1246            let wake = command::get_status();
1247            let mut status = [0u8; 1];
1248            self.spi
1249                .transaction(&mut [
1250                    Operation::DelayNs(WAKE_SETUP_NS),
1251                    Operation::Write(wake.command.as_bytes()),
1252                    Operation::Read(&mut status),
1253                ])
1254                .map_err(RadioError::Spi)?;
1255            self.asleep = false;
1256        }
1257        self.wait_busy()
1258    }
1259
1260    fn wait_busy(&mut self) -> Result<(), RadioError<SPI::Error>> {
1261        let settle_us = self.board.tcxo.map_or(0, |(_, settle_us)| settle_us);
1262        let limit_us = BUSY_LIMIT_US.saturating_add(settle_us);
1263        let mut waited_us = 0u32;
1264        while self.busy.is_high().map_err(pin)? {
1265            if waited_us >= limit_us {
1266                return Err(RadioError::Busy);
1267            }
1268            self.delay.delay_us(BUSY_POLL_US);
1269            waited_us = waited_us.saturating_add(BUSY_POLL_US);
1270        }
1271        Ok(())
1272    }
1273
1274    fn wait_for(
1275        &mut self,
1276        events: Irq,
1277        first_us: u64,
1278        limit_us: u64,
1279    ) -> Result<Irq, RadioError<SPI::Error>> {
1280        let mut waited_us = first_us.min(limit_us);
1281        self.pause_us(waited_us);
1282        loop {
1283            let irq = self.irq_status()?;
1284            if irq.intersects(events) {
1285                return Ok(irq);
1286            }
1287            if waited_us >= limit_us {
1288                return Err(RadioError::NoInterrupt);
1289            }
1290            self.delay.delay_us(IRQ_POLL_US);
1291            waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
1292        }
1293    }
1294
1295    fn pause_us(&mut self, micros: u64) {
1296        let mut left = micros;
1297        while left > 0 {
1298            let step = u32::try_from(left).unwrap_or(u32::MAX);
1299            self.delay.delay_us(step);
1300            left -= u64::from(step);
1301        }
1302    }
1303}
1304
1305#[cfg(test)]
1306mod tests {
1307    use super::*;
1308    use crate::sx126x::config::{PaConfig, TcxoVoltage};
1309    use pamoja_hal::digital::PinState;
1310    use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
1311
1312    type Radio = Sx126x<SpiScript, PinScript, PinScript, DelayLog>;
1313
1314    fn idle() -> PinScript {
1315        let mut busy = PinScript::new([]);
1316        busy.set_low().unwrap();
1317        busy
1318    }
1319
1320    fn radio(steps: Vec<SpiStep>, board: Board) -> Radio {
1321        Sx126x::new(
1322            SpiScript::new(steps),
1323            idle(),
1324            PinScript::new([]),
1325            DelayLog::new(),
1326            board,
1327        )
1328    }
1329
1330    fn sent(command: Command) -> SpiStep {
1331        SpiStep::write(command.as_bytes().to_vec())
1332    }
1333
1334    fn asked(query: Query, reply: &[u8]) -> [SpiStep; 2] {
1335        [
1336            SpiStep::write(query.command.as_bytes().to_vec()),
1337            SpiStep::read(reply.to_vec()),
1338        ]
1339    }
1340
1341    fn register_read(address: u16, value: u8) -> [SpiStep; 2] {
1342        let [high, low] = address.to_be_bytes();
1343        [
1344            SpiStep::write([0x1D, high, low, 0x00]),
1345            SpiStep::read([value]),
1346        ]
1347    }
1348
1349    #[test]
1350    fn a_random_number_is_read_while_listening_with_the_lna_and_mixer_bits_cleared() {
1351        let mut steps = Vec::new();
1352        steps.extend(register_read(0x08E2, 0xCB));
1353        steps.extend(register_write(0x08E2, &[0xCA]));
1354        steps.extend(register_read(0x08E5, 0x9F));
1355        steps.extend(register_write(0x08E5, &[0x1F]));
1356        steps.push(SpiStep::write([0x82, 0xFF, 0xFF, 0xFF]));
1357        steps.push(SpiStep::write([0x1D, 0x08, 0x19, 0x00]));
1358        steps.push(SpiStep::read([0x78, 0x56, 0x34, 0x12]));
1359        steps.push(SpiStep::write([0x80, 0x00]));
1360        steps.extend(register_write(0x08E2, &[0xCB]));
1361        steps.extend(register_write(0x08E5, &[0x9F]));
1362
1363        let mut radio = radio(steps, Board::new(PowerAmplifier::HighPower));
1364        assert_eq!(radio.random(), Ok(0x1234_5678));
1365        let (spi, _, _, _) = radio.release();
1366        assert!(spi.done(), "{} steps left", spi.remaining());
1367    }
1368
1369    fn register_write(address: u16, values: &[u8]) -> [SpiStep; 2] {
1370        let [high, low] = address.to_be_bytes();
1371        [
1372            SpiStep::write([0x0D, high, low]),
1373            SpiStep::write(values.to_vec()),
1374        ]
1375    }
1376
1377    fn irq(bits: u16) -> [SpiStep; 2] {
1378        [
1379            SpiStep::write([0x12, 0x00]),
1380            SpiStep::read(bits.to_be_bytes()),
1381        ]
1382    }
1383
1384    fn eu868() -> RadioConfig {
1385        RadioConfig::new(
1386            868_100_000,
1387            LinkSettings::new(7, 125_000),
1388            TxPower::for_output(PowerAmplifier::HighPower, 14),
1389        )
1390        .with_band(863_000_000, 870_000_000)
1391        .with_sync_word(SyncWord::Public)
1392    }
1393
1394    fn configured(mut steps: Vec<SpiStep>) -> Radio {
1395        let mut all = vec![
1396            SpiStep::write([0x80, 0x00]),
1397            SpiStep::write([0x8A, 0x01]),
1398            SpiStep::write([0x98, 0xD7, 0xDA]),
1399            SpiStep::write([0x86, 0x36, 0x41, 0x99, 0x9A]),
1400            SpiStep::write([0x95, 0x04, 0x07, 0x00, 0x01]),
1401            SpiStep::write([0x8E, 0x0E, 0x02]),
1402            SpiStep::write([0x8B, 0x07, 0x04, 0x01, 0x00]),
1403        ];
1404        all.extend(register_write(0x0740, &[0x34, 0x44]));
1405        all.append(&mut steps);
1406        let mut radio = radio(all, Board::new(PowerAmplifier::HighPower));
1407        radio.configure(eu868()).expect("configures");
1408        radio
1409    }
1410
1411    #[test]
1412    fn init_resets_then_sets_up_a_tcxo_the_switch_and_the_clamp() {
1413        let mut steps = vec![sent(command::set_standby(StandbyMode::Rc))];
1414        steps.extend(asked(command::get_status(), &[0x22]));
1415        steps.extend([
1416            SpiStep::write([0x97, 0x02, 0x00, 0x01, 0x40]),
1417            SpiStep::write([0x89, 0x7F]),
1418            sent(command::clear_device_errors()),
1419            SpiStep::write([0x96, 0x01]),
1420            SpiStep::write([0x9D, 0x01]),
1421            SpiStep::write([0x8A, 0x01]),
1422        ]);
1423        steps.extend(register_read(0x08D8, 0x08));
1424        steps.extend(register_write(0x08D8, &[0x1E]));
1425        steps.push(SpiStep::write([0x8F, 0x00, 0x00]));
1426        let board = Board::new(PowerAmplifier::HighPower)
1427            .with_tcxo(TcxoVoltage::V1_8, 5_000)
1428            .with_dio2_rf_switch()
1429            .with_dc_dc();
1430
1431        let mut radio = radio(steps, board);
1432        radio.init().expect("initializes");
1433
1434        let (spi, _, reset, delay) = radio.release();
1435        assert!(spi.done(), "{} steps left", spi.remaining());
1436        assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
1437        assert_eq!(delay.waits_ns(), [20_000_000, 10_000_000]);
1438    }
1439
1440    #[test]
1441    fn init_without_an_sx126x_on_the_bus_says_so() {
1442        let mut steps = vec![sent(command::set_standby(StandbyMode::Rc))];
1443        steps.extend(asked(command::get_status(), &[0x00]));
1444        let mut radio = radio(steps, Board::new(PowerAmplifier::HighPower));
1445        assert_eq!(
1446            radio.init(),
1447            Err(RadioError::Absent(Status::from_byte(0x00)))
1448        );
1449    }
1450
1451    #[test]
1452    fn a_busy_line_that_never_falls_times_out() {
1453        let mut radio = Sx126x::new(
1454            SpiScript::new([]),
1455            PinScript::new([]),
1456            PinScript::new([]),
1457            DelayLog::new(),
1458            Board::new(PowerAmplifier::HighPower),
1459        );
1460        assert_eq!(radio.standby(), Err(RadioError::Busy));
1461        let (_, _, _, delay) = radio.release();
1462        assert_eq!(delay.total_micros(), u64::from(BUSY_LIMIT_US));
1463    }
1464
1465    #[test]
1466    fn configure_calibrates_the_band_once_and_tunes_each_channel() {
1467        let mut steps = vec![
1468            SpiStep::write([0x80, 0x00]),
1469            SpiStep::write([0x8A, 0x01]),
1470            sent(command::set_rf_frequency(frequency_word(868_300_000))),
1471            SpiStep::write([0x95, 0x04, 0x07, 0x00, 0x01]),
1472            SpiStep::write([0x8E, 0x0E, 0x02]),
1473            SpiStep::write([0x8B, 0x07, 0x04, 0x01, 0x00]),
1474        ];
1475        steps.extend(register_write(0x0740, &[0x34, 0x44]));
1476        let mut radio = configured(steps);
1477
1478        let next_channel = RadioConfig {
1479            frequency_hz: 868_300_000,
1480            ..eu868()
1481        };
1482        radio.configure(next_channel).expect("retunes");
1483        assert_eq!(radio.config(), Some(&next_channel));
1484        assert!(radio.release().0.done());
1485    }
1486
1487    #[test]
1488    fn configure_refuses_a_bandwidth_or_an_amplifier_the_chip_lacks() {
1489        let mut radio = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1490        let narrow = RadioConfig {
1491            link: LinkSettings::new(7, 203_125),
1492            ..eu868()
1493        };
1494        assert_eq!(radio.configure(narrow), Err(RadioError::Bandwidth(203_125)));
1495
1496        let sx1261 = RadioConfig {
1497            power: TxPower::for_output(PowerAmplifier::LowPower, 14),
1498            ..eu868()
1499        };
1500        assert_eq!(radio.configure(sx1261), Err(RadioError::Amplifier));
1501        assert_eq!(radio.tx_power(14).pa, PaConfig::SX1262_22_DBM);
1502    }
1503
1504    #[test]
1505    fn configure_holds_an_llcc68_to_the_rates_it_supports() {
1506        let board = Board::new(PowerAmplifier::HighPower).with_llcc68();
1507        let mut radio = radio(Vec::new(), board);
1508        let sf10 = RadioConfig {
1509            link: LinkSettings::new(10, 125_000),
1510            ..eu868()
1511        };
1512        assert_eq!(
1513            radio.configure(sf10),
1514            Err(RadioError::Llcc68 {
1515                spreading_factor: 10,
1516                bandwidth_hz: 125_000
1517            })
1518        );
1519        let (spi, _, _, _) = radio.release();
1520        assert_eq!(spi.consumed(), 0, "nothing reaches the bus");
1521    }
1522
1523    #[test]
1524    fn transmit_follows_section_14_2_and_returns_the_airtime() {
1525        let link = LinkSettings::new(7, 125_000);
1526        let airtime_us = link.airtime_us(5);
1527        let mut steps = vec![
1528            SpiStep::write([0x80, 0x00]),
1529            SpiStep::write([0x0E, 0x00]),
1530            SpiStep::write(*b"hello"),
1531            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x05, 0x01, 0x00]),
1532        ];
1533        steps.extend(register_read(0x0736, 0x09));
1534        steps.extend(register_write(0x0736, &[0x0D]));
1535        steps.push(SpiStep::write([
1536            0x08, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
1537        ]));
1538        steps.extend(register_read(0x0889, 0x00));
1539        steps.extend(register_write(0x0889, &[0x04]));
1540        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1541        steps.push(sent(command::set_tx(timeout_steps(
1542            airtime_us + TIMEOUT_MARGIN_US,
1543        ))));
1544        steps.extend(irq(0x0001));
1545        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1546        let mut radio = configured(steps);
1547
1548        assert_eq!(radio.transmit(b"hello"), Ok(airtime_us));
1549        let (spi, _, _, delay) = radio.release();
1550        assert!(spi.done(), "{} steps left", spi.remaining());
1551        assert_eq!(delay.total_micros(), airtime_us);
1552    }
1553
1554    #[test]
1555    fn a_transmission_the_chip_times_out_is_an_error() {
1556        let mut steps = vec![
1557            SpiStep::write([0x80, 0x00]),
1558            SpiStep::write([0x0E, 0x00]),
1559            SpiStep::write([0xAA]),
1560            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x01, 0x01, 0x00]),
1561        ];
1562        steps.extend(register_read(0x0736, 0x0D));
1563        steps.extend(register_write(0x0736, &[0x0D]));
1564        steps.push(SpiStep::write([
1565            0x08, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
1566        ]));
1567        steps.extend(register_read(0x0889, 0x04));
1568        steps.extend(register_write(0x0889, &[0x04]));
1569        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1570        let airtime_us = LinkSettings::new(7, 125_000).airtime_us(1);
1571        steps.push(sent(command::set_tx(timeout_steps(
1572            airtime_us + TIMEOUT_MARGIN_US,
1573        ))));
1574        steps.extend(irq(0x0000));
1575        steps.extend(irq(0x0200));
1576        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1577        let mut radio = configured(steps);
1578
1579        assert_eq!(radio.transmit(&[0xAA]), Err(RadioError::TxTimeout));
1580        let (spi, _, _, delay) = radio.release();
1581        assert!(spi.done());
1582        assert_eq!(delay.total_micros(), airtime_us + u64::from(IRQ_POLL_US));
1583    }
1584
1585    #[test]
1586    fn transmit_refuses_before_configure_and_past_255_bytes() {
1587        let mut radio = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1588        assert_eq!(radio.transmit(b"early"), Err(RadioError::NotConfigured));
1589
1590        let mut radio = configured(Vec::new());
1591        assert_eq!(
1592            radio.transmit(&[0u8; 256]),
1593            Err(RadioError::PayloadTooLong(256))
1594        );
1595        assert!(radio.release().0.done());
1596    }
1597
1598    fn listening(irq_bits: u16) -> Vec<SpiStep> {
1599        let mut steps = vec![
1600            SpiStep::write([0x80, 0x00]),
1601            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x10, 0x01, 0x00]),
1602        ];
1603        steps.extend(register_read(0x0736, 0x0D));
1604        steps.extend(register_write(0x0736, &[0x0D]));
1605        steps.push(SpiStep::write([
1606            0x08, 0x02, 0x62, 0x02, 0x62, 0x00, 0x00, 0x00, 0x00,
1607        ]));
1608        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1609        steps.push(SpiStep::write([0x82, 0x01, 0xF4, 0x00]));
1610        steps.extend(irq(irq_bits));
1611        steps.extend(register_write(0x0902, &[0x00]));
1612        steps.extend(register_read(0x0944, 0x00));
1613        steps.extend(register_write(0x0944, &[0x02]));
1614        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1615        steps
1616    }
1617
1618    #[test]
1619    fn receive_follows_section_14_3_and_copies_out_a_good_frame() {
1620        let mut steps = listening(0x0002);
1621        steps.extend(asked(command::get_rx_buffer_status(), &[0x03, 0x80]));
1622        steps.push(SpiStep::write([0x1E, 0x80, 0x00]));
1623        steps.push(SpiStep::read(*b"hi!"));
1624        steps.extend(asked(command::get_packet_status(), &[0xDB, 0xF6, 0xE0]));
1625        let mut radio = configured(steps);
1626
1627        let mut buffer = [0u8; 16];
1628        let reception = radio.receive(&mut buffer, 2_000_000).expect("receives");
1629        assert_eq!(
1630            reception,
1631            Reception::Frame {
1632                len: 3,
1633                status: PacketStatus::from_bytes([0xDB, 0xF6, 0xE0]),
1634            }
1635        );
1636        assert_eq!(&buffer[..3], b"hi!");
1637        assert!(radio.release().0.done());
1638    }
1639
1640    #[test]
1641    fn a_corrupt_frame_or_a_timeout_carries_no_payload() {
1642        let mut radio = configured(listening(0x0042));
1643        let mut buffer = [0u8; 16];
1644        assert_eq!(
1645            radio.receive(&mut buffer, 2_000_000),
1646            Ok(Reception::Corrupt)
1647        );
1648        assert!(radio.release().0.done());
1649
1650        let mut radio = configured(listening(0x0200));
1651        assert_eq!(
1652            radio.receive(&mut buffer, 2_000_000),
1653            Ok(Reception::Timeout)
1654        );
1655        assert!(radio.release().0.done());
1656    }
1657
1658    #[test]
1659    fn detection_listens_with_semtechs_thresholds_and_says_what_it_heard() {
1660        // Configured at SF7 over 125 kHz, where Semtech's own radio layer detects over two
1661        // symbols with a peak of 22 and a minimum of 10.
1662        let scan = |bits: u16| {
1663            let mut steps = vec![
1664                SpiStep::write([0x80, 0x00]),
1665                SpiStep::write([0x88, 0x01, 0x16, 0x0A, 0x00, 0x00, 0x00, 0x00]),
1666                SpiStep::write([0x08, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00]),
1667                SpiStep::write([0x02, 0x43, 0xFF]),
1668                SpiStep::write([0xC5]),
1669            ];
1670            steps.extend(irq(bits));
1671            steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1672            steps
1673        };
1674
1675        let mut heard = configured(scan(0x0180));
1676        assert_eq!(heard.detect(2), Ok(true), "CadDone with CadDetected");
1677        assert!(heard.release().0.done());
1678
1679        let mut quiet = configured(scan(0x0080));
1680        assert_eq!(quiet.detect(2), Ok(false), "CadDone alone");
1681        assert!(quiet.release().0.done());
1682
1683        let mut bare = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1684        assert_eq!(bare.detect(2), Err(RadioError::NotConfigured));
1685    }
1686
1687    #[test]
1688    fn a_payload_longer_than_the_buffer_is_refused() {
1689        let mut steps = listening(0x0002);
1690        steps.extend(asked(command::get_rx_buffer_status(), &[0x20, 0x00]));
1691        let mut radio = configured(steps);
1692        let mut buffer = [0u8; 16];
1693        assert_eq!(
1694            radio.receive(&mut buffer, 2_000_000),
1695            Err(RadioError::BufferTooSmall(32))
1696        );
1697    }
1698
1699    #[test]
1700    fn sleep_forgets_the_configuration_and_the_next_command_wakes_the_chip() {
1701        let mut steps = vec![
1702            SpiStep::write([0x80, 0x00]),
1703            SpiStep::write([0x84, 0x04]),
1704            SpiStep::write([0xC0]),
1705            SpiStep::read([0x00]),
1706        ];
1707        steps.extend(asked(command::get_status(), &[0x22]));
1708        let mut radio = configured(steps);
1709
1710        radio.sleep(true).expect("sleeps");
1711        assert_eq!(radio.config(), None);
1712        assert_eq!(
1713            radio.status().expect("wakes").chip_mode,
1714            ChipMode::StandbyRc
1715        );
1716        assert!(radio.release().0.done());
1717    }
1718
1719    #[test]
1720    fn listen_keeps_receiving_and_take_frame_reads_each_frame_as_it_lands() {
1721        let mut steps = vec![
1722            SpiStep::write([0x80, 0x00]),
1723            SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0xFF, 0x01, 0x00]),
1724        ];
1725        steps.extend(register_read(0x0736, 0x0D));
1726        steps.extend(register_write(0x0736, &[0x0D]));
1727        steps.push(SpiStep::write([
1728            0x08, 0x00, 0x62, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00,
1729        ]));
1730        steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1731        steps.push(SpiStep::write([0x82, 0xFF, 0xFF, 0xFF]));
1732        steps.extend(irq(0x0000));
1733        steps.extend(irq(0x0002));
1734        steps.push(SpiStep::write([0x02, 0x00, 0x62]));
1735        steps.extend(asked(command::get_rx_buffer_status(), &[0x02, 0x00]));
1736        steps.push(SpiStep::write([0x1E, 0x00, 0x00]));
1737        steps.push(SpiStep::read(*b"ok"));
1738        steps.extend(asked(command::get_packet_status(), &[0x80, 0x1C, 0x82]));
1739        let mut radio = configured(steps);
1740
1741        radio.listen().expect("listens");
1742        let mut buffer = [0u8; 255];
1743        assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1744        let frame = radio.take_frame(&mut buffer).expect("takes the frame");
1745        assert!(matches!(frame, Some(Reception::Frame { len: 2, .. })));
1746        assert_eq!(&buffer[..2], b"ok");
1747        assert!(radio.release().0.done());
1748    }
1749}