Skip to main content

pamoja_radios/sx127x/
register.rs

1//! The SX127x register map, its SPI framing, and its operating modes.
2//!
3//! An SX1276, SX1277, SX1278, or SX1279 is driven through registers rather than commands.
4//! Each access is one SPI transaction framed by NSS: an address byte whose most significant
5//! bit is 1 for a write and 0 for a read, then the data bytes. The address advances with
6//! each further byte, except at [`FIFO`], where every byte goes to or comes from the LoRa
7//! data buffer. The addresses are those of Table 41 of the SX1276/77/78/79 datasheet
8//! (Rev 7) with the LoRa register page selected, and [`Mode`] holds the Mode bits of
9//! RegOpMode.
10
11/// The bit of the address byte that makes an access a write.
12pub const WRITE: u8 = 0x80;
13
14/// RegFifo: the LoRa data buffer, read or written a byte at a time at [`FIFO_ADDR_PTR`].
15pub const FIFO: u8 = 0x00;
16/// RegOpMode: LoRa or FSK, the register page, and the operating mode.
17pub const OP_MODE: u8 = 0x01;
18/// RegFrfMsb: the most significant byte of the carrier frequency word.
19pub const FRF_MSB: u8 = 0x06;
20/// RegFrfMid: the middle byte of the carrier frequency word.
21pub const FRF_MID: u8 = 0x07;
22/// RegFrfLsb: the least significant byte of the carrier frequency word.
23pub const FRF_LSB: u8 = 0x08;
24/// RegPaConfig: the amplifier output pin, its maximum power, and the output power.
25pub const PA_CONFIG: u8 = 0x09;
26/// RegPaRamp: the amplifier ramp time.
27pub const PA_RAMP: u8 = 0x0A;
28/// RegOcp: the over current protection of the amplifier.
29pub const OCP: u8 = 0x0B;
30/// RegLna: the LNA gain and current.
31pub const LNA: u8 = 0x0C;
32/// RegFifoAddrPtr: where in the data buffer the next [`FIFO`] access lands.
33pub const FIFO_ADDR_PTR: u8 = 0x0D;
34/// RegFifoTxBaseAddr: where in the data buffer a transmitted payload starts.
35pub const FIFO_TX_BASE_ADDR: u8 = 0x0E;
36/// RegFifoRxBaseAddr: where in the data buffer received payloads start.
37pub const FIFO_RX_BASE_ADDR: u8 = 0x0F;
38/// RegFifoRxCurrentAddr: where the last received packet starts in the data buffer.
39pub const FIFO_RX_CURRENT_ADDR: u8 = 0x10;
40/// RegIrqFlagsMask: the interrupts masked off.
41pub const IRQ_FLAGS_MASK: u8 = 0x11;
42/// RegIrqFlags: the interrupts raised, each cleared by writing it back as a 1.
43pub const IRQ_FLAGS: u8 = 0x12;
44/// RegRxNbBytes: the payload length of the last packet received.
45pub const RX_NB_BYTES: u8 = 0x13;
46/// RegModemStat: the live state of the LoRa modem.
47pub const MODEM_STAT: u8 = 0x18;
48/// RegPktSnrValue: the SNR of the last packet, in quarters of a decibel.
49pub const PKT_SNR_VALUE: u8 = 0x19;
50/// RegPktRssiValue: the RSSI of the last packet.
51pub const PKT_RSSI_VALUE: u8 = 0x1A;
52/// RegRssiValue: the RSSI the receiver hears right now.
53pub const RSSI_VALUE: u8 = 0x1B;
54/// RegHopChannel: the PLL lock and the CRC the last header announced.
55pub const HOP_CHANNEL: u8 = 0x1C;
56/// RegModemConfig1: bandwidth, coding rate, and header mode.
57pub const MODEM_CONFIG_1: u8 = 0x1D;
58/// RegModemConfig2: spreading factor, CRC, and the top bits of the symbol timeout.
59pub const MODEM_CONFIG_2: u8 = 0x1E;
60/// RegSymbTimeoutLsb: the low byte of the single reception timeout, in symbols.
61pub const SYMB_TIMEOUT_LSB: u8 = 0x1F;
62/// RegPreambleMsb: the high byte of the preamble length.
63pub const PREAMBLE_MSB: u8 = 0x20;
64/// RegPreambleLsb: the low byte of the preamble length.
65pub const PREAMBLE_LSB: u8 = 0x21;
66/// RegPayloadLength: the payload length to transmit, or to expect in implicit header mode.
67pub const PAYLOAD_LENGTH: u8 = 0x22;
68/// RegMaxPayloadLength: the longest payload a received header may announce.
69pub const MAX_PAYLOAD_LENGTH: u8 = 0x23;
70/// RegModemConfig3: low data rate optimization and the automatic gain control.
71pub const MODEM_CONFIG_3: u8 = 0x26;
72/// RegRssiWideband: a wideband RSSI sample, noisy enough to seed a random number.
73pub const RSSI_WIDEBAND: u8 = 0x2C;
74/// RegIfFreq2, which the spurious reception erratum sets per bandwidth.
75pub const IF_FREQ_2: u8 = 0x2F;
76/// RegIfFreq1, which the spurious reception erratum clears.
77pub const IF_FREQ_1: u8 = 0x30;
78/// RegDetectOptimize: the automatic IF and the LoRa detection optimization.
79pub const DETECT_OPTIMIZE: u8 = 0x31;
80/// RegInvertIQ: the IQ polarity of the receive and transmit paths.
81pub const INVERT_IQ: u8 = 0x33;
82/// RegHighBwOptimize1, which the 500 kHz sensitivity erratum sets.
83pub const HIGH_BW_OPTIMIZE_1: u8 = 0x36;
84/// RegDetectionThreshold: the LoRa detection threshold.
85pub const DETECTION_THRESHOLD: u8 = 0x37;
86/// RegSyncWord: the LoRa sync word.
87pub const SYNC_WORD: u8 = 0x39;
88/// RegHighBwOptimize2, which the 500 kHz sensitivity erratum sets.
89pub const HIGH_BW_OPTIMIZE_2: u8 = 0x3A;
90/// RegInvertIQ2, which completes an IQ inversion.
91pub const INVERT_IQ_2: u8 = 0x3B;
92/// RegImageCal, at the same address as [`INVERT_IQ_2`] on the FSK register page.
93pub const IMAGE_CAL: u8 = 0x3B;
94/// RegDioMapping1: the events DIO0 to DIO3 signal.
95pub const DIO_MAPPING_1: u8 = 0x40;
96/// RegDioMapping2: the events DIO4 and DIO5 signal.
97pub const DIO_MAPPING_2: u8 = 0x41;
98/// RegVersion: the silicon revision.
99pub const VERSION: u8 = 0x42;
100/// RegTcxo: a crystal or a TCXO on the XTA pin.
101pub const TCXO: u8 = 0x4B;
102/// RegPaDac: the +20 dBm setting of the PA_BOOST amplifier.
103pub const PA_DAC: u8 = 0x4D;
104
105/// The RegVersion value of an SX1276, SX1277, SX1278, or SX1279, and of the modules built
106/// on them.
107pub const VERSION_SX1276: u8 = 0x12;
108
109/// Returns the address byte that reads a register.
110///
111/// # Arguments
112///
113/// * `address` - the register address.
114///
115/// # Returns
116///
117/// The address with the write bit clear.
118///
119/// # Examples
120///
121/// ```
122/// use pamoja_radios::sx127x::register::{read_address, write_address, OP_MODE};
123///
124/// assert_eq!(read_address(OP_MODE), 0x01);
125/// assert_eq!(write_address(OP_MODE), 0x81);
126/// ```
127pub const fn read_address(address: u8) -> u8 {
128    address & !WRITE
129}
130
131/// Returns the address byte that writes a register.
132///
133/// # Arguments
134///
135/// * `address` - the register address.
136///
137/// # Returns
138///
139/// The address with the write bit set.
140pub const fn write_address(address: u8) -> u8 {
141    address | WRITE
142}
143
144/// An operating mode, the Mode bits 2 to 0 of RegOpMode.
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
146pub enum Mode {
147    /// 000: only the SPI interface and the registers are powered, and the data buffer is
148    /// lost. The only mode in which the chip may switch between LoRa and FSK.
149    Sleep,
150    /// 001: the crystal oscillator and the LoRa baseband are on.
151    Standby,
152    /// 010: the PLL is locked on the transmit frequency.
153    FsTx,
154    /// 011: one packet goes out, then the chip returns to standby.
155    Tx,
156    /// 100: the PLL is locked on the receive frequency.
157    FsRx,
158    /// 101: the receiver takes packet after packet until told otherwise.
159    RxContinuous,
160    /// 110: the receiver waits for one packet or the symbol timeout, then returns to
161    /// standby.
162    RxSingle,
163    /// 111: channel activity detection looks for a LoRa preamble.
164    Cad,
165}
166
167impl Mode {
168    /// Returns the Mode bits.
169    ///
170    /// # Returns
171    ///
172    /// The three bit code.
173    pub const fn code(self) -> u8 {
174        match self {
175            Mode::Sleep => 0b000,
176            Mode::Standby => 0b001,
177            Mode::FsTx => 0b010,
178            Mode::Tx => 0b011,
179            Mode::FsRx => 0b100,
180            Mode::RxContinuous => 0b101,
181            Mode::RxSingle => 0b110,
182            Mode::Cad => 0b111,
183        }
184    }
185
186    /// Decodes the Mode bits of a RegOpMode value.
187    ///
188    /// # Arguments
189    ///
190    /// * `op_mode` - the RegOpMode value; only bits 2 to 0 are read.
191    ///
192    /// # Returns
193    ///
194    /// The mode.
195    pub const fn from_op_mode(op_mode: u8) -> Mode {
196        match op_mode & 0b111 {
197            0b000 => Mode::Sleep,
198            0b001 => Mode::Standby,
199            0b010 => Mode::FsTx,
200            0b011 => Mode::Tx,
201            0b100 => Mode::FsRx,
202            0b101 => Mode::RxContinuous,
203            0b110 => Mode::RxSingle,
204            _ => Mode::Cad,
205        }
206    }
207}
208
209/// RegOpMode bit 7: the LoRa modem rather than FSK, changed only in [`Mode::Sleep`].
210pub const LONG_RANGE_MODE: u8 = 0x80;
211/// RegOpMode bit 6: the FSK register page while in LoRa mode.
212pub const ACCESS_SHARED_REG: u8 = 0x40;
213/// RegOpMode bit 3: the low frequency test registers, set at reset.
214pub const LOW_FREQUENCY_MODE_ON: u8 = 0x08;
215
216/// Returns the RegOpMode value for a LoRa operating mode.
217///
218/// The LoRa register page is selected and LowFrequencyModeOn keeps its reset value, since
219/// it only chooses which test registers are reachable.
220///
221/// # Arguments
222///
223/// * `mode` - the operating mode.
224///
225/// # Returns
226///
227/// The RegOpMode value.
228///
229/// # Examples
230///
231/// ```
232/// use pamoja_radios::sx127x::register::{lora_op_mode, Mode};
233///
234/// assert_eq!(lora_op_mode(Mode::Sleep), 0x88);
235/// assert_eq!(lora_op_mode(Mode::Tx), 0x8B);
236/// ```
237pub const fn lora_op_mode(mode: Mode) -> u8 {
238    LONG_RANGE_MODE | LOW_FREQUENCY_MODE_ON | mode.code()
239}
240
241/// Returns the RegOpMode value for an FSK operating mode, which image calibration needs.
242///
243/// # Arguments
244///
245/// * `mode` - the operating mode.
246///
247/// # Returns
248///
249/// The RegOpMode value, with LowFrequencyModeOn at its reset value.
250pub const fn fsk_op_mode(mode: Mode) -> u8 {
251    LOW_FREQUENCY_MODE_ON | mode.code()
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn every_mode_round_trips_through_its_bits() {
260        for mode in [
261            Mode::Sleep,
262            Mode::Standby,
263            Mode::FsTx,
264            Mode::Tx,
265            Mode::FsRx,
266            Mode::RxContinuous,
267            Mode::RxSingle,
268            Mode::Cad,
269        ] {
270            assert_eq!(Mode::from_op_mode(lora_op_mode(mode)), mode);
271            assert_eq!(Mode::from_op_mode(fsk_op_mode(mode)), mode);
272        }
273    }
274
275    #[test]
276    fn the_op_mode_values_match_the_reset_state_and_the_reference_driver() {
277        assert_eq!(
278            fsk_op_mode(Mode::Standby),
279            0x09,
280            "the RegOpMode reset value"
281        );
282        assert_eq!(lora_op_mode(Mode::Standby), 0x89);
283        assert_eq!(lora_op_mode(Mode::RxContinuous) & 0x07, 0x05);
284        assert_eq!(lora_op_mode(Mode::RxSingle) & 0x07, 0x06);
285    }
286
287    #[test]
288    fn the_address_byte_carries_the_access_direction_in_its_top_bit() {
289        assert_eq!(write_address(FIFO), 0x80);
290        assert_eq!(read_address(VERSION), 0x42);
291        assert_eq!(write_address(PA_DAC), 0xCD);
292        assert_eq!(read_address(write_address(SYNC_WORD)), SYNC_WORD);
293    }
294}