1use 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
23pub const RESET_HOLD_US: u32 = 20_000;
26
27pub const RESET_SETTLE_US: u32 = 10_000;
30
31pub const BUSY_POLL_US: u32 = 100;
33
34pub const BUSY_LIMIT_US: u32 = 100_000;
38
39pub const IRQ_POLL_US: u32 = 1_000;
42
43pub const TIMEOUT_MARGIN_US: u64 = 1_000_000;
46
47pub const WAKE_SETUP_NS: u32 = 100_000;
50
51pub const SLEEP_ENTRY_US: u32 = 1_000;
54
55pub const DEFAULT_TCXO_SETTLE_US: u32 = 5_000;
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub struct Board {
81 pub amplifier: PowerAmplifier,
84 pub tcxo: Option<(TcxoVoltage, u32)>,
87 pub dio2_rf_switch: bool,
89 pub regulator: RegulatorMode,
91 pub llcc68: bool,
94}
95
96impl Board {
97 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 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 pub const fn with_dio2_rf_switch(mut self) -> Board {
137 self.dio2_rf_switch = true;
138 self
139 }
140
141 pub const fn with_dc_dc(mut self) -> Board {
147 self.regulator = RegulatorMode::DcDc;
148 self
149 }
150
151 pub const fn with_llcc68(mut self) -> Board {
157 self.llcc68 = true;
158 self
159 }
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184pub struct RadioConfig {
185 pub frequency_hz: u32,
187 pub band_hz: (u32, u32),
189 pub link: LinkSettings,
191 pub power: TxPower,
193 pub ramp: RampTime,
195 pub sync_word: SyncWord,
197 pub invert_iq_transmit: bool,
199 pub invert_iq_receive: bool,
201}
202
203impl RadioConfig {
204 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 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 pub const fn with_sync_word(mut self, sync_word: SyncWord) -> RadioConfig {
257 self.sync_word = sync_word;
258 self
259 }
260
261 pub const fn with_ramp(mut self, ramp: RampTime) -> RadioConfig {
271 self.ramp = ramp;
272 self
273 }
274
275 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 pub const fn lorawan_device(self) -> RadioConfig {
298 self.with_sync_word(SyncWord::Public)
299 .with_inverted_iq(false, true)
300 }
301}
302
303#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
305pub enum Reception {
306 Frame {
308 len: usize,
310 status: PacketStatus,
312 },
313 Timeout,
315 Corrupt,
317}
318
319#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub enum RadioError<E> {
322 Spi(E),
324 Pin(digital::ErrorKind),
326 Busy,
329 Absent(Status),
332 Bandwidth(u32),
334 Llcc68 {
337 spreading_factor: u8,
339 bandwidth_hz: u32,
341 },
342 Amplifier,
344 PayloadTooLong(usize),
346 BufferTooSmall(usize),
348 NotConfigured,
350 TxTimeout,
352 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
394pub 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 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 pub fn board(&self) -> Board {
493 self.board
494 }
495
496 pub fn config(&self) -> Option<&RadioConfig> {
503 self.config.as_ref()
504 }
505
506 pub fn tx_power(&self, output_dbm: i8) -> TxPower {
516 TxPower::for_output(self.board.amplifier, output_dbm)
517 }
518
519 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 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 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 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 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 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 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 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 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 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 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 pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
925 self.command(command::set_standby(StandbyMode::Rc))
926 }
927
928 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 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 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 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 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 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 pub fn clear_device_errors(&mut self) -> Result<(), RadioError<SPI::Error>> {
1054 self.command(command::clear_device_errors())
1055 }
1056
1057 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 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 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 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 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 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 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 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}