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> {
323 spi: SPI,
324 reset: RESET,
325 delay: D,
326 board: Board,
327 config: Option<RadioConfig>,
328 tuned_hz: Option<u32>,
329 calibrated_high: bool,
330 calibrated_low: bool,
331}
332
333impl<SPI, RESET, D> Sx127x<SPI, RESET, D> {
334 pub fn new(spi: SPI, reset: RESET, delay: D, board: Board) -> Self {
348 Sx127x {
349 spi,
350 reset,
351 delay,
352 board,
353 config: None,
354 tuned_hz: None,
355 calibrated_high: false,
356 calibrated_low: false,
357 }
358 }
359
360 pub fn board(&self) -> Board {
366 self.board
367 }
368
369 pub fn config(&self) -> Option<&RadioConfig> {
376 self.config.as_ref()
377 }
378
379 pub fn tx_power(&self, output_dbm: i8) -> TxPower {
389 TxPower::for_output(self.board.output, output_dbm)
390 }
391
392 pub fn release(self) -> (SPI, RESET, D) {
398 (self.spi, self.reset, self.delay)
399 }
400}
401
402impl<SPI, RESET, D> Sx127x<SPI, RESET, D>
403where
404 SPI: SpiDevice,
405 RESET: OutputPin,
406 D: DelayNs,
407{
408 pub fn init(&mut self) -> Result<(), RadioError<SPI::Error>> {
420 self.config = None;
421 self.tuned_hz = None;
422 self.calibrated_high = false;
423 self.calibrated_low = false;
424 self.reset.set_low().map_err(pin)?;
425 self.delay.delay_us(RESET_HOLD_US);
426 self.reset.set_high().map_err(pin)?;
427 self.delay.delay_us(RESET_SETTLE_US);
428
429 let version = self.version()?;
430 if version != register::VERSION_SX1276 {
431 return Err(RadioError::Absent(version));
432 }
433 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Sleep))?;
434 if self.board.tcxo {
435 self.write_register(register::TCXO, config::TCXO_INPUT_ON)?;
436 }
437 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))?;
438 self.write_register(register::OP_MODE, lora_op_mode(Mode::Standby))?;
439 self.write_register(register::LNA, config::LNA_BOOSTED)
440 }
441
442 pub fn configure(&mut self, config: RadioConfig) -> Result<(), RadioError<SPI::Error>> {
461 let modulation = LoraModulation::from_link(&config.link).map_err(RadioError::Modulation)?;
462 if !modulation.bandwidth.in_band(config.frequency_hz) {
463 return Err(RadioError::Modulation(ModulationError::Bandwidth(
464 config.link.bandwidth_hz(),
465 )));
466 }
467 if config.power.output() != self.board.output {
468 return Err(RadioError::Output);
469 }
470 let calibrated = match Port::for_frequency(config.frequency_hz) {
471 Port::High => self.calibrated_high,
472 Port::Low => self.calibrated_low,
473 };
474 if calibrated {
475 self.standby()?;
476 } else {
477 self.calibrate(config.frequency_hz)?;
478 }
479 self.tune(config.frequency_hz)?;
480 self.write_register(register::PA_CONFIG, config.power.pa_config)?;
481 self.write_register(register::PA_DAC, config.power.pa_dac)?;
482 self.write_register(register::OCP, config.power.ocp)?;
483 self.write_register(register::MODEM_CONFIG_1, modulation.modem_config_1())?;
484 self.write_register(register::MODEM_CONFIG_2, modulation.modem_config_2(0))?;
485 self.write_register(register::MODEM_CONFIG_3, modulation.modem_config_3())?;
486 self.write_registers(register::PREAMBLE_MSB, &preamble_bytes(&config.link))?;
487 self.update_register(register::DETECT_OPTIMIZE, |value| {
488 modulation.detect_optimize(value)
489 })?;
490 self.write_register(
491 register::DETECTION_THRESHOLD,
492 modulation.detection_threshold(),
493 )?;
494 let (optimize_1, optimize_2) = high_bw_optimize(modulation.bandwidth, config.frequency_hz);
495 self.write_register(register::HIGH_BW_OPTIMIZE_1, optimize_1)?;
496 if let Some(optimize_2) = optimize_2 {
497 self.write_register(register::HIGH_BW_OPTIMIZE_2, optimize_2)?;
498 }
499 self.write_register(register::MAX_PAYLOAD_LENGTH, 0xFF)?;
500 self.write_register(register::SYNC_WORD, config.sync_word.to_byte())?;
501 self.config = Some(config);
502 Ok(())
503 }
504
505 pub fn transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
526 let airtime_us = self.start_transmit(payload)?;
527 let limit_us = airtime_us.saturating_add(2 * TIMEOUT_MARGIN_US);
528 let mut waited_us = airtime_us;
529 self.pause_us(airtime_us);
530 while !self.finish_transmit()? {
531 if waited_us >= limit_us {
532 return Err(RadioError::NoInterrupt);
533 }
534 self.delay.delay_us(IRQ_POLL_US);
535 waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
536 }
537 Ok(airtime_us)
538 }
539
540 pub fn start_transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
561 let settings = self.config.ok_or(RadioError::NotConfigured)?;
562 let len = u8::try_from(payload.len())
563 .ok()
564 .filter(|len| *len > 0)
565 .ok_or(RadioError::PayloadLength(payload.len()))?;
566 let invert = settings.invert_iq_transmit;
567
568 self.standby()?;
569 self.write_register(register::INVERT_IQ, invert_iq(false, invert))?;
570 self.write_register(register::INVERT_IQ_2, invert_iq_2(invert))?;
571 self.tune(settings.frequency_hz)?;
572 self.write_register(register::PAYLOAD_LENGTH, len)?;
573 self.write_register(register::FIFO_TX_BASE_ADDR, 0)?;
574 self.write_register(register::FIFO_ADDR_PTR, 0)?;
575 self.write_registers(register::FIFO, payload)?;
576 self.write_register(register::DIO_MAPPING_1, config::DIO0_TX_DONE)?;
577 self.write_register(register::IRQ_FLAGS, IrqFlags::ALL.bits())?;
578 self.write_register(register::OP_MODE, lora_op_mode(Mode::Tx))?;
579 Ok(settings.link.airtime_us(payload.len()))
580 }
581
582 pub fn finish_transmit(&mut self) -> Result<bool, RadioError<SPI::Error>> {
594 let flags = self.irq_flags()?;
595 if !flags.contains(IrqFlags::TX_DONE) {
596 return Ok(false);
597 }
598 self.write_register(register::IRQ_FLAGS, IrqFlags::TX_DONE.bits())?;
599 Ok(true)
600 }
601
602 pub fn receive(
625 &mut self,
626 buffer: &mut [u8],
627 timeout_us: u64,
628 ) -> Result<Reception, RadioError<SPI::Error>> {
629 let settings = self.config.ok_or(RadioError::NotConfigured)?;
630 let modulation =
631 LoraModulation::from_link(&settings.link).map_err(RadioError::Modulation)?;
632 self.prepare_reception(&settings, &modulation)?;
633 let symbols = config::symbol_timeout(&settings.link, timeout_us);
634 self.write_register(register::MODEM_CONFIG_2, modulation.modem_config_2(symbols))?;
635 self.write_register(register::SYMB_TIMEOUT_LSB, symbols as u8)?;
636 self.write_register(register::OP_MODE, lora_op_mode(Mode::RxSingle))?;
637
638 let window_us = u64::from(symbols) * settings.link.symbol_time_us();
639 let limit_us = window_us
640 .saturating_add(settings.link.airtime_us(usize::from(u8::MAX)))
641 .saturating_add(TIMEOUT_MARGIN_US);
642 let flags = self.wait_for(IrqFlags::RX_DONE | IrqFlags::RX_TIMEOUT, limit_us)?;
643 self.write_register(register::IRQ_FLAGS, flags.bits())?;
644 if !flags.contains(IrqFlags::RX_DONE) {
645 return Ok(Reception::Timeout);
646 }
647 if flags.contains(IrqFlags::PAYLOAD_CRC_ERROR) {
648 return Ok(Reception::Corrupt);
649 }
650 self.read_frame(buffer, &settings)
651 }
652
653 pub fn listen(&mut self) -> Result<(), RadioError<SPI::Error>> {
666 let settings = self.config.ok_or(RadioError::NotConfigured)?;
667 let modulation =
668 LoraModulation::from_link(&settings.link).map_err(RadioError::Modulation)?;
669 self.prepare_reception(&settings, &modulation)?;
670 self.write_register(register::OP_MODE, lora_op_mode(Mode::RxContinuous))
671 }
672
673 pub fn take_frame(
693 &mut self,
694 buffer: &mut [u8],
695 ) -> Result<Option<Reception>, RadioError<SPI::Error>> {
696 let settings = self.config.ok_or(RadioError::NotConfigured)?;
697 let flags = self.irq_flags()?;
698 if !flags.contains(IrqFlags::RX_DONE) {
699 return Ok(None);
700 }
701 let received = IrqFlags::RX_DONE | IrqFlags::PAYLOAD_CRC_ERROR | IrqFlags::VALID_HEADER;
702 self.write_register(register::IRQ_FLAGS, (flags & received).bits())?;
703 if flags.contains(IrqFlags::PAYLOAD_CRC_ERROR) {
704 return Ok(Some(Reception::Corrupt));
705 }
706 self.read_frame(buffer, &settings).map(Some)
707 }
708
709 pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
715 self.write_register(register::OP_MODE, lora_op_mode(Mode::Standby))
716 }
717
718 pub fn sleep(&mut self) -> Result<(), RadioError<SPI::Error>> {
727 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))
728 }
729
730 pub fn version(&mut self) -> Result<u8, RadioError<SPI::Error>> {
740 self.read_register(register::VERSION)
741 }
742
743 pub fn irq_flags(&mut self) -> Result<IrqFlags, RadioError<SPI::Error>> {
753 self.read_register(register::IRQ_FLAGS)
754 .map(IrqFlags::from_bits)
755 }
756
757 pub fn modem_status(&mut self) -> Result<ModemStatus, RadioError<SPI::Error>> {
767 self.read_register(register::MODEM_STAT)
768 .map(ModemStatus::from_byte)
769 }
770
771 pub fn rssi(&mut self) -> Result<Decibels, RadioError<SPI::Error>> {
782 let settings = self.config.ok_or(RadioError::NotConfigured)?;
783 let byte = self.read_register(register::RSSI_VALUE)?;
784 Ok(rssi_dbm(byte, Port::for_frequency(settings.frequency_hz)))
785 }
786
787 pub fn read_register(&mut self, address: u8) -> Result<u8, RadioError<SPI::Error>> {
801 let mut value = [0u8; 1];
802 self.read_registers(address, &mut value)?;
803 Ok(value[0])
804 }
805
806 pub fn write_register(&mut self, address: u8, value: u8) -> Result<(), RadioError<SPI::Error>> {
817 self.write_registers(address, &[value])
818 }
819
820 pub fn read_registers(
832 &mut self,
833 address: u8,
834 values: &mut [u8],
835 ) -> Result<(), RadioError<SPI::Error>> {
836 self.spi
837 .transaction(&mut [
838 Operation::Write(&[read_address(address)]),
839 Operation::Read(values),
840 ])
841 .map_err(RadioError::Spi)
842 }
843
844 pub fn write_registers(
856 &mut self,
857 address: u8,
858 values: &[u8],
859 ) -> Result<(), RadioError<SPI::Error>> {
860 self.spi
861 .transaction(&mut [
862 Operation::Write(&[write_address(address)]),
863 Operation::Write(values),
864 ])
865 .map_err(RadioError::Spi)
866 }
867
868 fn calibrate(&mut self, frequency_hz: u32) -> Result<(), RadioError<SPI::Error>> {
869 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))?;
870 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Sleep))?;
871 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Standby))?;
872 self.write_register(register::PA_CONFIG, 0x00)?;
873 self.write_registers(register::FRF_MSB, &frequency_bytes(frequency_hz))?;
874 self.tuned_hz = Some(frequency_hz);
875 self.update_register(register::IMAGE_CAL, image_cal_start)?;
876 let mut waited_us = 0u32;
877 while self.read_register(register::IMAGE_CAL)? & config::IMAGE_CAL_RUNNING != 0 {
878 if waited_us >= CALIBRATION_LIMIT_US {
879 return Err(RadioError::Calibration);
880 }
881 self.delay.delay_us(CALIBRATION_POLL_US);
882 waited_us = waited_us.saturating_add(CALIBRATION_POLL_US);
883 }
884 self.write_register(register::OP_MODE, fsk_op_mode(Mode::Sleep))?;
885 self.write_register(register::OP_MODE, lora_op_mode(Mode::Sleep))?;
886 self.standby()?;
887 match Port::for_frequency(frequency_hz) {
888 Port::High => self.calibrated_high = true,
889 Port::Low => self.calibrated_low = true,
890 }
891 Ok(())
892 }
893
894 fn tune(&mut self, frequency_hz: u32) -> Result<(), RadioError<SPI::Error>> {
895 if self.tuned_hz != Some(frequency_hz) {
896 self.write_registers(register::FRF_MSB, &frequency_bytes(frequency_hz))?;
897 self.tuned_hz = Some(frequency_hz);
898 }
899 Ok(())
900 }
901
902 fn prepare_reception(
903 &mut self,
904 settings: &RadioConfig,
905 modulation: &LoraModulation,
906 ) -> Result<(), RadioError<SPI::Error>> {
907 let invert = settings.invert_iq_receive;
908 self.standby()?;
909 self.write_register(register::INVERT_IQ, invert_iq(invert, false))?;
910 self.write_register(register::INVERT_IQ_2, invert_iq_2(invert))?;
911 let erratum = spurious_reception(modulation.bandwidth);
912 self.update_register(register::DETECT_OPTIMIZE, |value| {
913 automatic_if(value, erratum.automatic_if)
914 })?;
915 if let Some(if_freq_2) = erratum.if_freq_2 {
916 self.write_register(register::IF_FREQ_1, 0x00)?;
917 self.write_register(register::IF_FREQ_2, if_freq_2)?;
918 }
919 self.tune(settings.frequency_hz.saturating_add(erratum.offset_hz))?;
920 self.write_register(register::FIFO_RX_BASE_ADDR, 0)?;
921 self.write_register(register::FIFO_ADDR_PTR, 0)?;
922 self.write_register(register::DIO_MAPPING_1, config::DIO0_RX_DONE)?;
923 self.write_register(register::IRQ_FLAGS, IrqFlags::ALL.bits())
924 }
925
926 fn read_frame(
927 &mut self,
928 buffer: &mut [u8],
929 settings: &RadioConfig,
930 ) -> Result<Reception, RadioError<SPI::Error>> {
931 let len = usize::from(self.read_register(register::RX_NB_BYTES)?);
932 let frame = buffer
933 .get_mut(..len)
934 .ok_or(RadioError::BufferTooSmall(len))?;
935 let start = self.read_register(register::FIFO_RX_CURRENT_ADDR)?;
936 self.write_register(register::FIFO_ADDR_PTR, start)?;
937 if len > 0 {
938 self.read_registers(register::FIFO, frame)?;
939 }
940 let mut levels = [0u8; 2];
941 self.read_registers(register::PKT_SNR_VALUE, &mut levels)?;
942 Ok(Reception::Frame {
943 len,
944 status: PacketStatus::from_bytes(levels, Port::for_frequency(settings.frequency_hz)),
945 })
946 }
947
948 fn update_register(
949 &mut self,
950 address: u8,
951 change: impl FnOnce(u8) -> u8,
952 ) -> Result<(), RadioError<SPI::Error>> {
953 let value = self.read_register(address)?;
954 self.write_register(address, change(value))
955 }
956
957 fn wait_for(
958 &mut self,
959 events: IrqFlags,
960 limit_us: u64,
961 ) -> Result<IrqFlags, RadioError<SPI::Error>> {
962 let mut waited_us = 0u64;
963 loop {
964 let flags = self.irq_flags()?;
965 if flags.intersects(events) {
966 return Ok(flags);
967 }
968 if waited_us >= limit_us {
969 return Err(RadioError::NoInterrupt);
970 }
971 self.delay.delay_us(IRQ_POLL_US);
972 waited_us = waited_us.saturating_add(u64::from(IRQ_POLL_US));
973 }
974 }
975
976 fn pause_us(&mut self, micros: u64) {
977 let mut left = micros;
978 while left > 0 {
979 let step = u32::try_from(left).unwrap_or(u32::MAX);
980 self.delay.delay_us(step);
981 left -= u64::from(step);
982 }
983 }
984}
985
986#[cfg(test)]
987mod tests {
988 use super::*;
989 use pamoja_hal::digital::PinState;
990 use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
991
992 type Radio = Sx127x<SpiScript, PinScript, DelayLog>;
993
994 fn radio(steps: Vec<SpiStep>) -> Radio {
995 Sx127x::new(
996 SpiScript::new(steps),
997 PinScript::new([]),
998 DelayLog::new(),
999 Board::new(PaOutput::PaBoost),
1000 )
1001 }
1002
1003 fn wrote(address: u8, values: &[u8]) -> [SpiStep; 2] {
1004 [
1005 SpiStep::write([address | 0x80]),
1006 SpiStep::write(values.to_vec()),
1007 ]
1008 }
1009
1010 fn read(address: u8, values: &[u8]) -> [SpiStep; 2] {
1011 [SpiStep::write([address]), SpiStep::read(values.to_vec())]
1012 }
1013
1014 fn eu868() -> RadioConfig {
1015 RadioConfig::new(
1016 868_100_000,
1017 LinkSettings::new(7, 125_000),
1018 TxPower::for_output(PaOutput::PaBoost, 14),
1019 )
1020 .with_sync_word(SyncWord::Public)
1021 }
1022
1023 fn calibration(frequency: [u8; 3]) -> Vec<SpiStep> {
1024 let mut steps = Vec::new();
1025 steps.extend(wrote(0x01, &[0x88]));
1026 steps.extend(wrote(0x01, &[0x08]));
1027 steps.extend(wrote(0x01, &[0x09]));
1028 steps.extend(wrote(0x09, &[0x00]));
1029 steps.extend(wrote(0x06, &frequency));
1030 steps.extend(read(0x3B, &[0x82]));
1031 steps.extend(wrote(0x3B, &[0x42]));
1032 steps.extend(read(0x3B, &[0x22]));
1033 steps.extend(read(0x3B, &[0x02]));
1034 steps.extend(wrote(0x01, &[0x08]));
1035 steps.extend(wrote(0x01, &[0x88]));
1036 steps.extend(wrote(0x01, &[0x89]));
1037 steps
1038 }
1039
1040 fn configuration() -> Vec<SpiStep> {
1041 let mut steps = calibration([0xD9, 0x06, 0x66]);
1042 steps.extend(wrote(0x09, &[0xFC]));
1043 steps.extend(wrote(0x4D, &[0x84]));
1044 steps.extend(wrote(0x0B, &[0x2B]));
1045 steps.extend(wrote(0x1D, &[0x72]));
1046 steps.extend(wrote(0x1E, &[0x74]));
1047 steps.extend(wrote(0x26, &[0x04]));
1048 steps.extend(wrote(0x20, &[0x00, 0x08]));
1049 steps.extend(read(0x31, &[0xC3]));
1050 steps.extend(wrote(0x31, &[0xC3]));
1051 steps.extend(wrote(0x37, &[0x0A]));
1052 steps.extend(wrote(0x36, &[0x03]));
1053 steps.extend(wrote(0x23, &[0xFF]));
1054 steps.extend(wrote(0x39, &[0x34]));
1055 steps
1056 }
1057
1058 fn configured(more: Vec<SpiStep>) -> Radio {
1059 let mut steps = configuration();
1060 steps.extend(more);
1061 let mut radio = radio(steps);
1062 radio.configure(eu868()).expect("configures");
1063 radio
1064 }
1065
1066 fn reception_setup() -> Vec<SpiStep> {
1067 let mut steps = Vec::new();
1068 steps.extend(wrote(0x01, &[0x89]));
1069 steps.extend(wrote(0x33, &[0x27]));
1070 steps.extend(wrote(0x3B, &[0x1D]));
1071 steps.extend(read(0x31, &[0xC3]));
1072 steps.extend(wrote(0x31, &[0x43]));
1073 steps.extend(wrote(0x30, &[0x00]));
1074 steps.extend(wrote(0x2F, &[0x40]));
1075 steps.extend(wrote(0x0F, &[0x00]));
1076 steps.extend(wrote(0x0D, &[0x00]));
1077 steps.extend(wrote(0x40, &[0x00]));
1078 steps.extend(wrote(0x12, &[0xFF]));
1079 steps
1080 }
1081
1082 #[test]
1083 fn init_resets_checks_the_version_and_enters_lora_mode_with_a_tcxo() {
1084 let mut steps = Vec::new();
1085 steps.extend(read(0x42, &[0x12]));
1086 steps.extend(wrote(0x01, &[0x08]));
1087 steps.extend(wrote(0x4B, &[0x19]));
1088 steps.extend(wrote(0x01, &[0x88]));
1089 steps.extend(wrote(0x01, &[0x89]));
1090 steps.extend(wrote(0x0C, &[0x23]));
1091 let board = Board::new(PaOutput::PaBoost).with_tcxo();
1092 let mut radio = Sx127x::new(
1093 SpiScript::new(steps),
1094 PinScript::new([]),
1095 DelayLog::new(),
1096 board,
1097 );
1098
1099 radio.init().expect("initializes");
1100
1101 let (spi, reset, delay) = radio.release();
1102 assert!(spi.done(), "{} steps left", spi.remaining());
1103 assert_eq!(reset.driven(), [PinState::Low, PinState::High]);
1104 assert_eq!(delay.waits_ns(), [1_000_000, 6_000_000]);
1105 }
1106
1107 #[test]
1108 fn init_refuses_a_chip_that_is_not_an_sx1276() {
1109 let mut radio = radio(read(0x42, &[0x22]).to_vec());
1110 assert_eq!(radio.init(), Err(RadioError::Absent(0x22)));
1111 }
1112
1113 #[test]
1114 fn configure_calibrates_the_port_once_then_writes_the_modem() {
1115 let mut steps = configuration();
1116 steps.extend(wrote(0x01, &[0x89]));
1117 steps.extend(wrote(0x06, &[0xD9, 0x13, 0x33]));
1118 steps.extend(wrote(0x09, &[0xFC]));
1119 steps.extend(wrote(0x4D, &[0x84]));
1120 steps.extend(wrote(0x0B, &[0x2B]));
1121 steps.extend(wrote(0x1D, &[0x72]));
1122 steps.extend(wrote(0x1E, &[0x74]));
1123 steps.extend(wrote(0x26, &[0x04]));
1124 steps.extend(wrote(0x20, &[0x00, 0x08]));
1125 steps.extend(read(0x31, &[0xC3]));
1126 steps.extend(wrote(0x31, &[0xC3]));
1127 steps.extend(wrote(0x37, &[0x0A]));
1128 steps.extend(wrote(0x36, &[0x03]));
1129 steps.extend(wrote(0x23, &[0xFF]));
1130 steps.extend(wrote(0x39, &[0x34]));
1131 let mut radio = radio(steps);
1132
1133 radio.configure(eu868()).expect("configures on 868.1 MHz");
1134 let mut next = eu868();
1135 next.frequency_hz = 868_300_000;
1136 radio.configure(next).expect("configures on 868.3 MHz");
1137
1138 let (spi, _, delay) = radio.release();
1139 assert!(spi.done(), "{} steps left", spi.remaining());
1140 assert_eq!(delay.waits_ns(), [1_000_000], "one poll while calibrating");
1141 }
1142
1143 #[test]
1144 fn configure_refuses_settings_the_chip_cannot_use() {
1145 let mut radio = radio(Vec::new());
1146 let sf5 = RadioConfig::new(
1147 868_100_000,
1148 LinkSettings::new(5, 125_000),
1149 TxPower::for_output(PaOutput::PaBoost, 14),
1150 );
1151 assert_eq!(
1152 radio.configure(sf5),
1153 Err(RadioError::Modulation(ModulationError::SpreadingFactor(5)))
1154 );
1155 let wide_at_169 = RadioConfig::new(
1156 169_400_000,
1157 LinkSettings::new(7, 500_000),
1158 TxPower::for_output(PaOutput::PaBoost, 14),
1159 );
1160 assert_eq!(
1161 radio.configure(wide_at_169),
1162 Err(RadioError::Modulation(ModulationError::Bandwidth(500_000)))
1163 );
1164 let rfo = RadioConfig::new(
1165 868_100_000,
1166 LinkSettings::new(7, 125_000),
1167 TxPower::for_output(PaOutput::Rfo, 14),
1168 );
1169 assert_eq!(radio.configure(rfo), Err(RadioError::Output));
1170 let (spi, _, _) = radio.release();
1171 assert_eq!(spi.consumed(), 0, "nothing reaches the bus");
1172 }
1173
1174 #[test]
1175 fn a_frame_goes_out_through_the_fifo_and_tx_mode() {
1176 let mut steps = Vec::new();
1177 steps.extend(wrote(0x01, &[0x89]));
1178 steps.extend(wrote(0x33, &[0x27]));
1179 steps.extend(wrote(0x3B, &[0x1D]));
1180 steps.extend(wrote(0x22, &[0x05]));
1181 steps.extend(wrote(0x0E, &[0x00]));
1182 steps.extend(wrote(0x0D, &[0x00]));
1183 steps.extend(wrote(0x00, b"level"));
1184 steps.extend(wrote(0x40, &[0x40]));
1185 steps.extend(wrote(0x12, &[0xFF]));
1186 steps.extend(wrote(0x01, &[0x8B]));
1187 steps.extend(read(0x12, &[0x00]));
1188 steps.extend(read(0x12, &[0x08]));
1189 steps.extend(wrote(0x12, &[0x08]));
1190 let mut radio = configured(steps);
1191
1192 let airtime = radio.transmit(b"level").expect("sends");
1193
1194 assert_eq!(airtime, LinkSettings::new(7, 125_000).airtime_us(5));
1195 let (spi, _, _) = radio.release();
1196 assert!(spi.done(), "{} steps left", spi.remaining());
1197 }
1198
1199 #[test]
1200 fn a_transmission_needs_a_configuration_and_a_payload_that_fits() {
1201 let mut unconfigured = radio(Vec::new());
1202 assert_eq!(
1203 unconfigured.start_transmit(b"x"),
1204 Err(RadioError::NotConfigured)
1205 );
1206 let mut radio = configured(Vec::new());
1207 assert_eq!(radio.start_transmit(&[]), Err(RadioError::PayloadLength(0)));
1208 assert_eq!(
1209 radio.start_transmit(&[0; 256]),
1210 Err(RadioError::PayloadLength(256))
1211 );
1212 }
1213
1214 #[test]
1215 fn listening_applies_the_spurious_reception_erratum_and_takes_frames() {
1216 let mut steps = reception_setup();
1217 steps.extend(wrote(0x01, &[0x8D]));
1218 steps.extend(read(0x12, &[0x00]));
1219 steps.extend(read(0x12, &[0x50]));
1220 steps.extend(wrote(0x12, &[0x50]));
1221 steps.extend(read(0x13, &[0x02]));
1222 steps.extend(read(0x10, &[0x00]));
1223 steps.extend(wrote(0x0D, &[0x00]));
1224 steps.extend(read(0x00, b"hi"));
1225 steps.extend(read(0x19, &[0xF6, 0x30]));
1226 steps.extend(read(0x12, &[0x60]));
1227 steps.extend(wrote(0x12, &[0x60]));
1228 let mut radio = configured(steps);
1229 let mut buffer = [0u8; 255];
1230
1231 radio.listen().expect("listens");
1232 assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1233 let frame = radio.take_frame(&mut buffer).expect("reads the frame");
1234 assert_eq!(
1235 frame,
1236 Some(Reception::Frame {
1237 len: 2,
1238 status: PacketStatus::from_bytes([0xF6, 0x30], Port::High),
1239 })
1240 );
1241 assert_eq!(&buffer[..2], b"hi");
1242 assert_eq!(radio.take_frame(&mut buffer), Ok(Some(Reception::Corrupt)));
1243
1244 let (spi, _, _) = radio.release();
1245 assert!(spi.done(), "{} steps left", spi.remaining());
1246 }
1247
1248 #[test]
1249 fn a_narrow_bandwidth_listens_one_bandwidth_above_the_carrier() {
1250 let narrow = RadioConfig::new(
1251 433_175_000,
1252 LinkSettings::new(9, 20_833),
1253 TxPower::for_output(PaOutput::PaBoost, 10),
1254 );
1255 let mut steps = calibration([0x6C, 0x4B, 0x33]);
1256 steps.extend(wrote(0x09, &[0xF8]));
1257 steps.extend(wrote(0x4D, &[0x84]));
1258 steps.extend(wrote(0x0B, &[0x2B]));
1259 steps.extend(wrote(0x1D, &[0x32]));
1260 steps.extend(wrote(0x1E, &[0x94]));
1261 steps.extend(wrote(0x26, &[0x0C]));
1262 steps.extend(wrote(0x20, &[0x00, 0x08]));
1263 steps.extend(read(0x31, &[0xC3]));
1264 steps.extend(wrote(0x31, &[0xC3]));
1265 steps.extend(wrote(0x37, &[0x0A]));
1266 steps.extend(wrote(0x36, &[0x03]));
1267 steps.extend(wrote(0x23, &[0xFF]));
1268 steps.extend(wrote(0x39, &[0x12]));
1269 steps.extend(wrote(0x01, &[0x89]));
1270 steps.extend(wrote(0x33, &[0x27]));
1271 steps.extend(wrote(0x3B, &[0x1D]));
1272 steps.extend(read(0x31, &[0xC3]));
1273 steps.extend(wrote(0x31, &[0x43]));
1274 steps.extend(wrote(0x30, &[0x00]));
1275 steps.extend(wrote(0x2F, &[0x44]));
1276 steps.extend(wrote(0x06, &frequency_bytes(433_175_000 + 20_830)));
1277 steps.extend(wrote(0x0F, &[0x00]));
1278 steps.extend(wrote(0x0D, &[0x00]));
1279 steps.extend(wrote(0x40, &[0x00]));
1280 steps.extend(wrote(0x12, &[0xFF]));
1281 steps.extend(wrote(0x01, &[0x8D]));
1282 let mut radio = radio(steps);
1283
1284 radio.configure(narrow).expect("configures");
1285 radio.listen().expect("listens");
1286
1287 let (spi, _, _) = radio.release();
1288 assert!(spi.done(), "{} steps left", spi.remaining());
1289 }
1290
1291 #[test]
1292 fn a_single_reception_counts_its_timeout_in_symbols_and_reports_it() {
1293 let mut steps = reception_setup();
1294 steps.extend(wrote(0x1E, &[0x74]));
1295 steps.extend(wrote(0x1F, &[0x62]));
1296 steps.extend(wrote(0x01, &[0x8E]));
1297 steps.extend(read(0x12, &[0x80]));
1298 steps.extend(wrote(0x12, &[0x80]));
1299 let mut radio = configured(steps);
1300 let mut buffer = [0u8; 64];
1301
1302 let outcome = radio.receive(&mut buffer, 100_000).expect("listens");
1303
1304 assert_eq!(outcome, Reception::Timeout);
1305 let (spi, _, _) = radio.release();
1306 assert!(spi.done(), "{} steps left", spi.remaining());
1307 }
1308
1309 #[test]
1310 fn a_frame_longer_than_the_buffer_is_refused() {
1311 let mut steps = reception_setup();
1312 steps.extend(wrote(0x01, &[0x8D]));
1313 steps.extend(read(0x12, &[0x40]));
1314 steps.extend(wrote(0x12, &[0x40]));
1315 steps.extend(read(0x13, &[0x10]));
1316 let mut radio = configured(steps);
1317 let mut buffer = [0u8; 8];
1318
1319 radio.listen().expect("listens");
1320 assert_eq!(
1321 radio.take_frame(&mut buffer),
1322 Err(RadioError::BufferTooSmall(16))
1323 );
1324 }
1325
1326 #[test]
1327 fn a_calibration_that_never_finishes_is_an_error() {
1328 let mut steps = Vec::new();
1329 steps.extend(wrote(0x01, &[0x88]));
1330 steps.extend(wrote(0x01, &[0x08]));
1331 steps.extend(wrote(0x01, &[0x09]));
1332 steps.extend(wrote(0x09, &[0x00]));
1333 steps.extend(wrote(0x06, &[0xD9, 0x06, 0x66]));
1334 steps.extend(read(0x3B, &[0x82]));
1335 steps.extend(wrote(0x3B, &[0x42]));
1336 for _ in 0..=CALIBRATION_LIMIT_US / CALIBRATION_POLL_US {
1337 steps.extend(read(0x3B, &[0x22]));
1338 }
1339 let mut radio = radio(steps);
1340
1341 assert_eq!(radio.configure(eu868()), Err(RadioError::Calibration));
1342 }
1343
1344 #[test]
1345 fn the_rssi_uses_the_offset_of_the_configured_port() {
1346 let mut radio = configured(read(0x1B, &[0x30]).to_vec());
1347 assert_eq!(radio.rssi(), Ok(Decibels::from_db(-109)));
1348 }
1349}