1use embedded_hal::delay::DelayNs;
10use embedded_hal::digital::{self, OutputPin};
11use embedded_hal::spi::{Operation, SpiDevice};
12use pamoja_lora::budget::Decibels;
13use pamoja_lora::LinkSettings;
14
15use super::config::{
16 self, automatic_if, frequency_bytes, high_bw_optimize, image_cal_start, invert_iq, invert_iq_2,
17 preamble_bytes, spurious_reception, LoraModulation, ModulationError, PaOutput, SyncWord,
18 TxPower,
19};
20use super::irq::IrqFlags;
21use super::register::{self, fsk_op_mode, lora_op_mode, read_address, write_address, Mode};
22use super::status::{rssi_dbm, ModemStatus, PacketStatus, Port};
23
24pub const RESET_HOLD_US: u32 = 1_000;
27
28pub const RESET_SETTLE_US: u32 = 6_000;
31
32pub const IRQ_POLL_US: u32 = 1_000;
35
36pub const TIMEOUT_MARGIN_US: u64 = 1_000_000;
39
40pub const CALIBRATION_POLL_US: u32 = 1_000;
42
43pub const CALIBRATION_LIMIT_US: u32 = 100_000;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
63pub struct Board {
64 pub output: PaOutput,
66 pub tcxo: bool,
68}
69
70impl Board {
71 pub const fn new(output: PaOutput) -> Board {
81 Board {
82 output,
83 tcxo: false,
84 }
85 }
86
87 pub const fn with_tcxo(mut self) -> Board {
93 self.tcxo = true;
94 self
95 }
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub struct RadioConfig {
120 pub frequency_hz: u32,
122 pub link: LinkSettings,
124 pub power: TxPower,
126 pub sync_word: SyncWord,
128 pub invert_iq_transmit: bool,
130 pub invert_iq_receive: bool,
132}
133
134impl RadioConfig {
135 pub const fn new(frequency_hz: u32, link: LinkSettings, power: TxPower) -> RadioConfig {
147 RadioConfig {
148 frequency_hz,
149 link,
150 power,
151 sync_word: SyncWord::Private,
152 invert_iq_transmit: false,
153 invert_iq_receive: false,
154 }
155 }
156
157 pub const fn with_sync_word(mut self, sync_word: SyncWord) -> RadioConfig {
167 self.sync_word = sync_word;
168 self
169 }
170
171 pub const fn with_inverted_iq(mut self, transmit: bool, receive: bool) -> RadioConfig {
182 self.invert_iq_transmit = transmit;
183 self.invert_iq_receive = receive;
184 self
185 }
186
187 pub const fn lorawan_device(self) -> RadioConfig {
194 self.with_sync_word(SyncWord::Public)
195 .with_inverted_iq(false, true)
196 }
197}
198
199#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
201pub enum Reception {
202 Frame {
204 len: usize,
206 status: PacketStatus,
208 },
209 Timeout,
211 Corrupt,
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq)]
217pub enum RadioError<E> {
218 Spi(E),
220 Pin(digital::ErrorKind),
222 Absent(u8),
225 Modulation(ModulationError),
227 Output,
229 PayloadLength(usize),
231 BufferTooSmall(usize),
233 NotConfigured,
235 Calibration,
237 NoInterrupt,
239}
240
241impl<E: core::fmt::Debug> core::fmt::Display for RadioError<E> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 match self {
244 RadioError::Spi(error) => write!(f, "SPI error: {error:?}"),
245 RadioError::Pin(kind) => write!(f, "NRESET line error: {kind:?}"),
246 RadioError::Absent(version) => {
247 write!(f, "no SX127x answered: RegVersion read {version:#04x}")
248 }
249 RadioError::Modulation(ModulationError::Bandwidth(hz)) => {
250 write!(
251 f,
252 "the SX127x has no {hz} Hz LoRa bandwidth at this carrier"
253 )
254 }
255 RadioError::Modulation(ModulationError::SpreadingFactor(sf)) => {
256 write!(f, "the SX127x has no SF{sf}")
257 }
258 RadioError::Modulation(ModulationError::ExplicitHeaderAtSf6) => {
259 write!(f, "SF6 needs an implicit header")
260 }
261 RadioError::Output => {
262 write!(f, "the power settings are for the other amplifier output")
263 }
264 RadioError::PayloadLength(len) => {
265 write!(f, "a LoRa frame carries 1 to 255 bytes, not {len}")
266 }
267 RadioError::BufferTooSmall(len) => {
268 write!(f, "a {len} byte payload does not fit the buffer")
269 }
270 RadioError::NotConfigured => write!(f, "the radio has not been configured"),
271 RadioError::Calibration => write!(f, "the image calibration did not finish"),
272 RadioError::NoInterrupt => write!(f, "the radio raised no interrupt in time"),
273 }
274 }
275}
276
277impl<E: core::fmt::Debug> core::error::Error for RadioError<E> {}
278
279fn pin<E, P: digital::Error>(error: P) -> RadioError<E> {
280 RadioError::Pin(error.kind())
281}
282
283pub struct Sx127x<SPI, RESET, D> {
329 spi: SPI,
330 reset: RESET,
331 delay: D,
332 board: Board,
333 config: Option<RadioConfig>,
334 tuned_hz: Option<u32>,
335 calibrated_high: bool,
336 calibrated_low: bool,
337}
338
339impl<SPI, RESET, D> Sx127x<SPI, RESET, D> {
340 pub fn new(spi: SPI, reset: RESET, delay: D, board: Board) -> Self {
354 Sx127x {
355 spi,
356 reset,
357 delay,
358 board,
359 config: None,
360 tuned_hz: None,
361 calibrated_high: false,
362 calibrated_low: false,
363 }
364 }
365
366 pub fn board(&self) -> Board {
372 self.board
373 }
374
375 pub fn config(&self) -> Option<&RadioConfig> {
382 self.config.as_ref()
383 }
384
385 pub fn tx_power(&self, output_dbm: i8) -> TxPower {
395 TxPower::for_output(self.board.output, output_dbm)
396 }
397
398 pub fn release(self) -> (SPI, RESET, D) {
404 (self.spi, self.reset, self.delay)
405 }
406}
407
408impl<SPI, RESET, D> Sx127x<SPI, RESET, D>
409where
410 SPI: SpiDevice,
411 RESET: OutputPin,
412 D: DelayNs,
413{
414 pub fn init(&mut self) -> Result<(), RadioError<SPI::Error>> {
426 self.config = None;
427 self.tuned_hz = None;
428 self.calibrated_high = false;
429 self.calibrated_low = false;
430 self.reset.set_low().map_err(pin)?;
431 self.delay.delay_us(RESET_HOLD_US);
432 self.reset.set_high().map_err(pin)?;
433 self.delay.delay_us(RESET_SETTLE_US);
434
435 let version = self.version()?;
436 if version != register::VERSION_SX1276 {
437 return Err(RadioError::Absent(version));
438 }
439 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Sleep))?;
440 if self.board.tcxo {
441 self.write_register(register::TCXO, config::TCXO_INPUT_ON)?;
442 }
443 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))?;
444 self.write_register(register::OP_MODE, lora_op_mode(Mode::Standby))?;
445 self.write_register(register::LNA, config::LNA_BOOSTED)
446 }
447
448 pub fn configure(&mut self, config: RadioConfig) -> Result<(), RadioError<SPI::Error>> {
467 let modulation = LoraModulation::from_link(&config.link).map_err(RadioError::Modulation)?;
468 if !modulation.bandwidth.in_band(config.frequency_hz) {
469 return Err(RadioError::Modulation(ModulationError::Bandwidth(
470 config.link.bandwidth_hz(),
471 )));
472 }
473 if config.power.output() != self.board.output {
474 return Err(RadioError::Output);
475 }
476 let calibrated = match Port::for_frequency(config.frequency_hz) {
477 Port::High => self.calibrated_high,
478 Port::Low => self.calibrated_low,
479 };
480 if calibrated {
481 self.standby()?;
482 } else {
483 self.calibrate(config.frequency_hz)?;
484 }
485 self.tune(config.frequency_hz)?;
486 self.write_register(register::PA_CONFIG, config.power.pa_config)?;
487 self.write_register(register::PA_DAC, config.power.pa_dac)?;
488 self.write_register(register::OCP, config.power.ocp)?;
489 self.write_register(register::MODEM_CONFIG_1, modulation.modem_config_1())?;
490 self.write_register(register::MODEM_CONFIG_2, modulation.modem_config_2(0))?;
491 self.write_register(register::MODEM_CONFIG_3, modulation.modem_config_3())?;
492 self.write_registers(register::PREAMBLE_MSB, &preamble_bytes(&config.link))?;
493 self.update_register(register::DETECT_OPTIMIZE, |value| {
494 modulation.detect_optimize(value)
495 })?;
496 self.write_register(
497 register::DETECTION_THRESHOLD,
498 modulation.detection_threshold(),
499 )?;
500 let (optimize_1, optimize_2) = high_bw_optimize(modulation.bandwidth, config.frequency_hz);
501 self.write_register(register::HIGH_BW_OPTIMIZE_1, optimize_1)?;
502 if let Some(optimize_2) = optimize_2 {
503 self.write_register(register::HIGH_BW_OPTIMIZE_2, optimize_2)?;
504 }
505 self.write_register(register::MAX_PAYLOAD_LENGTH, 0xFF)?;
506 self.write_register(register::SYNC_WORD, config.sync_word.to_byte())?;
507 self.config = Some(config);
508 Ok(())
509 }
510
511 pub fn transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
532 let airtime_us = self.start_transmit(payload)?;
533 let limit_us = airtime_us.saturating_add(2 * TIMEOUT_MARGIN_US);
534 let mut waited_us = airtime_us;
535 self.pause_us(airtime_us);
536 while !self.finish_transmit()? {
537 if waited_us >= limit_us {
538 return Err(RadioError::NoInterrupt);
539 }
540 self.delay.delay_us(IRQ_POLL_US);
541 waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
542 }
543 Ok(airtime_us)
544 }
545
546 pub fn start_transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
567 let settings = self.config.ok_or(RadioError::NotConfigured)?;
568 let len = u8::try_from(payload.len())
569 .ok()
570 .filter(|len| *len > 0)
571 .ok_or(RadioError::PayloadLength(payload.len()))?;
572 let invert = settings.invert_iq_transmit;
573
574 self.standby()?;
575 self.write_register(register::INVERT_IQ, invert_iq(false, invert))?;
576 self.write_register(register::INVERT_IQ_2, invert_iq_2(invert))?;
577 self.tune(settings.frequency_hz)?;
578 self.write_register(register::PAYLOAD_LENGTH, len)?;
579 self.write_register(register::FIFO_TX_BASE_ADDR, 0)?;
580 self.write_register(register::FIFO_ADDR_PTR, 0)?;
581 self.write_registers(register::FIFO, payload)?;
582 self.write_register(register::DIO_MAPPING_1, config::DIO0_TX_DONE)?;
583 self.write_register(register::IRQ_FLAGS, IrqFlags::ALL.bits())?;
584 self.write_register(register::OP_MODE, lora_op_mode(Mode::Tx))?;
585 Ok(settings.link.airtime_us(payload.len()))
586 }
587
588 pub fn finish_transmit(&mut self) -> Result<bool, RadioError<SPI::Error>> {
600 let flags = self.irq_flags()?;
601 if !flags.contains(IrqFlags::TX_DONE) {
602 return Ok(false);
603 }
604 self.write_register(register::IRQ_FLAGS, IrqFlags::TX_DONE.bits())?;
605 Ok(true)
606 }
607
608 pub fn receive(
631 &mut self,
632 buffer: &mut [u8],
633 timeout_us: u64,
634 ) -> Result<Reception, RadioError<SPI::Error>> {
635 let settings = self.config.ok_or(RadioError::NotConfigured)?;
636 let modulation =
637 LoraModulation::from_link(&settings.link).map_err(RadioError::Modulation)?;
638 self.prepare_reception(&settings, &modulation)?;
639 let symbols = config::symbol_timeout(&settings.link, timeout_us);
640 self.write_register(register::MODEM_CONFIG_2, modulation.modem_config_2(symbols))?;
641 self.write_register(register::SYMB_TIMEOUT_LSB, symbols as u8)?;
642 self.write_register(register::OP_MODE, lora_op_mode(Mode::RxSingle))?;
643
644 let window_us = u64::from(symbols) * settings.link.symbol_time_us();
645 let limit_us = window_us
646 .saturating_add(settings.link.airtime_us(usize::from(u8::MAX)))
647 .saturating_add(TIMEOUT_MARGIN_US);
648 let flags = self.wait_for(IrqFlags::RX_DONE | IrqFlags::RX_TIMEOUT, limit_us)?;
649 self.write_register(register::IRQ_FLAGS, flags.bits())?;
650 if !flags.contains(IrqFlags::RX_DONE) {
651 return Ok(Reception::Timeout);
652 }
653 if flags.contains(IrqFlags::PAYLOAD_CRC_ERROR) {
654 return Ok(Reception::Corrupt);
655 }
656 self.read_frame(buffer, &settings)
657 }
658
659 pub fn detect(&mut self) -> Result<bool, RadioError<SPI::Error>> {
678 let settings = self.config.ok_or(RadioError::NotConfigured)?;
679 let modulation =
680 LoraModulation::from_link(&settings.link).map_err(RadioError::Modulation)?;
681 self.prepare_reception(&settings, &modulation)?;
682 self.write_register(register::DIO_MAPPING_1, config::DIO0_CAD_DONE)?;
683 self.write_register(register::OP_MODE, lora_op_mode(Mode::Cad))?;
684
685 let limit_us = settings
688 .link
689 .symbol_time_us()
690 .saturating_mul(4)
691 .saturating_add(TIMEOUT_MARGIN_US);
692 let flags = self.wait_for(IrqFlags::CAD_DONE, limit_us)?;
693 self.write_register(register::IRQ_FLAGS, IrqFlags::ALL.bits())?;
694 Ok(flags.contains(IrqFlags::CAD_DETECTED))
695 }
696
697 pub fn listen(&mut self) -> Result<(), RadioError<SPI::Error>> {
710 let settings = self.config.ok_or(RadioError::NotConfigured)?;
711 let modulation =
712 LoraModulation::from_link(&settings.link).map_err(RadioError::Modulation)?;
713 self.prepare_reception(&settings, &modulation)?;
714 self.write_register(register::OP_MODE, lora_op_mode(Mode::RxContinuous))
715 }
716
717 pub fn take_frame(
737 &mut self,
738 buffer: &mut [u8],
739 ) -> Result<Option<Reception>, RadioError<SPI::Error>> {
740 let settings = self.config.ok_or(RadioError::NotConfigured)?;
741 let flags = self.irq_flags()?;
742 if !flags.contains(IrqFlags::RX_DONE) {
743 return Ok(None);
744 }
745 let received = IrqFlags::RX_DONE | IrqFlags::PAYLOAD_CRC_ERROR | IrqFlags::VALID_HEADER;
746 self.write_register(register::IRQ_FLAGS, (flags & received).bits())?;
747 if flags.contains(IrqFlags::PAYLOAD_CRC_ERROR) {
748 return Ok(Some(Reception::Corrupt));
749 }
750 self.read_frame(buffer, &settings).map(Some)
751 }
752
753 pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
759 self.write_register(register::OP_MODE, lora_op_mode(Mode::Standby))
760 }
761
762 pub fn random(&mut self) -> Result<u32, RadioError<SPI::Error>> {
781 let mask = self.read_register(register::IRQ_FLAGS_MASK)?;
782 self.write_register(register::IRQ_FLAGS_MASK, 0xFF)?;
783 self.write_register(register::OP_MODE, lora_op_mode(Mode::RxContinuous))?;
784 let mut number = 0u32;
785 for bit in 0..32 {
786 self.delay.delay_ms(1);
787 let sample = self.read_register(register::RSSI_WIDEBAND)?;
788 number |= u32::from(sample & 0x01) << bit;
789 }
790 self.write_register(register::OP_MODE, lora_op_mode(Mode::Standby))?;
791 self.write_register(register::IRQ_FLAGS_MASK, mask)?;
792 Ok(number)
793 }
794
795 pub fn sleep(&mut self) -> Result<(), RadioError<SPI::Error>> {
804 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))
805 }
806
807 pub fn version(&mut self) -> Result<u8, RadioError<SPI::Error>> {
817 self.read_register(register::VERSION)
818 }
819
820 pub fn irq_flags(&mut self) -> Result<IrqFlags, RadioError<SPI::Error>> {
830 self.read_register(register::IRQ_FLAGS)
831 .map(IrqFlags::from_bits)
832 }
833
834 pub fn modem_status(&mut self) -> Result<ModemStatus, RadioError<SPI::Error>> {
844 self.read_register(register::MODEM_STAT)
845 .map(ModemStatus::from_byte)
846 }
847
848 pub fn rssi(&mut self) -> Result<Decibels, RadioError<SPI::Error>> {
859 let settings = self.config.ok_or(RadioError::NotConfigured)?;
860 let byte = self.read_register(register::RSSI_VALUE)?;
861 Ok(rssi_dbm(byte, Port::for_frequency(settings.frequency_hz)))
862 }
863
864 pub fn read_register(&mut self, address: u8) -> Result<u8, RadioError<SPI::Error>> {
878 let mut value = [0u8; 1];
879 self.read_registers(address, &mut value)?;
880 Ok(value[0])
881 }
882
883 pub fn write_register(&mut self, address: u8, value: u8) -> Result<(), RadioError<SPI::Error>> {
894 self.write_registers(address, &[value])
895 }
896
897 pub fn read_registers(
909 &mut self,
910 address: u8,
911 values: &mut [u8],
912 ) -> Result<(), RadioError<SPI::Error>> {
913 self.spi
914 .transaction(&mut [
915 Operation::Write(&[read_address(address)]),
916 Operation::Read(values),
917 ])
918 .map_err(RadioError::Spi)
919 }
920
921 pub fn write_registers(
933 &mut self,
934 address: u8,
935 values: &[u8],
936 ) -> Result<(), RadioError<SPI::Error>> {
937 self.spi
938 .transaction(&mut [
939 Operation::Write(&[write_address(address)]),
940 Operation::Write(values),
941 ])
942 .map_err(RadioError::Spi)
943 }
944
945 fn calibrate(&mut self, frequency_hz: u32) -> Result<(), RadioError<SPI::Error>> {
946 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))?;
947 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Sleep))?;
948 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Standby))?;
949 self.write_register(register::PA_CONFIG, 0x00)?;
950 self.write_registers(register::FRF_MSB, &frequency_bytes(frequency_hz))?;
951 self.tuned_hz = Some(frequency_hz);
952 self.update_register(register::IMAGE_CAL, image_cal_start)?;
953 let mut waited_us = 0u32;
954 while self.read_register(register::IMAGE_CAL)? & config::IMAGE_CAL_RUNNING != 0 {
955 if waited_us >= CALIBRATION_LIMIT_US {
956 return Err(RadioError::Calibration);
957 }
958 self.delay.delay_us(CALIBRATION_POLL_US);
959 waited_us = waited_us.saturating_add(CALIBRATION_POLL_US);
960 }
961 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Sleep))?;
962 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))?;
963 self.standby()?;
964 match Port::for_frequency(frequency_hz) {
965 Port::High => self.calibrated_high = true,
966 Port::Low => self.calibrated_low = true,
967 }
968 Ok(())
969 }
970
971 fn tune(&mut self, frequency_hz: u32) -> Result<(), RadioError<SPI::Error>> {
972 if self.tuned_hz != Some(frequency_hz) {
973 self.write_registers(register::FRF_MSB, &frequency_bytes(frequency_hz))?;
974 self.tuned_hz = Some(frequency_hz);
975 }
976 Ok(())
977 }
978
979 fn prepare_reception(
980 &mut self,
981 settings: &RadioConfig,
982 modulation: &LoraModulation,
983 ) -> Result<(), RadioError<SPI::Error>> {
984 let invert = settings.invert_iq_receive;
985 self.standby()?;
986 self.write_register(register::INVERT_IQ, invert_iq(invert, false))?;
987 self.write_register(register::INVERT_IQ_2, invert_iq_2(invert))?;
988 let erratum = spurious_reception(modulation.bandwidth);
989 self.update_register(register::DETECT_OPTIMIZE, |value| {
990 automatic_if(value, erratum.automatic_if)
991 })?;
992 if let Some(if_freq_2) = erratum.if_freq_2 {
993 self.write_register(register::IF_FREQ_1, 0x00)?;
994 self.write_register(register::IF_FREQ_2, if_freq_2)?;
995 }
996 self.tune(settings.frequency_hz.saturating_add(erratum.offset_hz))?;
997 self.write_register(register::FIFO_RX_BASE_ADDR, 0)?;
998 self.write_register(register::FIFO_ADDR_PTR, 0)?;
999 self.write_register(register::DIO_MAPPING_1, config::DIO0_RX_DONE)?;
1000 self.write_register(register::IRQ_FLAGS, IrqFlags::ALL.bits())
1001 }
1002
1003 fn read_frame(
1004 &mut self,
1005 buffer: &mut [u8],
1006 settings: &RadioConfig,
1007 ) -> Result<Reception, RadioError<SPI::Error>> {
1008 let len = usize::from(self.read_register(register::RX_NB_BYTES)?);
1009 let frame = buffer
1010 .get_mut(..len)
1011 .ok_or(RadioError::BufferTooSmall(len))?;
1012 let start = self.read_register(register::FIFO_RX_CURRENT_ADDR)?;
1013 self.write_register(register::FIFO_ADDR_PTR, start)?;
1014 if len > 0 {
1015 self.read_registers(register::FIFO, frame)?;
1016 }
1017 let mut levels = [0u8; 2];
1018 self.read_registers(register::PKT_SNR_VALUE, &mut levels)?;
1019 Ok(Reception::Frame {
1020 len,
1021 status: PacketStatus::from_bytes(levels, Port::for_frequency(settings.frequency_hz)),
1022 })
1023 }
1024
1025 fn update_register(
1026 &mut self,
1027 address: u8,
1028 change: impl FnOnce(u8) -> u8,
1029 ) -> Result<(), RadioError<SPI::Error>> {
1030 let value = self.read_register(address)?;
1031 self.write_register(address, change(value))
1032 }
1033
1034 fn wait_for(
1035 &mut self,
1036 events: IrqFlags,
1037 limit_us: u64,
1038 ) -> Result<IrqFlags, RadioError<SPI::Error>> {
1039 let mut waited_us = 0u64;
1040 loop {
1041 let flags = self.irq_flags()?;
1042 if flags.intersects(events) {
1043 return Ok(flags);
1044 }
1045 if waited_us >= limit_us {
1046 return Err(RadioError::NoInterrupt);
1047 }
1048 self.delay.delay_us(IRQ_POLL_US);
1049 waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
1050 }
1051 }
1052
1053 fn pause_us(&mut self, micros: u64) {
1054 let mut left = micros;
1055 while left > 0 {
1056 let step = u32::try_from(left).unwrap_or(u32::MAX);
1057 self.delay.delay_us(step);
1058 left -= u64::from(step);
1059 }
1060 }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065 use super::*;
1066 use pamoja_hal::digital::PinState;
1067 use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
1068
1069 type Radio = Sx127x<SpiScript, PinScript, DelayLog>;
1070
1071 fn radio(steps: Vec<SpiStep>) -> Radio {
1072 Sx127x::new(
1073 SpiScript::new(steps),
1074 PinScript::new([]),
1075 DelayLog::new(),
1076 Board::new(PaOutput::PaBoost),
1077 )
1078 }
1079
1080 fn wrote(address: u8, values: &[u8]) -> [SpiStep; 2] {
1081 [
1082 SpiStep::write([address | 0x80]),
1083 SpiStep::write(values.to_vec()),
1084 ]
1085 }
1086
1087 fn read(address: u8, values: &[u8]) -> [SpiStep; 2] {
1088 [SpiStep::write([address]), SpiStep::read(values.to_vec())]
1089 }
1090
1091 #[test]
1092 fn a_random_number_is_the_low_bit_of_32_wideband_samples() {
1093 let mut steps = Vec::new();
1094 steps.extend(read(0x11, &[0x00]));
1095 steps.extend(wrote(0x11, &[0xFF]));
1096 steps.extend(wrote(0x01, &[0x8D]));
1097 let samples: [u8; 32] = core::array::from_fn(|bit| if bit % 3 == 0 { 0x6B } else { 0x6A });
1098 for sample in samples {
1099 steps.extend(read(0x2C, &[sample]));
1100 }
1101 steps.extend(wrote(0x01, &[0x89]));
1102 steps.extend(wrote(0x11, &[0x00]));
1103
1104 let mut radio = radio(steps);
1105 let expected = (0..32).fold(0u32, |number, bit| {
1106 number | (u32::from(bit % 3 == 0) << bit)
1107 });
1108 assert_eq!(radio.random(), Ok(expected));
1109 let (spi, _, delay) = radio.release();
1110 assert!(spi.done(), "{} steps left", spi.remaining());
1111 assert_eq!(
1112 delay.total_ns(),
1113 32 * 1_000_000,
1114 "a millisecond before each sample"
1115 );
1116 }
1117
1118 fn eu868() -> RadioConfig {
1119 RadioConfig::new(
1120 868_100_000,
1121 LinkSettings::new(7, 125_000),
1122 TxPower::for_output(PaOutput::PaBoost, 14),
1123 )
1124 .with_sync_word(SyncWord::Public)
1125 }
1126
1127 fn calibration(frequency: [u8; 3]) -> Vec<SpiStep> {
1128 let mut steps = Vec::new();
1129 steps.extend(wrote(0x01, &[0x88]));
1130 steps.extend(wrote(0x01, &[0x08]));
1131 steps.extend(wrote(0x01, &[0x09]));
1132 steps.extend(wrote(0x09, &[0x00]));
1133 steps.extend(wrote(0x06, &frequency));
1134 steps.extend(read(0x3B, &[0x82]));
1135 steps.extend(wrote(0x3B, &[0x42]));
1136 steps.extend(read(0x3B, &[0x22]));
1137 steps.extend(read(0x3B, &[0x02]));
1138 steps.extend(wrote(0x01, &[0x08]));
1139 steps.extend(wrote(0x01, &[0x88]));
1140 steps.extend(wrote(0x01, &[0x89]));
1141 steps
1142 }
1143
1144 fn configuration() -> Vec<SpiStep> {
1145 let mut steps = calibration([0xD9, 0x06, 0x66]);
1146 steps.extend(wrote(0x09, &[0xFC]));
1147 steps.extend(wrote(0x4D, &[0x84]));
1148 steps.extend(wrote(0x0B, &[0x2B]));
1149 steps.extend(wrote(0x1D, &[0x72]));
1150 steps.extend(wrote(0x1E, &[0x74]));
1151 steps.extend(wrote(0x26, &[0x04]));
1152 steps.extend(wrote(0x20, &[0x00, 0x08]));
1153 steps.extend(read(0x31, &[0xC3]));
1154 steps.extend(wrote(0x31, &[0xC3]));
1155 steps.extend(wrote(0x37, &[0x0A]));
1156 steps.extend(wrote(0x36, &[0x03]));
1157 steps.extend(wrote(0x23, &[0xFF]));
1158 steps.extend(wrote(0x39, &[0x34]));
1159 steps
1160 }
1161
1162 fn configured(more: Vec<SpiStep>) -> Radio {
1163 let mut steps = configuration();
1164 steps.extend(more);
1165 let mut radio = radio(steps);
1166 radio.configure(eu868()).expect("configures");
1167 radio
1168 }
1169
1170 fn reception_setup() -> Vec<SpiStep> {
1171 let mut steps = Vec::new();
1172 steps.extend(wrote(0x01, &[0x89]));
1173 steps.extend(wrote(0x33, &[0x27]));
1174 steps.extend(wrote(0x3B, &[0x1D]));
1175 steps.extend(read(0x31, &[0xC3]));
1176 steps.extend(wrote(0x31, &[0x43]));
1177 steps.extend(wrote(0x30, &[0x00]));
1178 steps.extend(wrote(0x2F, &[0x40]));
1179 steps.extend(wrote(0x0F, &[0x00]));
1180 steps.extend(wrote(0x0D, &[0x00]));
1181 steps.extend(wrote(0x40, &[0x00]));
1182 steps.extend(wrote(0x12, &[0xFF]));
1183 steps
1184 }
1185
1186 #[test]
1187 fn init_resets_checks_the_version_and_enters_lora_mode_with_a_tcxo() {
1188 let mut steps = Vec::new();
1189 steps.extend(read(0x42, &[0x12]));
1190 steps.extend(wrote(0x01, &[0x08]));
1191 steps.extend(wrote(0x4B, &[0x19]));
1192 steps.extend(wrote(0x01, &[0x88]));
1193 steps.extend(wrote(0x01, &[0x89]));
1194 steps.extend(wrote(0x0C, &[0x23]));
1195 let board = Board::new(PaOutput::PaBoost).with_tcxo();
1196 let mut radio = Sx127x::new(
1197 SpiScript::new(steps),
1198 PinScript::new([]),
1199 DelayLog::new(),
1200 board,
1201 );
1202
1203 radio.init().expect("initializes");
1204
1205 let (spi, reset, delay) = radio.release();
1206 assert!(spi.done(), "{} steps left", spi.remaining());
1207 assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
1208 assert_eq!(delay.waits_ns(), [1_000_000, 6_000_000]);
1209 }
1210
1211 #[test]
1212 fn init_refuses_a_chip_that_is_not_an_sx1276() {
1213 let mut radio = radio(read(0x42, &[0x22]).to_vec());
1214 assert_eq!(radio.init(), Err(RadioError::Absent(0x22)));
1215 }
1216
1217 #[test]
1218 fn configure_calibrates_the_port_once_then_writes_the_modem() {
1219 let mut steps = configuration();
1220 steps.extend(wrote(0x01, &[0x89]));
1221 steps.extend(wrote(0x06, &[0xD9, 0x13, 0x33]));
1222 steps.extend(wrote(0x09, &[0xFC]));
1223 steps.extend(wrote(0x4D, &[0x84]));
1224 steps.extend(wrote(0x0B, &[0x2B]));
1225 steps.extend(wrote(0x1D, &[0x72]));
1226 steps.extend(wrote(0x1E, &[0x74]));
1227 steps.extend(wrote(0x26, &[0x04]));
1228 steps.extend(wrote(0x20, &[0x00, 0x08]));
1229 steps.extend(read(0x31, &[0xC3]));
1230 steps.extend(wrote(0x31, &[0xC3]));
1231 steps.extend(wrote(0x37, &[0x0A]));
1232 steps.extend(wrote(0x36, &[0x03]));
1233 steps.extend(wrote(0x23, &[0xFF]));
1234 steps.extend(wrote(0x39, &[0x34]));
1235 let mut radio = radio(steps);
1236
1237 radio.configure(eu868()).expect("configures on 868.1 MHz");
1238 let mut next = eu868();
1239 next.frequency_hz = 868_300_000;
1240 radio.configure(next).expect("configures on 868.3 MHz");
1241
1242 let (spi, _, delay) = radio.release();
1243 assert!(spi.done(), "{} steps left", spi.remaining());
1244 assert_eq!(delay.waits_ns(), [1_000_000], "one poll while calibrating");
1245 }
1246
1247 #[test]
1248 fn configure_refuses_settings_the_chip_cannot_use() {
1249 let mut radio = radio(Vec::new());
1250 let sf5 = RadioConfig::new(
1251 868_100_000,
1252 LinkSettings::new(5, 125_000),
1253 TxPower::for_output(PaOutput::PaBoost, 14),
1254 );
1255 assert_eq!(
1256 radio.configure(sf5),
1257 Err(RadioError::Modulation(ModulationError::SpreadingFactor(5)))
1258 );
1259 let wide_at_169 = RadioConfig::new(
1260 169_400_000,
1261 LinkSettings::new(7, 500_000),
1262 TxPower::for_output(PaOutput::PaBoost, 14),
1263 );
1264 assert_eq!(
1265 radio.configure(wide_at_169),
1266 Err(RadioError::Modulation(ModulationError::Bandwidth(500_000)))
1267 );
1268 let rfo = RadioConfig::new(
1269 868_100_000,
1270 LinkSettings::new(7, 125_000),
1271 TxPower::for_output(PaOutput::Rfo, 14),
1272 );
1273 assert_eq!(radio.configure(rfo), Err(RadioError::Output));
1274 let (spi, _, _) = radio.release();
1275 assert_eq!(spi.consumed(), 0, "nothing reaches the bus");
1276 }
1277
1278 #[test]
1279 fn a_frame_goes_out_through_the_fifo_and_tx_mode() {
1280 let mut steps = Vec::new();
1281 steps.extend(wrote(0x01, &[0x89]));
1282 steps.extend(wrote(0x33, &[0x27]));
1283 steps.extend(wrote(0x3B, &[0x1D]));
1284 steps.extend(wrote(0x22, &[0x05]));
1285 steps.extend(wrote(0x0E, &[0x00]));
1286 steps.extend(wrote(0x0D, &[0x00]));
1287 steps.extend(wrote(0x00, b"level"));
1288 steps.extend(wrote(0x40, &[0x40]));
1289 steps.extend(wrote(0x12, &[0xFF]));
1290 steps.extend(wrote(0x01, &[0x8B]));
1291 steps.extend(read(0x12, &[0x00]));
1292 steps.extend(read(0x12, &[0x08]));
1293 steps.extend(wrote(0x12, &[0x08]));
1294 let mut radio = configured(steps);
1295
1296 let airtime = radio.transmit(b"level").expect("sends");
1297
1298 assert_eq!(airtime, LinkSettings::new(7, 125_000).airtime_us(5));
1299 let (spi, _, _) = radio.release();
1300 assert!(spi.done(), "{} steps left", spi.remaining());
1301 }
1302
1303 #[test]
1304 fn a_transmission_needs_a_configuration_and_a_payload_that_fits() {
1305 let mut unconfigured = radio(Vec::new());
1306 assert_eq!(
1307 unconfigured.start_transmit(b"x"),
1308 Err(RadioError::NotConfigured)
1309 );
1310 let mut radio = configured(Vec::new());
1311 assert_eq!(radio.start_transmit(&[]), Err(RadioError::PayloadLength(0)));
1312 assert_eq!(
1313 radio.start_transmit(&[0; 256]),
1314 Err(RadioError::PayloadLength(256))
1315 );
1316 }
1317
1318 #[test]
1319 fn detection_reports_whether_a_preamble_was_in_the_symbol_it_listened_over() {
1320 let scan = |flags: u8| {
1321 let mut steps = reception_setup();
1322 steps.extend(wrote(0x40, &[0x80]));
1323 steps.extend(wrote(0x01, &[0x8F]));
1324 steps.extend(read(0x12, &[flags]));
1325 steps.extend(wrote(0x12, &[0xFF]));
1326 steps
1327 };
1328
1329 let mut heard = configured(scan(0x05));
1330 assert_eq!(heard.detect(), Ok(true), "CadDone with CadDetected");
1331 assert!(heard.release().0.done());
1332
1333 let mut quiet = configured(scan(0x04));
1334 assert_eq!(quiet.detect(), Ok(false), "CadDone alone");
1335 assert!(quiet.release().0.done());
1336
1337 let mut bare = radio(Vec::new());
1338 assert_eq!(bare.detect(), Err(RadioError::NotConfigured));
1339 }
1340
1341 #[test]
1342 fn listening_applies_the_spurious_reception_erratum_and_takes_frames() {
1343 let mut steps = reception_setup();
1344 steps.extend(wrote(0x01, &[0x8D]));
1345 steps.extend(read(0x12, &[0x00]));
1346 steps.extend(read(0x12, &[0x50]));
1347 steps.extend(wrote(0x12, &[0x50]));
1348 steps.extend(read(0x13, &[0x02]));
1349 steps.extend(read(0x10, &[0x00]));
1350 steps.extend(wrote(0x0D, &[0x00]));
1351 steps.extend(read(0x00, b"hi"));
1352 steps.extend(read(0x19, &[0xF6, 0x30]));
1353 steps.extend(read(0x12, &[0x60]));
1354 steps.extend(wrote(0x12, &[0x60]));
1355 let mut radio = configured(steps);
1356 let mut buffer = [0u8; 255];
1357
1358 radio.listen().expect("listens");
1359 assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1360 let frame = radio.take_frame(&mut buffer).expect("reads the frame");
1361 assert_eq!(
1362 frame,
1363 Some(Reception::Frame {
1364 len: 2,
1365 status: PacketStatus::from_bytes([0xF6, 0x30], Port::High),
1366 })
1367 );
1368 assert_eq!(&buffer[..2], b"hi");
1369 assert_eq!(radio.take_frame(&mut buffer), Ok(Some(Reception::Corrupt)));
1370
1371 let (spi, _, _) = radio.release();
1372 assert!(spi.done(), "{} steps left", spi.remaining());
1373 }
1374
1375 #[test]
1376 fn a_narrow_bandwidth_listens_one_bandwidth_above_the_carrier() {
1377 let narrow = RadioConfig::new(
1378 433_175_000,
1379 LinkSettings::new(9, 20_833),
1380 TxPower::for_output(PaOutput::PaBoost, 10),
1381 );
1382 let mut steps = calibration([0x6C, 0x4B, 0x33]);
1383 steps.extend(wrote(0x09, &[0xF8]));
1384 steps.extend(wrote(0x4D, &[0x84]));
1385 steps.extend(wrote(0x0B, &[0x2B]));
1386 steps.extend(wrote(0x1D, &[0x32]));
1387 steps.extend(wrote(0x1E, &[0x94]));
1388 steps.extend(wrote(0x26, &[0x0C]));
1389 steps.extend(wrote(0x20, &[0x00, 0x08]));
1390 steps.extend(read(0x31, &[0xC3]));
1391 steps.extend(wrote(0x31, &[0xC3]));
1392 steps.extend(wrote(0x37, &[0x0A]));
1393 steps.extend(wrote(0x36, &[0x03]));
1394 steps.extend(wrote(0x23, &[0xFF]));
1395 steps.extend(wrote(0x39, &[0x12]));
1396 steps.extend(wrote(0x01, &[0x89]));
1397 steps.extend(wrote(0x33, &[0x27]));
1398 steps.extend(wrote(0x3B, &[0x1D]));
1399 steps.extend(read(0x31, &[0xC3]));
1400 steps.extend(wrote(0x31, &[0x43]));
1401 steps.extend(wrote(0x30, &[0x00]));
1402 steps.extend(wrote(0x2F, &[0x44]));
1403 steps.extend(wrote(0x06, &frequency_bytes(433_175_000 + 20_830)));
1404 steps.extend(wrote(0x0F, &[0x00]));
1405 steps.extend(wrote(0x0D, &[0x00]));
1406 steps.extend(wrote(0x40, &[0x00]));
1407 steps.extend(wrote(0x12, &[0xFF]));
1408 steps.extend(wrote(0x01, &[0x8D]));
1409 let mut radio = radio(steps);
1410
1411 radio.configure(narrow).expect("configures");
1412 radio.listen().expect("listens");
1413
1414 let (spi, _, _) = radio.release();
1415 assert!(spi.done(), "{} steps left", spi.remaining());
1416 }
1417
1418 #[test]
1419 fn a_single_reception_counts_its_timeout_in_symbols_and_reports_it() {
1420 let mut steps = reception_setup();
1421 steps.extend(wrote(0x1E, &[0x74]));
1422 steps.extend(wrote(0x1F, &[0x62]));
1423 steps.extend(wrote(0x01, &[0x8E]));
1424 steps.extend(read(0x12, &[0x80]));
1425 steps.extend(wrote(0x12, &[0x80]));
1426 let mut radio = configured(steps);
1427 let mut buffer = [0u8; 64];
1428
1429 let outcome = radio.receive(&mut buffer, 100_000).expect("listens");
1430
1431 assert_eq!(outcome, Reception::Timeout);
1432 let (spi, _, _) = radio.release();
1433 assert!(spi.done(), "{} steps left", spi.remaining());
1434 }
1435
1436 #[test]
1437 fn a_frame_longer_than_the_buffer_is_refused() {
1438 let mut steps = reception_setup();
1439 steps.extend(wrote(0x01, &[0x8D]));
1440 steps.extend(read(0x12, &[0x40]));
1441 steps.extend(wrote(0x12, &[0x40]));
1442 steps.extend(read(0x13, &[0x10]));
1443 let mut radio = configured(steps);
1444 let mut buffer = [0u8; 8];
1445
1446 radio.listen().expect("listens");
1447 assert_eq!(
1448 radio.take_frame(&mut buffer),
1449 Err(RadioError::BufferTooSmall(16))
1450 );
1451 }
1452
1453 #[test]
1454 fn a_calibration_that_never_finishes_is_an_error() {
1455 let mut steps = Vec::new();
1456 steps.extend(wrote(0x01, &[0x88]));
1457 steps.extend(wrote(0x01, &[0x08]));
1458 steps.extend(wrote(0x01, &[0x09]));
1459 steps.extend(wrote(0x09, &[0x00]));
1460 steps.extend(wrote(0x06, &[0xD9, 0x06, 0x66]));
1461 steps.extend(read(0x3B, &[0x82]));
1462 steps.extend(wrote(0x3B, &[0x42]));
1463 for _ in 0..=CALIBRATION_LIMIT_US / CALIBRATION_POLL_US {
1464 steps.extend(read(0x3B, &[0x22]));
1465 }
1466 let mut radio = radio(steps);
1467
1468 assert_eq!(radio.configure(eu868()), Err(RadioError::Calibration));
1469 }
1470
1471 #[test]
1472 fn the_rssi_uses_the_offset_of_the_configured_port() {
1473 let mut radio = configured(read(0x1B, &[0x30]).to_vec());
1474 assert_eq!(radio.rssi(), Ok(Decibels::from_db(-109)));
1475 }
1476}