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> {
439 spi: SPI,
440 busy: BUSY,
441 reset: RESET,
442 delay: D,
443 board: Board,
444 config: Option<RadioConfig>,
445 image: Option<[u8; 2]>,
446 asleep: bool,
447}
448
449impl<SPI, BUSY, RESET, D> Sx126x<SPI, BUSY, RESET, D> {
450 pub fn new(spi: SPI, busy: BUSY, reset: RESET, delay: D, board: Board) -> Self {
464 Sx126x {
465 spi,
466 busy,
467 reset,
468 delay,
469 board,
470 config: None,
471 image: None,
472 asleep: false,
473 }
474 }
475
476 pub fn board(&self) -> Board {
482 self.board
483 }
484
485 pub fn config(&self) -> Option<&RadioConfig> {
492 self.config.as_ref()
493 }
494
495 pub fn tx_power(&self, output_dbm: i8) -> TxPower {
505 TxPower::for_output(self.board.amplifier, output_dbm)
506 }
507
508 pub fn release(self) -> (SPI, BUSY, RESET, D) {
514 (self.spi, self.busy, self.reset, self.delay)
515 }
516}
517
518impl<SPI, BUSY, RESET, D> Sx126x<SPI, BUSY, RESET, D>
519where
520 SPI: SpiDevice,
521 BUSY: InputPin,
522 RESET: OutputPin,
523 D: DelayNs,
524{
525 pub fn init(&mut self) -> Result<(), RadioError<SPI::Error>> {
541 self.config = None;
542 self.image = None;
543 self.asleep = false;
544 self.reset.set_low().map_err(pin)?;
545 self.delay.delay_us(RESET_HOLD_US);
546 self.reset.set_high().map_err(pin)?;
547 self.delay.delay_us(RESET_SETTLE_US);
548
549 self.command(command::set_standby(StandbyMode::Rc))?;
550 let status = self.status()?;
551 if !matches!(status.chip_mode, ChipMode::StandbyRc) {
552 return Err(RadioError::Absent(status));
553 }
554 if let Some((voltage, settle_us)) = self.board.tcxo {
555 let settle = timeout_steps(u64::from(settle_us));
556 self.command(command::set_dio3_as_tcxo(voltage, settle))?;
557 self.command(command::calibrate(CALIBRATE_ALL))?;
558 self.command(command::clear_device_errors())?;
559 }
560 self.command(command::set_regulator_mode(self.board.regulator))?;
561 if self.board.dio2_rf_switch {
562 self.command(command::set_dio2_as_rf_switch(true))?;
563 }
564 self.command(command::set_packet_type(PacketType::Lora))?;
565 if self.board.amplifier == PowerAmplifier::HighPower {
566 self.update_register(register::TX_CLAMP_CONFIG, tx_clamp)?;
567 }
568 self.command(command::set_buffer_base_address(0, 0))
569 }
570
571 pub fn configure(&mut self, config: RadioConfig) -> Result<(), RadioError<SPI::Error>> {
589 let modulation = LoraModulation::from_link(&config.link)
590 .ok_or(RadioError::Bandwidth(config.link.bandwidth_hz()))?;
591 if self.board.llcc68 && !llcc68_supports(modulation.spreading_factor, modulation.bandwidth)
592 {
593 return Err(RadioError::Llcc68 {
594 spreading_factor: modulation.spreading_factor,
595 bandwidth_hz: config.link.bandwidth_hz(),
596 });
597 }
598 let low_power = self.board.amplifier == PowerAmplifier::LowPower;
599 if (config.power.pa.device == 1) != low_power {
600 return Err(RadioError::Amplifier);
601 }
602
603 self.command(command::set_standby(StandbyMode::Rc))?;
604 self.command(command::set_packet_type(PacketType::Lora))?;
605 let (low_hz, high_hz) = config.band_hz;
606 let image = image_calibration(
607 low_hz.min(config.frequency_hz),
608 high_hz.max(config.frequency_hz),
609 );
610 if self.image != Some(image) {
611 self.command(command::calibrate_image(image))?;
612 self.image = Some(image);
613 }
614 self.command(command::set_rf_frequency(frequency_word(
615 config.frequency_hz,
616 )))?;
617 self.command(command::set_pa_config(config.power.pa))?;
618 self.command(command::set_tx_params(
619 config.power.setting_dbm,
620 config.ramp,
621 ))?;
622 self.command(command::set_lora_modulation_params(modulation))?;
623 self.write_register(register::LORA_SYNC_WORD, &config.sync_word.to_bytes())?;
624 self.config = Some(config);
625 Ok(())
626 }
627
628 pub fn transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
649 let airtime_us = self.start_transmit(payload)?;
650 let limit_us = airtime_us.saturating_add(2 * TIMEOUT_MARGIN_US);
651 let mut waited_us = airtime_us;
652 self.pause_us(airtime_us);
653 while !self.finish_transmit()? {
654 if waited_us >= limit_us {
655 return Err(RadioError::NoInterrupt);
656 }
657 self.delay.delay_us(IRQ_POLL_US);
658 waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
659 }
660 Ok(airtime_us)
661 }
662
663 pub fn start_transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
686 let settings = self.config.ok_or(RadioError::NotConfigured)?;
687 let len =
688 u8::try_from(payload.len()).map_err(|_| RadioError::PayloadTooLong(payload.len()))?;
689 let bandwidth = LoraModulation::from_link(&settings.link)
690 .ok_or(RadioError::Bandwidth(settings.link.bandwidth_hz()))?
691 .bandwidth;
692
693 self.command(command::set_standby(StandbyMode::Rc))?;
694 if !payload.is_empty() {
695 self.write_buffer(0, payload)?;
696 }
697 let invert = settings.invert_iq_transmit;
698 let packet = LoraPacket::from_link(&settings.link, len, invert);
699 self.command(command::set_lora_packet_params(packet))?;
700 self.update_register(register::IQ_POLARITY, |value| iq_polarity(value, invert))?;
701 let events = Irq::TX_DONE | Irq::TIMEOUT;
702 self.command(command::set_dio_irq_params(
703 events,
704 events,
705 Irq::NONE,
706 Irq::NONE,
707 ))?;
708 self.update_register(register::TX_MODULATION, |value| {
709 tx_modulation(value, bandwidth)
710 })?;
711 self.command(command::clear_irq_status(Irq::ALL))?;
712
713 let airtime_us = settings.link.airtime_us(payload.len());
714 let timeout_us = airtime_us.saturating_add(TIMEOUT_MARGIN_US);
715 self.command(command::set_tx(timeout_steps(timeout_us)))?;
716 Ok(airtime_us)
717 }
718
719 pub fn finish_transmit(&mut self) -> Result<bool, RadioError<SPI::Error>> {
732 let irq = self.irq_status()?;
733 if !irq.intersects(Irq::TX_DONE | Irq::TIMEOUT) {
734 return Ok(false);
735 }
736 self.command(command::clear_irq_status(Irq::ALL))?;
737 if irq.contains(Irq::TX_DONE) {
738 Ok(true)
739 } else {
740 Err(RadioError::TxTimeout)
741 }
742 }
743
744 pub fn receive(
769 &mut self,
770 buffer: &mut [u8],
771 timeout_us: u64,
772 ) -> Result<Reception, RadioError<SPI::Error>> {
773 let settings = self.config.ok_or(RadioError::NotConfigured)?;
774 let most = u8::try_from(buffer.len()).unwrap_or(u8::MAX);
775 let events = Irq::RX_DONE | Irq::TIMEOUT | Irq::CRC_ERROR | Irq::HEADER_ERROR;
776 self.prepare_reception(&settings, most, events)?;
777
778 let timeout_us = timeout_us.max(1);
779 self.command(command::set_rx(timeout_steps(timeout_us)))?;
780 let frame_us = settings.link.airtime_us(usize::from(most));
781 let limit_us = timeout_us
782 .saturating_add(frame_us)
783 .saturating_add(TIMEOUT_MARGIN_US);
784 let irq = self.wait_for(events, 0, limit_us)?;
785 self.write_register(register::RTC_CONTROL, &[config::RTC_STOP])?;
786 self.update_register(register::EVENT_MASK, config::event_clear)?;
787 self.command(command::clear_irq_status(Irq::ALL))?;
788
789 if irq.intersects(Irq::CRC_ERROR | Irq::HEADER_ERROR) {
790 return Ok(Reception::Corrupt);
791 }
792 if !irq.contains(Irq::RX_DONE) {
793 return Ok(Reception::Timeout);
794 }
795 self.read_frame(buffer)
796 }
797
798 pub fn listen(&mut self) -> Result<(), RadioError<SPI::Error>> {
810 let settings = self.config.ok_or(RadioError::NotConfigured)?;
811 let events = Irq::RX_DONE | Irq::CRC_ERROR | Irq::HEADER_ERROR;
812 self.prepare_reception(&settings, u8::MAX, events)?;
813 self.command(command::set_rx(config::RX_CONTINUOUS))
814 }
815
816 pub fn take_frame(
835 &mut self,
836 buffer: &mut [u8],
837 ) -> Result<Option<Reception>, RadioError<SPI::Error>> {
838 let events = Irq::RX_DONE | Irq::CRC_ERROR | Irq::HEADER_ERROR;
839 let irq = self.irq_status()?;
840 if !irq.intersects(events) {
841 return Ok(None);
842 }
843 self.command(command::clear_irq_status(events))?;
844 if irq.intersects(Irq::CRC_ERROR | Irq::HEADER_ERROR) {
845 return Ok(Some(Reception::Corrupt));
846 }
847 self.read_frame(buffer).map(Some)
848 }
849
850 pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
856 self.command(command::set_standby(StandbyMode::Rc))
857 }
858
859 pub fn sleep(&mut self, warm_start: bool) -> Result<(), RadioError<SPI::Error>> {
875 self.command(command::set_standby(StandbyMode::Rc))?;
876 self.command(command::set_sleep(warm_start, false))?;
877 self.delay.delay_us(SLEEP_ENTRY_US);
878 self.asleep = true;
879 self.config = None;
880 if !warm_start {
881 self.image = None;
882 }
883 Ok(())
884 }
885
886 pub fn status(&mut self) -> Result<Status, RadioError<SPI::Error>> {
896 let mut byte = [0u8; 1];
897 self.query(command::get_status(), &mut byte)?;
898 Ok(Status::from_byte(byte[0]))
899 }
900
901 pub fn irq_status(&mut self) -> Result<Irq, RadioError<SPI::Error>> {
911 let mut bytes = [0u8; 2];
912 self.query(command::get_irq_status(), &mut bytes)?;
913 Ok(Irq::from_bytes(bytes))
914 }
915
916 pub fn instantaneous_rssi(&mut self) -> Result<Decibels, RadioError<SPI::Error>> {
926 let mut byte = [0u8; 1];
927 self.query(command::get_rssi_inst(), &mut byte)?;
928 Ok(rssi_inst_dbm(byte[0]))
929 }
930
931 pub fn device_errors(&mut self) -> Result<DeviceErrors, RadioError<SPI::Error>> {
941 let mut bytes = [0u8; 2];
942 self.query(command::get_device_errors(), &mut bytes)?;
943 Ok(DeviceErrors::from_bytes(bytes))
944 }
945
946 pub fn clear_device_errors(&mut self) -> Result<(), RadioError<SPI::Error>> {
952 self.command(command::clear_device_errors())
953 }
954
955 pub fn set_rx_boosted(&mut self, boosted: bool) -> Result<(), RadioError<SPI::Error>> {
966 let gain = if boosted {
967 config::RX_GAIN_BOOSTED
968 } else {
969 config::RX_GAIN_POWER_SAVING
970 };
971 self.write_register(register::RX_GAIN, &[gain])
972 }
973
974 pub fn command(&mut self, command: Command) -> Result<(), RadioError<SPI::Error>> {
985 self.ready()?;
986 self.spi.write(command.as_bytes()).map_err(RadioError::Spi)
987 }
988
989 pub fn query(&mut self, query: Query, answer: &mut [u8]) -> Result<(), RadioError<SPI::Error>> {
1001 let len = query.answer_len;
1002 let answer = answer
1003 .get_mut(..len)
1004 .ok_or(RadioError::BufferTooSmall(len))?;
1005 self.ready()?;
1006 self.spi
1007 .transaction(&mut [
1008 Operation::Write(query.command.as_bytes()),
1009 Operation::Read(answer),
1010 ])
1011 .map_err(RadioError::Spi)
1012 }
1013
1014 pub fn write_register(
1025 &mut self,
1026 address: u16,
1027 values: &[u8],
1028 ) -> Result<(), RadioError<SPI::Error>> {
1029 let header = command::write_register(address);
1030 self.ready()?;
1031 self.spi
1032 .transaction(&mut [
1033 Operation::Write(header.as_bytes()),
1034 Operation::Write(values),
1035 ])
1036 .map_err(RadioError::Spi)
1037 }
1038
1039 pub fn read_register(
1050 &mut self,
1051 address: u16,
1052 values: &mut [u8],
1053 ) -> Result<(), RadioError<SPI::Error>> {
1054 self.query(command::read_register(address, values.len()), values)
1055 }
1056
1057 pub fn write_buffer(&mut self, offset: u8, bytes: &[u8]) -> Result<(), RadioError<SPI::Error>> {
1068 let header = command::write_buffer(offset);
1069 self.ready()?;
1070 self.spi
1071 .transaction(&mut [Operation::Write(header.as_bytes()), Operation::Write(bytes)])
1072 .map_err(RadioError::Spi)
1073 }
1074
1075 pub fn read_buffer(
1086 &mut self,
1087 offset: u8,
1088 bytes: &mut [u8],
1089 ) -> Result<(), RadioError<SPI::Error>> {
1090 self.query(command::read_buffer(offset, bytes.len()), bytes)
1091 }
1092
1093 fn prepare_reception(
1094 &mut self,
1095 settings: &RadioConfig,
1096 most: u8,
1097 events: Irq,
1098 ) -> Result<(), RadioError<SPI::Error>> {
1099 self.command(command::set_standby(StandbyMode::Rc))?;
1100 let invert = settings.invert_iq_receive;
1101 let packet = LoraPacket::from_link(&settings.link, most, invert);
1102 self.command(command::set_lora_packet_params(packet))?;
1103 self.update_register(register::IQ_POLARITY, |value| iq_polarity(value, invert))?;
1104 self.command(command::set_dio_irq_params(
1105 events,
1106 events,
1107 Irq::NONE,
1108 Irq::NONE,
1109 ))?;
1110 self.command(command::clear_irq_status(Irq::ALL))
1111 }
1112
1113 fn read_frame(&mut self, buffer: &mut [u8]) -> Result<Reception, RadioError<SPI::Error>> {
1114 let mut position = [0u8; 2];
1115 self.query(command::get_rx_buffer_status(), &mut position)?;
1116 let position = RxBufferStatus::from_bytes(position);
1117 let len = usize::from(position.payload_len);
1118 let frame = buffer
1119 .get_mut(..len)
1120 .ok_or(RadioError::BufferTooSmall(len))?;
1121 if len > 0 {
1122 self.read_buffer(position.start, frame)?;
1123 }
1124 let mut levels = [0u8; 3];
1125 self.query(command::get_packet_status(), &mut levels)?;
1126 Ok(Reception::Frame {
1127 len,
1128 status: PacketStatus::from_bytes(levels),
1129 })
1130 }
1131
1132 fn update_register(
1133 &mut self,
1134 address: u16,
1135 change: impl FnOnce(u8) -> u8,
1136 ) -> Result<(), RadioError<SPI::Error>> {
1137 let mut value = [0u8; 1];
1138 self.read_register(address, &mut value)?;
1139 self.write_register(address, &[change(value[0])])
1140 }
1141
1142 fn ready(&mut self) -> Result<(), RadioError<SPI::Error>> {
1143 if self.asleep {
1144 let wake = command::get_status();
1145 let mut status = [0u8; 1];
1146 self.spi
1147 .transaction(&mut [
1148 Operation::DelayNs(WAKE_SETUP_NS),
1149 Operation::Write(wake.command.as_bytes()),
1150 Operation::Read(&mut status),
1151 ])
1152 .map_err(RadioError::Spi)?;
1153 self.asleep = false;
1154 }
1155 self.wait_busy()
1156 }
1157
1158 fn wait_busy(&mut self) -> Result<(), RadioError<SPI::Error>> {
1159 let settle_us = self.board.tcxo.map_or(0, |(_, settle_us)| settle_us);
1160 let limit_us = BUSY_LIMIT_US.saturating_add(settle_us);
1161 let mut waited_us = 0u32;
1162 while self.busy.is_high().map_err(pin)? {
1163 if waited_us >= limit_us {
1164 return Err(RadioError::Busy);
1165 }
1166 self.delay.delay_us(BUSY_POLL_US);
1167 waited_us = waited_us.saturating_add(BUSY_POLL_US);
1168 }
1169 Ok(())
1170 }
1171
1172 fn wait_for(
1173 &mut self,
1174 events: Irq,
1175 first_us: u64,
1176 limit_us: u64,
1177 ) -> Result<Irq, RadioError<SPI::Error>> {
1178 let mut waited_us = first_us.min(limit_us);
1179 self.pause_us(waited_us);
1180 loop {
1181 let irq = self.irq_status()?;
1182 if irq.intersects(events) {
1183 return Ok(irq);
1184 }
1185 if waited_us >= limit_us {
1186 return Err(RadioError::NoInterrupt);
1187 }
1188 self.delay.delay_us(IRQ_POLL_US);
1189 waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
1190 }
1191 }
1192
1193 fn pause_us(&mut self, micros: u64) {
1194 let mut left = micros;
1195 while left > 0 {
1196 let step = u32::try_from(left).unwrap_or(u32::MAX);
1197 self.delay.delay_us(step);
1198 left -= u64::from(step);
1199 }
1200 }
1201}
1202
1203#[cfg(test)]
1204mod tests {
1205 use super::*;
1206 use crate::sx126x::config::{PaConfig, TcxoVoltage};
1207 use pamoja_hal::digital::PinState;
1208 use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
1209
1210 type Radio = Sx126x<SpiScript, PinScript, PinScript, DelayLog>;
1211
1212 fn idle() -> PinScript {
1213 let mut busy = PinScript::new([]);
1214 busy.set_low().unwrap();
1215 busy
1216 }
1217
1218 fn radio(steps: Vec<SpiStep>, board: Board) -> Radio {
1219 Sx126x::new(
1220 SpiScript::new(steps),
1221 idle(),
1222 PinScript::new([]),
1223 DelayLog::new(),
1224 board,
1225 )
1226 }
1227
1228 fn sent(command: Command) -> SpiStep {
1229 SpiStep::write(command.as_bytes().to_vec())
1230 }
1231
1232 fn asked(query: Query, reply: &[u8]) -> [SpiStep; 2] {
1233 [
1234 SpiStep::write(query.command.as_bytes().to_vec()),
1235 SpiStep::read(reply.to_vec()),
1236 ]
1237 }
1238
1239 fn register_read(address: u16, value: u8) -> [SpiStep; 2] {
1240 let [high, low] = address.to_be_bytes();
1241 [
1242 SpiStep::write([0x1D, high, low, 0x00]),
1243 SpiStep::read([value]),
1244 ]
1245 }
1246
1247 fn register_write(address: u16, values: &[u8]) -> [SpiStep; 2] {
1248 let [high, low] = address.to_be_bytes();
1249 [
1250 SpiStep::write([0x0D, high, low]),
1251 SpiStep::write(values.to_vec()),
1252 ]
1253 }
1254
1255 fn irq(bits: u16) -> [SpiStep; 2] {
1256 [
1257 SpiStep::write([0x12, 0x00]),
1258 SpiStep::read(bits.to_be_bytes()),
1259 ]
1260 }
1261
1262 fn eu868() -> RadioConfig {
1263 RadioConfig::new(
1264 868_100_000,
1265 LinkSettings::new(7, 125_000),
1266 TxPower::for_output(PowerAmplifier::HighPower, 14),
1267 )
1268 .with_band(863_000_000, 870_000_000)
1269 .with_sync_word(SyncWord::Public)
1270 }
1271
1272 fn configured(mut steps: Vec<SpiStep>) -> Radio {
1273 let mut all = vec![
1274 SpiStep::write([0x80, 0x00]),
1275 SpiStep::write([0x8A, 0x01]),
1276 SpiStep::write([0x98, 0xD7, 0xDA]),
1277 SpiStep::write([0x86, 0x36, 0x41, 0x99, 0x9A]),
1278 SpiStep::write([0x95, 0x04, 0x07, 0x00, 0x01]),
1279 SpiStep::write([0x8E, 0x0E, 0x02]),
1280 SpiStep::write([0x8B, 0x07, 0x04, 0x01, 0x00]),
1281 ];
1282 all.extend(register_write(0x0740, &[0x34, 0x44]));
1283 all.append(&mut steps);
1284 let mut radio = radio(all, Board::new(PowerAmplifier::HighPower));
1285 radio.configure(eu868()).expect("configures");
1286 radio
1287 }
1288
1289 #[test]
1290 fn init_resets_then_sets_up_a_tcxo_the_switch_and_the_clamp() {
1291 let mut steps = vec![sent(command::set_standby(StandbyMode::Rc))];
1292 steps.extend(asked(command::get_status(), &[0x22]));
1293 steps.extend([
1294 SpiStep::write([0x97, 0x02, 0x00, 0x01, 0x40]),
1295 SpiStep::write([0x89, 0x7F]),
1296 sent(command::clear_device_errors()),
1297 SpiStep::write([0x96, 0x01]),
1298 SpiStep::write([0x9D, 0x01]),
1299 SpiStep::write([0x8A, 0x01]),
1300 ]);
1301 steps.extend(register_read(0x08D8, 0x08));
1302 steps.extend(register_write(0x08D8, &[0x1E]));
1303 steps.push(SpiStep::write([0x8F, 0x00, 0x00]));
1304 let board = Board::new(PowerAmplifier::HighPower)
1305 .with_tcxo(TcxoVoltage::V1_8, 5_000)
1306 .with_dio2_rf_switch()
1307 .with_dc_dc();
1308
1309 let mut radio = radio(steps, board);
1310 radio.init().expect("initializes");
1311
1312 let (spi, _, reset, delay) = radio.release();
1313 assert!(spi.done(), "{} steps left", spi.remaining());
1314 assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
1315 assert_eq!(delay.waits_ns(), [20_000_000, 10_000_000]);
1316 }
1317
1318 #[test]
1319 fn init_without_an_sx126x_on_the_bus_says_so() {
1320 let mut steps = vec![sent(command::set_standby(StandbyMode::Rc))];
1321 steps.extend(asked(command::get_status(), &[0x00]));
1322 let mut radio = radio(steps, Board::new(PowerAmplifier::HighPower));
1323 assert_eq!(
1324 radio.init(),
1325 Err(RadioError::Absent(Status::from_byte(0x00)))
1326 );
1327 }
1328
1329 #[test]
1330 fn a_busy_line_that_never_falls_times_out() {
1331 let mut radio = Sx126x::new(
1332 SpiScript::new([]),
1333 PinScript::new([]),
1334 PinScript::new([]),
1335 DelayLog::new(),
1336 Board::new(PowerAmplifier::HighPower),
1337 );
1338 assert_eq!(radio.standby(), Err(RadioError::Busy));
1339 let (_, _, _, delay) = radio.release();
1340 assert_eq!(delay.total_micros(), u64::from(BUSY_LIMIT_US));
1341 }
1342
1343 #[test]
1344 fn configure_calibrates_the_band_once_and_tunes_each_channel() {
1345 let mut steps = vec![
1346 SpiStep::write([0x80, 0x00]),
1347 SpiStep::write([0x8A, 0x01]),
1348 sent(command::set_rf_frequency(frequency_word(868_300_000))),
1349 SpiStep::write([0x95, 0x04, 0x07, 0x00, 0x01]),
1350 SpiStep::write([0x8E, 0x0E, 0x02]),
1351 SpiStep::write([0x8B, 0x07, 0x04, 0x01, 0x00]),
1352 ];
1353 steps.extend(register_write(0x0740, &[0x34, 0x44]));
1354 let mut radio = configured(steps);
1355
1356 let next_channel = RadioConfig {
1357 frequency_hz: 868_300_000,
1358 ..eu868()
1359 };
1360 radio.configure(next_channel).expect("retunes");
1361 assert_eq!(radio.config(), Some(&next_channel));
1362 assert!(radio.release().0.done());
1363 }
1364
1365 #[test]
1366 fn configure_refuses_a_bandwidth_or_an_amplifier_the_chip_lacks() {
1367 let mut radio = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1368 let narrow = RadioConfig {
1369 link: LinkSettings::new(7, 203_125),
1370 ..eu868()
1371 };
1372 assert_eq!(radio.configure(narrow), Err(RadioError::Bandwidth(203_125)));
1373
1374 let sx1261 = RadioConfig {
1375 power: TxPower::for_output(PowerAmplifier::LowPower, 14),
1376 ..eu868()
1377 };
1378 assert_eq!(radio.configure(sx1261), Err(RadioError::Amplifier));
1379 assert_eq!(radio.tx_power(14).pa, PaConfig::SX1262_22_DBM);
1380 }
1381
1382 #[test]
1383 fn configure_holds_an_llcc68_to_the_rates_it_supports() {
1384 let board = Board::new(PowerAmplifier::HighPower).with_llcc68();
1385 let mut radio = radio(Vec::new(), board);
1386 let sf10 = RadioConfig {
1387 link: LinkSettings::new(10, 125_000),
1388 ..eu868()
1389 };
1390 assert_eq!(
1391 radio.configure(sf10),
1392 Err(RadioError::Llcc68 {
1393 spreading_factor: 10,
1394 bandwidth_hz: 125_000
1395 })
1396 );
1397 let (spi, _, _, _) = radio.release();
1398 assert_eq!(spi.consumed(), 0, "nothing reaches the bus");
1399 }
1400
1401 #[test]
1402 fn transmit_follows_section_14_2_and_returns_the_airtime() {
1403 let link = LinkSettings::new(7, 125_000);
1404 let airtime_us = link.airtime_us(5);
1405 let mut steps = vec![
1406 SpiStep::write([0x80, 0x00]),
1407 SpiStep::write([0x0E, 0x00]),
1408 SpiStep::write(*b"hello"),
1409 SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x05, 0x01, 0x00]),
1410 ];
1411 steps.extend(register_read(0x0736, 0x09));
1412 steps.extend(register_write(0x0736, &[0x0D]));
1413 steps.push(SpiStep::write([
1414 0x08, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
1415 ]));
1416 steps.extend(register_read(0x0889, 0x00));
1417 steps.extend(register_write(0x0889, &[0x04]));
1418 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1419 steps.push(sent(command::set_tx(timeout_steps(
1420 airtime_us + TIMEOUT_MARGIN_US,
1421 ))));
1422 steps.extend(irq(0x0001));
1423 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1424 let mut radio = configured(steps);
1425
1426 assert_eq!(radio.transmit(b"hello"), Ok(airtime_us));
1427 let (spi, _, _, delay) = radio.release();
1428 assert!(spi.done(), "{} steps left", spi.remaining());
1429 assert_eq!(delay.total_micros(), airtime_us);
1430 }
1431
1432 #[test]
1433 fn a_transmission_the_chip_times_out_is_an_error() {
1434 let mut steps = vec![
1435 SpiStep::write([0x80, 0x00]),
1436 SpiStep::write([0x0E, 0x00]),
1437 SpiStep::write([0xAA]),
1438 SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x01, 0x01, 0x00]),
1439 ];
1440 steps.extend(register_read(0x0736, 0x0D));
1441 steps.extend(register_write(0x0736, &[0x0D]));
1442 steps.push(SpiStep::write([
1443 0x08, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
1444 ]));
1445 steps.extend(register_read(0x0889, 0x04));
1446 steps.extend(register_write(0x0889, &[0x04]));
1447 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1448 let airtime_us = LinkSettings::new(7, 125_000).airtime_us(1);
1449 steps.push(sent(command::set_tx(timeout_steps(
1450 airtime_us + TIMEOUT_MARGIN_US,
1451 ))));
1452 steps.extend(irq(0x0000));
1453 steps.extend(irq(0x0200));
1454 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1455 let mut radio = configured(steps);
1456
1457 assert_eq!(radio.transmit(&[0xAA]), Err(RadioError::TxTimeout));
1458 let (spi, _, _, delay) = radio.release();
1459 assert!(spi.done());
1460 assert_eq!(delay.total_micros(), airtime_us + u64::from(IRQ_POLL_US));
1461 }
1462
1463 #[test]
1464 fn transmit_refuses_before_configure_and_past_255_bytes() {
1465 let mut radio = radio(Vec::new(), Board::new(PowerAmplifier::HighPower));
1466 assert_eq!(radio.transmit(b"early"), Err(RadioError::NotConfigured));
1467
1468 let mut radio = configured(Vec::new());
1469 assert_eq!(
1470 radio.transmit(&[0u8; 256]),
1471 Err(RadioError::PayloadTooLong(256))
1472 );
1473 assert!(radio.release().0.done());
1474 }
1475
1476 fn listening(irq_bits: u16) -> Vec<SpiStep> {
1477 let mut steps = vec![
1478 SpiStep::write([0x80, 0x00]),
1479 SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0x10, 0x01, 0x00]),
1480 ];
1481 steps.extend(register_read(0x0736, 0x0D));
1482 steps.extend(register_write(0x0736, &[0x0D]));
1483 steps.push(SpiStep::write([
1484 0x08, 0x02, 0x62, 0x02, 0x62, 0x00, 0x00, 0x00, 0x00,
1485 ]));
1486 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1487 steps.push(SpiStep::write([0x82, 0x01, 0xF4, 0x00]));
1488 steps.extend(irq(irq_bits));
1489 steps.extend(register_write(0x0902, &[0x00]));
1490 steps.extend(register_read(0x0944, 0x00));
1491 steps.extend(register_write(0x0944, &[0x02]));
1492 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1493 steps
1494 }
1495
1496 #[test]
1497 fn receive_follows_section_14_3_and_copies_out_a_good_frame() {
1498 let mut steps = listening(0x0002);
1499 steps.extend(asked(command::get_rx_buffer_status(), &[0x03, 0x80]));
1500 steps.push(SpiStep::write([0x1E, 0x80, 0x00]));
1501 steps.push(SpiStep::read(*b"hi!"));
1502 steps.extend(asked(command::get_packet_status(), &[0xDB, 0xF6, 0xE0]));
1503 let mut radio = configured(steps);
1504
1505 let mut buffer = [0u8; 16];
1506 let reception = radio.receive(&mut buffer, 2_000_000).expect("receives");
1507 assert_eq!(
1508 reception,
1509 Reception::Frame {
1510 len: 3,
1511 status: PacketStatus::from_bytes([0xDB, 0xF6, 0xE0]),
1512 }
1513 );
1514 assert_eq!(&buffer[..3], b"hi!");
1515 assert!(radio.release().0.done());
1516 }
1517
1518 #[test]
1519 fn a_corrupt_frame_or_a_timeout_carries_no_payload() {
1520 let mut radio = configured(listening(0x0042));
1521 let mut buffer = [0u8; 16];
1522 assert_eq!(
1523 radio.receive(&mut buffer, 2_000_000),
1524 Ok(Reception::Corrupt)
1525 );
1526 assert!(radio.release().0.done());
1527
1528 let mut radio = configured(listening(0x0200));
1529 assert_eq!(
1530 radio.receive(&mut buffer, 2_000_000),
1531 Ok(Reception::Timeout)
1532 );
1533 assert!(radio.release().0.done());
1534 }
1535
1536 #[test]
1537 fn a_payload_longer_than_the_buffer_is_refused() {
1538 let mut steps = listening(0x0002);
1539 steps.extend(asked(command::get_rx_buffer_status(), &[0x20, 0x00]));
1540 let mut radio = configured(steps);
1541 let mut buffer = [0u8; 16];
1542 assert_eq!(
1543 radio.receive(&mut buffer, 2_000_000),
1544 Err(RadioError::BufferTooSmall(32))
1545 );
1546 }
1547
1548 #[test]
1549 fn sleep_forgets_the_configuration_and_the_next_command_wakes_the_chip() {
1550 let mut steps = vec![
1551 SpiStep::write([0x80, 0x00]),
1552 SpiStep::write([0x84, 0x04]),
1553 SpiStep::write([0xC0]),
1554 SpiStep::read([0x00]),
1555 ];
1556 steps.extend(asked(command::get_status(), &[0x22]));
1557 let mut radio = configured(steps);
1558
1559 radio.sleep(true).expect("sleeps");
1560 assert_eq!(radio.config(), None);
1561 assert_eq!(
1562 radio.status().expect("wakes").chip_mode,
1563 ChipMode::StandbyRc
1564 );
1565 assert!(radio.release().0.done());
1566 }
1567
1568 #[test]
1569 fn listen_keeps_receiving_and_take_frame_reads_each_frame_as_it_lands() {
1570 let mut steps = vec![
1571 SpiStep::write([0x80, 0x00]),
1572 SpiStep::write([0x8C, 0x00, 0x08, 0x00, 0xFF, 0x01, 0x00]),
1573 ];
1574 steps.extend(register_read(0x0736, 0x0D));
1575 steps.extend(register_write(0x0736, &[0x0D]));
1576 steps.push(SpiStep::write([
1577 0x08, 0x00, 0x62, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00,
1578 ]));
1579 steps.push(SpiStep::write([0x02, 0x43, 0xFF]));
1580 steps.push(SpiStep::write([0x82, 0xFF, 0xFF, 0xFF]));
1581 steps.extend(irq(0x0000));
1582 steps.extend(irq(0x0002));
1583 steps.push(SpiStep::write([0x02, 0x00, 0x62]));
1584 steps.extend(asked(command::get_rx_buffer_status(), &[0x02, 0x00]));
1585 steps.push(SpiStep::write([0x1E, 0x00, 0x00]));
1586 steps.push(SpiStep::read(*b"ok"));
1587 steps.extend(asked(command::get_packet_status(), &[0x80, 0x1C, 0x82]));
1588 let mut radio = configured(steps);
1589
1590 radio.listen().expect("listens");
1591 let mut buffer = [0u8; 255];
1592 assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1593 let frame = radio.take_frame(&mut buffer).expect("takes the frame");
1594 assert!(matches!(frame, Some(Reception::Frame { len: 2, .. })));
1595 assert_eq!(&buffer[..2], b"ok");
1596 assert!(radio.release().0.done());
1597 }
1598}