├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── conf.rs ├── lib.rs ├── op ├── calib.rs ├── err.rs ├── init.rs ├── irq.rs ├── mod.rs ├── modulation.rs ├── packet.rs ├── rxtx.rs ├── status.rs └── tcxo.rs ├── reg.rs └── sx ├── err.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "critical-section" 7 | version = "1.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 10 | 11 | [[package]] 12 | name = "embedded-hal" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" 16 | 17 | [[package]] 18 | name = "sx126x" 19 | version = "0.3.0" 20 | dependencies = [ 21 | "critical-section", 22 | "embedded-hal", 23 | ] 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sx126x" 3 | version = "0.3.0" 4 | authors = ["Henk Oordt "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "A driver for the SX126X Family of LoRa modems" 8 | keywords = ["embedded", "lora", "sx126x", "lorawan"] 9 | categories = ["embedded", "hardware-support", "no-std"] 10 | readme = "README.md" 11 | repository = "https://github.com/tweedegolf/sx126x-rs" 12 | exclude = [ 13 | "/.cargo", 14 | "/target", 15 | "/.gitignore", 16 | ] 17 | 18 | [dependencies] 19 | embedded-hal = "1.0" 20 | critical-section = "^1" 21 | 22 | [profile.dev] 23 | opt-level = 0 24 | debug = true 25 | lto = false 26 | 27 | [profile.release] 28 | opt-level = 3 29 | codegen-units = 1 30 | debug = true 31 | lto = true 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SX126x-rs 2 | Driver crate for Semtech's SX1261/62 family of LoRa trancievers. 3 | 4 | [Documentation on docs.rs](https://docs.rs/sx126x) 5 | -------------------------------------------------------------------------------- /src/conf.rs: -------------------------------------------------------------------------------- 1 | //! Wrapper for modem configuration parameters 2 | use super::op::*; 3 | 4 | /// Configuration parameters. 5 | /// Used to initialize the SX126x modem 6 | pub struct Config { 7 | /// Packet type 8 | pub packet_type: PacketType, 9 | /// LoRa sync word 10 | pub sync_word: u16, 11 | /// Calibration parameters 12 | pub calib_param: CalibParam, 13 | /// Modulation parameters 14 | pub mod_params: ModParams, 15 | /// Power-amplifier configuration 16 | pub pa_config: PaConfig, 17 | /// Packet parameters. Set tot none if you want to configure 18 | /// these later 19 | pub packet_params: Option, 20 | /// TX parameters 21 | pub tx_params: TxParams, 22 | /// DIO1 IRQ mask 23 | pub dio1_irq_mask: IrqMask, 24 | /// DIO2 IRQ mask 25 | pub dio2_irq_mask: IrqMask, 26 | /// DIO3 IRW mask 27 | pub dio3_irq_mask: IrqMask, 28 | /// RF freq, calculated using crate::calc_rf_freq 29 | pub rf_freq: u32, 30 | /// RF frequency in MHz 31 | pub rf_frequency: u32, 32 | /// TCXO options. Set to None if not using TCXO 33 | pub tcxo_opts: Option<(TcxoVoltage, TcxoDelay)>, 34 | } 35 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod conf; 4 | pub mod op; 5 | pub mod reg; 6 | 7 | mod sx; 8 | pub use sx::*; 9 | -------------------------------------------------------------------------------- /src/op/calib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub struct CalibParam { 3 | inner: u8, 4 | } 5 | 6 | impl From for u8 { 7 | fn from(val: CalibParam) -> Self { 8 | val.inner 9 | } 10 | } 11 | 12 | impl From for CalibParam { 13 | fn from(val: u8) -> Self { 14 | Self { inner: val & 0x7F } 15 | } 16 | } 17 | 18 | impl CalibParam { 19 | pub const fn new( 20 | rc64k_en: bool, 21 | rc13_en: bool, 22 | pll_en: bool, 23 | adc_pulse_en: bool, 24 | adc_bulk_n_en: bool, 25 | adc_bulk_p_en: bool, 26 | image_en: bool, 27 | ) -> Self { 28 | let inner = (rc64k_en as u8) 29 | | (rc13_en as u8) << 1 30 | | (pll_en as u8) << 2 31 | | (adc_pulse_en as u8) << 3 32 | | (adc_bulk_n_en as u8) << 4 33 | | (adc_bulk_p_en as u8) << 5 34 | | (image_en as u8) << 6; 35 | Self { inner } 36 | } 37 | 38 | pub const fn all() -> Self { 39 | Self::new(true, true, true, true, true, true, true) 40 | } 41 | } 42 | 43 | #[derive(Copy, Clone)] 44 | #[repr(u16)] 45 | pub enum CalibImageFreq { 46 | MHz430_440 = 0x6B_6F, 47 | MHz470_510 = 0x75_81, 48 | MHz779_787 = 0xC1_C5, 49 | MHz863_870 = 0xD7_DB, 50 | MHz902_928 = 0xE1_E9, 51 | } 52 | 53 | impl From for [u8; 2] { 54 | fn from(val: CalibImageFreq) -> Self { 55 | (val as u16).to_be_bytes() 56 | } 57 | } 58 | 59 | impl CalibImageFreq { 60 | pub const fn from_rf_frequency(rf_frequency: u32) -> Self { 61 | match rf_frequency / 1000000 { 62 | 902..=928 => Self::MHz902_928, 63 | 863..=870 => Self::MHz863_870, 64 | 779..=787 => Self::MHz779_787, 65 | 470..=510 => Self::MHz470_510, 66 | 430..=440 => Self::MHz430_440, 67 | _ => Self::MHz902_928, // Default 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/op/err.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub struct DeviceErrors { 3 | inner: u16, 4 | } 5 | 6 | impl core::fmt::Debug for DeviceErrors { 7 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 8 | let rc64k_calib_err = self.rc64k_calib_err(); 9 | let rc13m_calib_err = self.rc13m_calib_err(); 10 | let pll_calib_err = self.pll_calib_err(); 11 | let adc_calib_err = self.adc_calib_err(); 12 | let img_calib_err = self.img_calib_err(); 13 | let xosc_start_err = self.xosc_start_err(); 14 | let pll_lock_err = self.pll_lock_err(); 15 | let pa_ramp_err = self.pa_ramp_err(); 16 | write!( 17 | f, 18 | "DeviceErrors {{inner: {:#016b}, rc64k_calib_err: {}, rc13m_calib_err: {}, pll_calib_err: {}, adc_calib_err: {}, img_calib_err: {}, xosc_start_err: {}, pll_lock_err: {}, pa_ramp_err: {}}}", 19 | self.inner, 20 | rc64k_calib_err, 21 | rc13m_calib_err, 22 | pll_calib_err, 23 | adc_calib_err, 24 | img_calib_err, 25 | xosc_start_err, 26 | pll_lock_err, 27 | pa_ramp_err, 28 | ) 29 | } 30 | } 31 | 32 | impl From for DeviceErrors { 33 | fn from(val: u16) -> Self { 34 | Self { inner: val } 35 | } 36 | } 37 | 38 | impl DeviceErrors { 39 | pub fn rc64k_calib_err(self) -> bool { 40 | (self.inner & 1 << 0) > 0 41 | } 42 | 43 | pub fn rc13m_calib_err(self) -> bool { 44 | (self.inner & 1 << 1) > 0 45 | } 46 | 47 | pub fn pll_calib_err(self) -> bool { 48 | (self.inner & 1 << 2) > 0 49 | } 50 | 51 | pub fn adc_calib_err(self) -> bool { 52 | (self.inner & 1 << 3) > 0 53 | } 54 | 55 | pub fn img_calib_err(self) -> bool { 56 | (self.inner & 1 << 4) > 0 57 | } 58 | 59 | pub fn xosc_start_err(self) -> bool { 60 | (self.inner & 1 << 5) > 0 61 | } 62 | 63 | pub fn pll_lock_err(self) -> bool { 64 | (self.inner & 1 << 6) > 0 65 | } 66 | 67 | pub fn pa_ramp_err(self) -> bool { 68 | (self.inner & 1 << 8) > 0 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/op/init.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | #[derive(Copy, Clone)] 3 | pub enum StandbyConfig { 4 | StbyRc = 0x00, 5 | StbyXOSC = 0x01, 6 | } 7 | -------------------------------------------------------------------------------- /src/op/irq.rs: -------------------------------------------------------------------------------- 1 | #[repr(u16)] 2 | #[derive(Copy, Clone)] 3 | pub enum IrqMaskBit { 4 | None = 0x0000, 5 | TxDone = 1 << 0, 6 | RxDone = 1 << 1, 7 | PreambleDetected = 1 << 2, 8 | SyncwordValid = 1 << 3, 9 | HeaderValid = 1 << 4, 10 | HeaderError = 1 << 5, 11 | CrcErr = 1 << 6, 12 | CadDone = 1 << 7, 13 | CadDetected = 1 << 8, 14 | Timeout = 1 << 9, 15 | All = 0xFFFF, 16 | } 17 | 18 | #[derive(Copy, Clone)] 19 | pub struct IrqMask { 20 | inner: u16, 21 | } 22 | 23 | impl IrqMask { 24 | pub const fn none() -> Self { 25 | Self { 26 | inner: IrqMaskBit::None as u16, 27 | } 28 | } 29 | 30 | pub const fn all() -> Self { 31 | Self { 32 | inner: IrqMaskBit::All as u16, 33 | } 34 | } 35 | 36 | pub const fn combine(self, bit: IrqMaskBit) -> Self { 37 | let inner = self.inner | bit as u16; 38 | Self { inner } 39 | } 40 | } 41 | 42 | impl From for u16 { 43 | fn from(val: IrqMask) -> Self { 44 | val.inner 45 | } 46 | } 47 | 48 | impl From for IrqMask { 49 | fn from(mask: u16) -> Self { 50 | Self { inner: mask } 51 | } 52 | } 53 | 54 | impl Default for IrqMask { 55 | fn default() -> Self { 56 | Self::none() 57 | } 58 | } 59 | 60 | #[derive(Copy, Clone)] 61 | pub struct IrqStatus { 62 | inner: u16, 63 | } 64 | 65 | impl From for IrqStatus { 66 | fn from(status: u16) -> Self { 67 | Self { inner: status } 68 | } 69 | } 70 | 71 | impl core::fmt::Debug for IrqStatus { 72 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 73 | write!( 74 | f, 75 | "IrqStatus {{inner: {:#016b}, tx_done: {}, rx_done: {}, preamble_detected: {}, syncword_valid: {}, header_valid: {}, header_error: {}, crc_err: {}, cad_done: {}, cad_detected: {}, timeout : {}}}", 76 | self.inner, 77 | self.tx_done(), 78 | self.rx_done(), 79 | self.preamble_detected(), 80 | self.syncword_valid(), 81 | self.header_valid(), 82 | self.header_error(), 83 | self.crc_err(), 84 | self.cad_done(), 85 | self.cad_detected(), 86 | self.timeout(), 87 | ) 88 | } 89 | } 90 | 91 | impl IrqStatus { 92 | pub fn tx_done(self) -> bool { 93 | (self.inner & IrqMaskBit::TxDone as u16) > 0 94 | } 95 | 96 | pub fn rx_done(self) -> bool { 97 | (self.inner & IrqMaskBit::RxDone as u16) > 0 98 | } 99 | 100 | pub fn preamble_detected(self) -> bool { 101 | (self.inner & IrqMaskBit::PreambleDetected as u16) > 0 102 | } 103 | 104 | pub fn syncword_valid(self) -> bool { 105 | (self.inner & IrqMaskBit::SyncwordValid as u16) > 0 106 | } 107 | 108 | pub fn header_valid(self) -> bool { 109 | (self.inner & IrqMaskBit::HeaderValid as u16) > 0 110 | } 111 | 112 | pub fn header_error(self) -> bool { 113 | (self.inner & IrqMaskBit::HeaderError as u16) > 0 114 | } 115 | 116 | pub fn crc_err(self) -> bool { 117 | (self.inner & IrqMaskBit::CrcErr as u16) > 0 118 | } 119 | 120 | pub fn cad_done(self) -> bool { 121 | (self.inner & IrqMaskBit::CadDone as u16) > 0 122 | } 123 | 124 | pub fn cad_detected(self) -> bool { 125 | (self.inner & IrqMaskBit::CadDetected as u16) > 0 126 | } 127 | 128 | pub fn timeout(self) -> bool { 129 | (self.inner & IrqMaskBit::Timeout as u16) > 0 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/op/mod.rs: -------------------------------------------------------------------------------- 1 | //! Defines the parameters used in every command detailed in chapter 13 2 | pub mod calib; 3 | pub mod err; 4 | pub mod init; 5 | pub mod irq; 6 | pub mod modulation; 7 | pub mod packet; 8 | pub mod rxtx; 9 | pub mod status; 10 | pub mod tcxo; 11 | 12 | pub use calib::*; 13 | pub use err::*; 14 | pub use init::*; 15 | pub use irq::*; 16 | pub use modulation::*; 17 | pub use packet::*; 18 | pub use rxtx::*; 19 | pub use status::*; 20 | pub use tcxo::*; 21 | -------------------------------------------------------------------------------- /src/op/modulation.rs: -------------------------------------------------------------------------------- 1 | pub struct ModParams { 2 | inner: [u8; 8], 3 | } 4 | 5 | impl From for [u8; 8] { 6 | fn from(val: ModParams) -> Self { 7 | val.inner 8 | } 9 | } 10 | 11 | pub use lora::*; 12 | 13 | mod lora { 14 | use super::ModParams; 15 | #[derive(Copy, Clone)] 16 | #[repr(u8)] 17 | pub enum LoRaSpreadFactor { 18 | SF5 = 0x05, 19 | SF6 = 0x06, 20 | SF7 = 0x07, 21 | SF8 = 0x08, 22 | SF9 = 0x09, 23 | SF10 = 0x0A, 24 | SF11 = 0x0B, 25 | SF12 = 0x0C, 26 | } 27 | 28 | impl From for LoRaSpreadFactor { 29 | fn from(value: u8) -> Self { 30 | match value { 31 | 0x05 => Self::SF5, 32 | 0x06 => Self::SF6, 33 | 0x07 => Self::SF7, 34 | 0x08 => Self::SF8, 35 | 0x09 => Self::SF9, 36 | 0x0A => Self::SF10, 37 | 0x0B => Self::SF11, 38 | 0x0C => Self::SF12, 39 | _ => panic!("Invalid LoRa spread factor"), 40 | } 41 | } 42 | } 43 | 44 | #[derive(Copy, Clone)] 45 | #[repr(u8)] 46 | pub enum LoRaBandWidth { 47 | /// 7.81 kHz 48 | BW7 = 0x00, 49 | /// 10.42 kHz 50 | BW10 = 0x08, 51 | /// 15.63 kHz 52 | BW15 = 0x01, 53 | /// 20.83 kHz 54 | BW20 = 0x09, 55 | /// 31.25 kHz 56 | BW31 = 0x02, 57 | /// 41.67 kHz 58 | BW41 = 0x0A, 59 | /// 62.50 kHz 60 | BW62 = 0x03, 61 | /// 125 kHz 62 | BW125 = 0x04, 63 | /// 250 kHz 64 | BW250 = 0x05, 65 | /// 500 kHz 66 | BW500 = 0x06, 67 | } 68 | 69 | #[derive(Copy, Clone)] 70 | #[repr(u8)] 71 | pub enum LoraCodingRate { 72 | CR4_5 = 0x01, 73 | CR4_6 = 0x02, 74 | CR4_7 = 0x03, 75 | CR4_8 = 0x04, 76 | } 77 | 78 | pub struct LoraModParams { 79 | spread_factor: LoRaSpreadFactor, 80 | bandwidth: LoRaBandWidth, 81 | coding_rate: LoraCodingRate, 82 | /// LowDataRateOptimize 83 | low_dr_opt: bool, 84 | } 85 | 86 | impl Default for LoraModParams { 87 | fn default() -> Self { 88 | Self { 89 | spread_factor: LoRaSpreadFactor::SF7, 90 | bandwidth: LoRaBandWidth::BW125, 91 | coding_rate: LoraCodingRate::CR4_5, 92 | low_dr_opt: false, 93 | } 94 | } 95 | } 96 | 97 | impl LoraModParams { 98 | pub fn set_spread_factor(mut self, spread_factor: LoRaSpreadFactor) -> Self { 99 | self.spread_factor = spread_factor; 100 | self 101 | } 102 | pub fn set_bandwidth(mut self, bandwidth: LoRaBandWidth) -> Self { 103 | self.bandwidth = bandwidth; 104 | self 105 | } 106 | pub fn set_coding_rate(mut self, coding_rate: LoraCodingRate) -> Self { 107 | self.coding_rate = coding_rate; 108 | self 109 | } 110 | 111 | pub fn set_low_dr_opt(mut self, low_dr_opt: bool) -> Self { 112 | self.low_dr_opt = low_dr_opt; 113 | self 114 | } 115 | } 116 | 117 | impl From for ModParams { 118 | fn from(val: LoraModParams) -> Self { 119 | ModParams { 120 | inner: [ 121 | val.spread_factor as u8, 122 | val.bandwidth as u8, 123 | val.coding_rate as u8, 124 | val.low_dr_opt as u8, 125 | 0x00, 126 | 0x00, 127 | 0x00, 128 | 0x00, 129 | ], 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/op/packet.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | #[derive(Copy, Clone)] 3 | pub enum PacketType { 4 | GFSK = 0x00, 5 | LoRa = 0x01, 6 | } 7 | 8 | pub struct PacketParams { 9 | inner: [u8; 9], 10 | } 11 | 12 | impl From for [u8; 9] { 13 | fn from(val: PacketParams) -> Self { 14 | val.inner 15 | } 16 | } 17 | 18 | pub use lora::*; 19 | 20 | mod lora { 21 | use super::PacketParams; 22 | 23 | #[repr(u8)] 24 | #[derive(Copy, Clone)] 25 | pub enum LoRaHeaderType { 26 | /// Variable length packet (explicit header) 27 | VarLen = 0x00, 28 | /// Fixed length packet (implicit header) 29 | FixedLen = 0x01, 30 | } 31 | 32 | #[repr(u8)] 33 | #[derive(Copy, Clone)] 34 | pub enum LoRaCrcType { 35 | /// CRC off 36 | CrcOff = 0x00, 37 | /// CRC on 38 | CrcOn = 0x01, 39 | } 40 | 41 | #[repr(u8)] 42 | #[derive(Copy, Clone)] 43 | pub enum LoRaInvertIq { 44 | /// Standard IQ setup 45 | Standard = 0x00, 46 | /// Inverted IQ setup 47 | Inverted = 0x01, 48 | } 49 | 50 | pub struct LoRaPacketParams { 51 | /// preamble length: number of symbols sent as preamble 52 | /// The preamble length is a 16-bit value which represents 53 | /// the number of LoRa® symbols which will be sent by the radio. 54 | pub preamble_len: u16, // 1, 2 55 | /// Header type. When the byte headerType is at 0x00, 56 | /// the payload length, coding rate and the header 57 | /// CRC will be added to the LoRa® header and transported 58 | /// to the receiver. 59 | pub header_type: LoRaHeaderType, // 3 60 | /// Size of the payload (in bytes) to transmit or maximum size of the 61 | /// payload that the receiver can accept. 62 | pub payload_len: u8, // 4 63 | /// CRC type 64 | pub crc_type: LoRaCrcType, // 5 65 | /// Invert IW 66 | pub invert_iq: LoRaInvertIq, 67 | } 68 | 69 | impl From for PacketParams { 70 | fn from(val: LoRaPacketParams) -> Self { 71 | let preamble_len = val.preamble_len.to_be_bytes(); 72 | 73 | PacketParams { 74 | inner: [ 75 | preamble_len[0], 76 | preamble_len[1], 77 | val.header_type as u8, 78 | val.payload_len, 79 | val.crc_type as u8, 80 | val.invert_iq as u8, 81 | 0x00, 82 | 0x00, 83 | 0x00, 84 | ], 85 | } 86 | } 87 | } 88 | 89 | impl Default for LoRaPacketParams { 90 | fn default() -> Self { 91 | Self { 92 | preamble_len: 0x0008, 93 | header_type: LoRaHeaderType::VarLen, 94 | payload_len: 0x00, 95 | crc_type: LoRaCrcType::CrcOff, 96 | invert_iq: LoRaInvertIq::Standard, 97 | } 98 | } 99 | } 100 | 101 | impl LoRaPacketParams { 102 | pub fn set_preamble_len(mut self, preamble_len: u16) -> Self { 103 | self.preamble_len = preamble_len; 104 | self 105 | } 106 | 107 | pub fn set_header_type(mut self, header_type: LoRaHeaderType) -> Self { 108 | self.header_type = header_type; 109 | self 110 | } 111 | 112 | pub fn set_payload_len(mut self, payload_len: u8) -> Self { 113 | self.payload_len = payload_len; 114 | self 115 | } 116 | 117 | pub fn set_crc_type(mut self, crc_type: LoRaCrcType) -> Self { 118 | self.crc_type = crc_type; 119 | self 120 | } 121 | 122 | pub fn set_invert_iq(mut self, invert_iq: LoRaInvertIq) -> Self { 123 | self.invert_iq = invert_iq; 124 | self 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/op/rxtx.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub struct RxTxTimeout { 3 | inner: [u8; 3], 4 | } 5 | 6 | impl From for [u8; 3] { 7 | fn from(val: RxTxTimeout) -> Self { 8 | val.inner 9 | } 10 | } 11 | 12 | impl RxTxTimeout { 13 | pub const fn from_ms(ms: u32) -> Self { 14 | let inner = ms << 6; 15 | let inner = inner.to_le_bytes(); 16 | let inner = [inner[2], inner[1], inner[0]]; 17 | Self { inner } 18 | } 19 | 20 | pub const fn continuous_rx() -> Self { 21 | Self { inner: [0xFF, 0xFF, 0xFF] } 22 | } 23 | } 24 | 25 | impl From for RxTxTimeout { 26 | fn from(val: u32) -> Self { 27 | let bytes = val.to_be_bytes(); 28 | Self { 29 | inner: [bytes[0], bytes[1], bytes[2]], 30 | } 31 | } 32 | } 33 | 34 | #[repr(u8)] 35 | #[derive(Copy, Clone)] 36 | pub enum RampTime { 37 | /// 10us 38 | Ramp10u = 0x00, 39 | /// 20us 40 | Ramp20u = 0x01, 41 | /// 40u 42 | Ramp40u = 0x02, 43 | /// 80us 44 | Ramp80u = 0x03, 45 | /// 200us 46 | Ramp200u = 0x04, 47 | /// 800us 48 | Ramp800u = 0x05, 49 | /// 1700us 50 | Ramp1700u = 0x06, 51 | /// 3400us 52 | Ramp3400u = 0x07, 53 | } 54 | 55 | pub struct TxParams { 56 | power_dbm: i8, 57 | ramp_time: RampTime, 58 | } 59 | 60 | impl Default for TxParams { 61 | fn default() -> Self { 62 | Self { 63 | power_dbm: 0, 64 | ramp_time: RampTime::Ramp10u, 65 | } 66 | } 67 | } 68 | 69 | impl From for [u8; 2] { 70 | fn from(val: TxParams) -> Self { 71 | [val.power_dbm as u8, val.ramp_time as u8] 72 | } 73 | } 74 | 75 | impl TxParams { 76 | /// The output power is defined as power in dBm in a range of 77 | /// - -17 (0xEF) to +14 (0x0E) dBm by step of 1 dB if low power PA is selected 78 | /// - -9 (0xF7) to +22 (0x16) dBm by step of 1 dB if high power PA is selected 79 | /// Selection between high power PA and low power PA is done with the command SetPaConfig and the parameter deviceSel. 80 | /// By default low power PA and +14 dBm are set. 81 | pub fn set_power_dbm(mut self, power_dbm: i8) -> Self { 82 | debug_assert!(power_dbm >= -17); 83 | debug_assert!(power_dbm <= 22); 84 | self.power_dbm = power_dbm; 85 | self 86 | } 87 | 88 | /// Set power ramp time 89 | pub fn set_ramp_time(mut self, ramp_time: RampTime) -> Self { 90 | self.ramp_time = ramp_time; 91 | self 92 | } 93 | } 94 | 95 | #[repr(u8)] 96 | #[derive(Copy, Clone)] 97 | pub enum DeviceSel { 98 | SX1262 = 0x00, 99 | SX1261 = 0x01, 100 | } 101 | 102 | pub struct PaConfig { 103 | pa_duty_cycle: u8, 104 | hp_max: u8, 105 | device_sel: DeviceSel, 106 | } 107 | 108 | impl From for [u8; 4] { 109 | fn from(val: PaConfig) -> Self { 110 | [val.pa_duty_cycle, val.hp_max, val.device_sel as u8, 0x01] 111 | } 112 | } 113 | 114 | impl Default for PaConfig { 115 | fn default() -> Self { 116 | Self { 117 | pa_duty_cycle: 0x00, 118 | hp_max: 0x00, 119 | device_sel: DeviceSel::SX1262, 120 | } 121 | } 122 | } 123 | 124 | impl PaConfig { 125 | pub fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> Self { 126 | self.pa_duty_cycle = pa_duty_cycle; 127 | self 128 | } 129 | 130 | pub fn set_hp_max(mut self, hp_max: u8) -> Self { 131 | self.hp_max = hp_max; 132 | self 133 | } 134 | 135 | pub fn set_device_sel(mut self, device_sel: DeviceSel) -> Self { 136 | self.device_sel = device_sel; 137 | self 138 | } 139 | } 140 | 141 | #[derive(Debug)] 142 | pub struct RxBufferStatus { 143 | payload_length_rx: u8, 144 | rx_start_buffer_pointer: u8, 145 | } 146 | 147 | impl From<[u8; 2]> for RxBufferStatus { 148 | fn from(raw: [u8; 2]) -> Self { 149 | Self { 150 | payload_length_rx: raw[0], 151 | rx_start_buffer_pointer: raw[1], 152 | } 153 | } 154 | } 155 | 156 | impl RxBufferStatus { 157 | pub fn payload_length_rx(&self) -> u8 { 158 | self.payload_length_rx 159 | } 160 | 161 | pub fn rx_start_buffer_pointer(&self) -> u8 { 162 | self.rx_start_buffer_pointer 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/op/status.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub struct Status { 3 | inner: u8, 4 | } 5 | 6 | impl core::fmt::Debug for Status { 7 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 8 | let chip_mode = self.chip_mode(); 9 | let command_status = self.command_status(); 10 | write!( 11 | f, 12 | "Status {{inner: {:#08b}, chip_mode: {:?}, command_status: {:?}}}", 13 | self.inner, chip_mode, command_status 14 | ) 15 | } 16 | } 17 | 18 | #[repr(u8)] 19 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 20 | pub enum ChipMode { 21 | StbyRC = 0x02, 22 | StbyXOSC = 0x03, 23 | FS = 0x04, 24 | RX = 0x05, 25 | TX = 0x06, 26 | } 27 | 28 | #[repr(u8)] 29 | #[derive(Copy, Clone, Debug)] 30 | pub enum CommandStatus { 31 | DataAvailable = 0x02, 32 | CommandTimeout = 0x03, 33 | CommandProcessingError = 0x04, 34 | FailureToExecute = 0x05, 35 | CommandTxDone = 0x06, 36 | } 37 | 38 | impl From for Status { 39 | fn from(b: u8) -> Self { 40 | Self { inner: b } 41 | } 42 | } 43 | 44 | impl Status { 45 | pub fn chip_mode(&self) -> Option { 46 | use ChipMode::*; 47 | match (self.inner & 0x70) >> 4 { 48 | 0x02 => Some(StbyRC), 49 | 0x03 => Some(StbyXOSC), 50 | 0x04 => Some(FS), 51 | 0x05 => Some(RX), 52 | 0x06 => Some(TX), 53 | _ => None, 54 | } 55 | } 56 | 57 | pub fn command_status(self) -> Option { 58 | use CommandStatus::*; 59 | match (self.inner & 0x0E) >> 1 { 60 | 0x02 => Some(DataAvailable), 61 | 0x03 => Some(CommandTimeout), 62 | 0x04 => Some(CommandProcessingError), 63 | 0x05 => Some(FailureToExecute), 64 | 0x06 => Some(CommandTxDone), 65 | _ => None, 66 | } 67 | } 68 | } 69 | 70 | #[derive(Copy, Clone, Debug)] 71 | pub struct Stats { 72 | pub status: Status, 73 | pub rx_pkt: u16, 74 | pub crc_error: u16, 75 | pub header_error: u16, 76 | } 77 | 78 | impl From<[u8; 7]> for Stats { 79 | fn from(b: [u8; 7]) -> Self { 80 | Self { 81 | status: b[0].into(), 82 | rx_pkt: u16::from_be_bytes([b[1], b[2]]), 83 | crc_error: u16::from_be_bytes([b[3], b[4]]), 84 | header_error: u16::from_be_bytes([b[5], b[6]]), 85 | } 86 | } 87 | } 88 | 89 | #[derive(Copy, Clone, Debug)] 90 | pub struct PacketStatus { 91 | rssi_pkt: u8, 92 | snr_pkt: i8, 93 | signal_rssi_pkt: u8, 94 | } 95 | 96 | impl From<[u8; 3]> for PacketStatus { 97 | fn from(b: [u8; 3]) -> Self { 98 | Self { 99 | rssi_pkt: b[0], 100 | snr_pkt: i8::from_be_bytes([b[1]]), 101 | signal_rssi_pkt: b[2], 102 | } 103 | } 104 | } 105 | 106 | impl PacketStatus { 107 | pub fn rssi_pkt(&self) -> f32 { 108 | self.rssi_pkt as f32 / -2.0 109 | } 110 | 111 | pub fn snr_pkt(&self) -> f32 { 112 | self.snr_pkt as f32 / 4.0 113 | } 114 | 115 | pub fn signal_rssi_pkt(&self) -> f32 { 116 | self.signal_rssi_pkt as f32 / -2.0 117 | } 118 | } -------------------------------------------------------------------------------- /src/op/tcxo.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | #[derive(Copy, Clone)] 3 | pub enum TcxoVoltage { 4 | Volt1_6 = 0x00, 5 | Volt1_7 = 0x01, 6 | Volt1_8 = 0x02, 7 | Volt2_2 = 0x03, 8 | Volt2_4 = 0x04, 9 | Volt2_7 = 0x05, 10 | Volt3_0 = 0x06, 11 | Volt3_3 = 0x07, 12 | } 13 | 14 | #[derive(Copy, Clone)] 15 | pub struct TcxoDelay { 16 | inner: [u8; 3], 17 | } 18 | 19 | impl From for [u8; 3] { 20 | fn from(val: TcxoDelay) -> Self { 21 | val.inner 22 | } 23 | } 24 | 25 | impl From<[u8; 3]> for TcxoDelay { 26 | fn from(b: [u8; 3]) -> Self { 27 | Self { inner: b } 28 | } 29 | } 30 | 31 | impl TcxoDelay { 32 | pub const fn from_ms(ms: u32) -> Self { 33 | let inner = ms << 6; 34 | let inner = inner.to_le_bytes(); 35 | let inner = [inner[2], inner[1], inner[0]]; 36 | Self { inner } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/reg.rs: -------------------------------------------------------------------------------- 1 | //! Registers as defined in chapter 12 2 | #[allow(dead_code)] 3 | #[repr(u16)] 4 | /// Every register defined in the SX126X datasheet 5 | /// See table 12-1 in the datasheet 6 | pub enum Register { 7 | /// Non-standard DIOx control 8 | DioxOutputEnable = 0x0580, 9 | /// Non-standard DIOx control 10 | DioxInputEnable = 0x0583, 11 | /// Non-standard DIOx control 12 | DioxPullUpControl = 0x0584, 13 | /// Non-standard DIOx control 14 | DioxPullDownControl = 0x0585, 15 | /// Initial value used for the whitening LFSR in 16 | /// FSK mode; MSB. The user should not change the value of the 7 17 | /// MSB of this register 18 | WhiteningInitialValueMsb = 0x06B8, 19 | /// Initial value used for the whitening LFSR in 20 | /// FSK mode; LSB 21 | WhiteningInitialValueLsb = 0x06B9, 22 | /// Initial value used for the polynomial used to 23 | /// compute the CRC in FSK mode; LSB 24 | CrcMsbInitialValue = 0x06BC, 25 | /// Initial value used for the polynomial used to 26 | /// compute the CRC in FSK mode; LSB 27 | CrcLsbInitialValue = 0x006BD, 28 | /// Polynomial used to compute the CRC in FSK mode; MSB 29 | CrcMsbPolynomialValue = 0x06BE, 30 | /// Polynomial used to compute the CRC in FSK mode; LSB 31 | CrcLsbPolynomialValue = 0x06BF, 32 | /// 1st byte of the Sync Word in FSK mode 33 | SyncWord0 = 0x06C0, 34 | /// 2nd byte of the Sync Word in FSK mode 35 | SyncWord1 = 0x06C1, 36 | /// 3rd byte of the Sync Word in FSK mode 37 | SyncWord2 = 0x06C2, 38 | /// 4th byte of the Sync Word in FSK mode 39 | SyncWord3 = 0x06C3, 40 | /// 5th byte of the Sync Word in FSK mode 41 | SyncWord4 = 0x06C4, 42 | /// 6th byte of the Sync Word in FSK mode 43 | SyncWord5 = 0x06C5, 44 | /// 7th byte of the Sync Word in FSK mode 45 | SyncWord6 = 0x06C6, 46 | /// 8th byte of the Sync Word in FSK mode 47 | SyncWord7 = 0x06C7, 48 | /// Node Address used in FSK mode 49 | NodeAddress = 0x06CD, 50 | /// Broadcast Address used in FSK mode 51 | BroadcastAddress = 0x06CE, 52 | /// Optimize the inverted IQ operation 53 | IqPolaritySetup = 0x0736, 54 | /// Differantiate the LoRa signal for Public or Private Network; MSB 55 | /// Set to 0x3444 for Public Netwok 56 | /// Set to 0x1424 for Private Network 57 | LoRaSyncWordMsb = 0x0740, 58 | /// Differantiate the LoRa signal for Public or Private Network; LSB 59 | /// Set to 0x3444 for Public Netwok 60 | /// Set to 0x1424 for Private Network 61 | LoRaSyncWordLsb = 0x0741, 62 | /// Can be used to get a 32-bit random numer; 1st byte 63 | RandomNumberGen0 = 0x0819, 64 | /// Can be used to get a 32-bit random numer; 2nd byte 65 | RandomNumberGen1 = 0x081A, 66 | /// Can be used to get a 32-bit random numer; 3rd byte 67 | RandomNumberGen2 = 0x081B, 68 | /// Can be used to get a 32-bit random numer; 4th byte 69 | RandomNumberGen3 = 0x081C, 70 | /// Refer to Section 15 of the Data Sheet 71 | TxModulaton = 0x0889, 72 | /// Set the gain used in Rx mode: 73 | /// Rx Power Saving gain: 0x94, 74 | /// Rx Boosted gain: 0x96 75 | RxGain = 0x08AC, 76 | /// Refer to Section 15 of the Data Sheet 77 | TxClampConfig = 0x08D8, 78 | /// Set the Over Current Protection level. 79 | /// The value is changed internally depending 80 | /// on the device selected. Default values are: 81 | /// SX1262: 0x38 (140mA) 82 | /// SX1261; 0x18: (60mA) 83 | OcpConfiguration = 0x08E7, 84 | /// Enable or disable RTC Timer 85 | RtcControl = 0x0902, 86 | /// Value of the trimming cap on XTA pin. 87 | /// This register should only be changed while 88 | /// the radio is in STDBY_XOSC mode 89 | XtaTrim = 0x0911, 90 | /// Value of the trimming cap on XTB pin. 91 | /// This register should only be changed while 92 | /// the radio is in STDBY_XOSC mode 93 | XtbTrim = 0x0912, 94 | /// Non-standard DIO3 control 95 | Dio3OutputVoltageControl = 0x0920, 96 | /// Used to clear events 97 | EventMask = 0x0944, 98 | } 99 | -------------------------------------------------------------------------------- /src/sx/err.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Debug}; 2 | 3 | pub enum SpiError { 4 | Write(TSPIERR), 5 | Transfer(TSPIERR), 6 | } 7 | 8 | impl Debug for SpiError { 9 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 10 | match self { 11 | Self::Write(err) => write!(f, "Write({:?})", err), 12 | Self::Transfer(err) => write!(f, "Transfer({:?})", err), 13 | } 14 | } 15 | } 16 | 17 | pub enum PinError { 18 | Output(TPINERR), 19 | Input(TPINERR), 20 | } 21 | 22 | impl Debug for PinError { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Self::Output(err) => write!(f, "Output({:?})", err), 26 | Self::Input(err) => write!(f, "Input({:?})", err), 27 | } 28 | } 29 | } 30 | 31 | pub enum SxError { 32 | Spi(SpiError), 33 | Pin(PinError), 34 | } 35 | 36 | impl Debug for SxError { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | match self { 39 | Self::Spi(err) => write!(f, "Spi({:?})", err), 40 | Self::Pin(err) => write!(f, "Pin({:?})", err), 41 | } 42 | } 43 | } 44 | 45 | impl From> for SxError { 46 | fn from(spi_err: SpiError) -> Self { 47 | SxError::Spi(spi_err) 48 | } 49 | } 50 | 51 | impl From> for SxError { 52 | fn from(spi_err: PinError) -> Self { 53 | SxError::Pin(spi_err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/sx/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod err; 2 | 3 | use core::convert::TryInto; 4 | use embedded_hal::digital::InputPin; 5 | use embedded_hal::digital::OutputPin; 6 | use embedded_hal::spi::Operation; 7 | use embedded_hal::spi::SpiDevice; 8 | use err::SpiError; 9 | 10 | use crate::conf::Config; 11 | use crate::op::*; 12 | use crate::reg::*; 13 | // use err::OutputPinError; 14 | 15 | use self::err::{PinError, SxError}; 16 | 17 | type Pins = (TNRST, TBUSY, TANT, TDIO1); 18 | 19 | const NOP: u8 = 0x00; 20 | 21 | /// Calculates the rf_freq value that should be passed to SX126x::set_rf_frequency 22 | /// based on the desired RF frequency and the XTAL frequency. 23 | /// 24 | /// Example calculation for 868MHz: 25 | /// 13.4.1.: RFfrequecy = (RFfreq * Fxtal) / 2^25 = 868M 26 | /// -> RFfreq = 27 | /// -> RFfrequecy ~ ((RFfreq >> 12) * (Fxtal >> 12)) >> 1 28 | pub fn calc_rf_freq(rf_frequency: f32, f_xtal: f32) -> u32 { 29 | (rf_frequency * (33554432. / f_xtal)) as u32 30 | } 31 | 32 | /// Wrapper around a Semtech SX1261/62 LoRa modem 33 | pub struct SX126x { 34 | spi: TSPI, 35 | nrst_pin: TNRST, 36 | busy_pin: TBUSY, 37 | ant_pin: TANT, 38 | dio1_pin: TDIO1, 39 | } 40 | 41 | impl SX126x 42 | where 43 | TPINERR: core::fmt::Debug, 44 | TSPI: SpiDevice, 45 | TNRST: OutputPin, 46 | TBUSY: InputPin, 47 | TANT: OutputPin, 48 | TDIO1: InputPin, 49 | { 50 | // Create a new SX126x 51 | pub fn new(spi: TSPI, pins: Pins) -> Self { 52 | let (nrst_pin, busy_pin, ant_pin, dio1_pin) = pins; 53 | Self { 54 | spi, 55 | nrst_pin, 56 | busy_pin, 57 | ant_pin, 58 | dio1_pin, 59 | } 60 | } 61 | 62 | // Initialize and configure the SX126x using the provided Config 63 | pub fn init(&mut self, conf: Config) -> Result<(), SxError> { 64 | // Reset the sx 65 | self.reset()?; 66 | self.wait_on_busy()?; 67 | 68 | // 1. If not in STDBY_RC mode, then go to this mode with the command SetStandby(...) 69 | self.set_standby(crate::op::StandbyConfig::StbyRc)?; 70 | self.wait_on_busy()?; 71 | 72 | // 2. Define the protocol (LoRa® or FSK) with the command SetPacketType(...) 73 | self.set_packet_type(conf.packet_type)?; 74 | self.wait_on_busy()?; 75 | 76 | // 3. Define the RF frequency with the command SetRfFrequency(...) 77 | self.set_rf_frequency(conf.rf_freq)?; 78 | self.wait_on_busy()?; 79 | 80 | if let Some((tcxo_voltage, tcxo_delay)) = conf.tcxo_opts { 81 | self.set_dio3_as_tcxo_ctrl(tcxo_voltage, tcxo_delay)?; 82 | self.wait_on_busy()?; 83 | } 84 | 85 | // Calibrate 86 | self.calibrate(conf.calib_param)?; 87 | self.wait_on_busy()?; 88 | self.calibrate_image(CalibImageFreq::from_rf_frequency(conf.rf_frequency))?; 89 | self.wait_on_busy()?; 90 | 91 | // 4. Define the Power Amplifier configuration with the command SetPaConfig(...) 92 | self.set_pa_config(conf.pa_config)?; 93 | self.wait_on_busy()?; 94 | 95 | // 5. Define output power and ramping time with the command SetTxParams(...) 96 | self.set_tx_params(conf.tx_params)?; 97 | self.wait_on_busy()?; 98 | 99 | // 6. Define where the data payload will be stored with the command SetBufferBaseAddress(...) 100 | self.set_buffer_base_address(0x00, 0x00)?; 101 | self.wait_on_busy()?; 102 | 103 | // 7. Send the payload to the data buffer with the command WriteBuffer(...) 104 | // This is done later in SX126x::write_bytes 105 | 106 | // 8. Define the modulation parameter according to the chosen protocol with the command SetModulationParams(...) 1 107 | self.set_mod_params(conf.mod_params)?; 108 | self.wait_on_busy()?; 109 | 110 | // 9. Define the frame format to be used with the command SetPacketParams(...) 2 111 | if let Some(packet_params) = conf.packet_params { 112 | self.set_packet_params(packet_params)?; 113 | self.wait_on_busy()?; 114 | } 115 | 116 | // 10. Configure DIO and IRQ: use the command SetDioIrqParams(...) to select TxDone IRQ and map this IRQ to a DIO (DIO1, 117 | // DIO2 or DIO3) 118 | self.set_dio_irq_params( 119 | conf.dio1_irq_mask, 120 | conf.dio1_irq_mask, 121 | conf.dio2_irq_mask, 122 | conf.dio3_irq_mask, 123 | )?; 124 | self.wait_on_busy()?; 125 | self.set_dio2_as_rf_switch_ctrl(true)?; 126 | self.wait_on_busy()?; 127 | 128 | // 11. Define Sync Word value: use the command WriteReg(...) to write the value of the register via direct register access 129 | self.set_sync_word(conf.sync_word)?; 130 | self.wait_on_busy()?; 131 | 132 | // The rest of the steps are done by the user 133 | Ok(()) 134 | } 135 | 136 | /// Set the LoRa Sync word 137 | /// Use 0x3444 for public networks like TTN 138 | /// Use 0x1424 for private networks 139 | pub fn set_sync_word(&mut self, sync_word: u16) -> Result<(), SxError> { 140 | self.write_register(Register::LoRaSyncWordMsb, &sync_word.to_be_bytes()) 141 | } 142 | 143 | /// Set the modem packet type, which can be either GFSK of LoRa 144 | /// Note: GFSK is not fully supported by this crate at the moment 145 | pub fn set_packet_type( 146 | &mut self, 147 | packet_type: PacketType, 148 | ) -> Result<(), SxError> { 149 | self.spi 150 | .write(&[0x8A, packet_type as u8]) 151 | .map_err(SpiError::Write) 152 | .map_err(Into::into) 153 | } 154 | 155 | /// Put the modem in standby mode 156 | pub fn set_standby( 157 | &mut self, 158 | 159 | standby_config: StandbyConfig, 160 | ) -> Result<(), SxError> { 161 | self.spi 162 | .write(&[0x80, standby_config as u8]) 163 | .map_err(SpiError::Write) 164 | .map_err(Into::into) 165 | } 166 | 167 | /// Get the current status of the modem 168 | pub fn get_status(&mut self) -> Result> { 169 | let mut result = [0xC0, NOP]; 170 | self.spi 171 | .transfer_in_place(&mut result) 172 | .map_err(SpiError::Transfer)?; 173 | 174 | Ok(result[1].into()) 175 | } 176 | 177 | pub fn set_fs(&mut self) -> Result<(), SxError> { 178 | self.spi.write(&[0xC1]).map_err(SpiError::Write)?; 179 | Ok(()) 180 | } 181 | 182 | pub fn get_stats(&mut self) -> Result> { 183 | let mut result = [0x10, NOP, NOP, NOP, NOP, NOP, NOP, NOP]; 184 | self.spi 185 | .transfer_in_place(&mut result) 186 | .map_err(SpiError::Transfer)?; 187 | 188 | Ok(TryInto::<[u8; 7]>::try_into(&result[1..]).unwrap().into()) 189 | } 190 | 191 | /// Calibrate image 192 | pub fn calibrate_image( 193 | &mut self, 194 | 195 | freq: CalibImageFreq, 196 | ) -> Result<(), SxError> { 197 | let freq: [u8; 2] = freq.into(); 198 | let mut ops = [Operation::Write(&[0x98]), Operation::Write(&freq)]; 199 | self.spi 200 | .transaction(&mut ops) 201 | .map_err(SpiError::Write) 202 | .map_err(Into::into) 203 | } 204 | 205 | /// Calibrate modem 206 | pub fn calibrate(&mut self, calib_param: CalibParam) -> Result<(), SxError> { 207 | self.spi 208 | .write(&[0x89, calib_param.into()]) 209 | .map_err(SpiError::Write) 210 | .map_err(Into::into) 211 | } 212 | 213 | /// Write data into a register 214 | pub fn write_register( 215 | &mut self, 216 | 217 | register: Register, 218 | data: &[u8], 219 | ) -> Result<(), SxError> { 220 | let start_addr = (register as u16).to_be_bytes(); 221 | let mut ops = [ 222 | Operation::Write(&[0x0D]), 223 | Operation::Write(&start_addr), 224 | Operation::Write(data), 225 | ]; 226 | 227 | self.spi.transaction(&mut ops).map_err(SpiError::Write)?; 228 | Ok(()) 229 | } 230 | 231 | /// Read data from a register 232 | pub fn read_register( 233 | &mut self, 234 | 235 | start_addr: u16, 236 | result: &mut [u8], 237 | ) -> Result<(), SxError> { 238 | debug_assert!(!result.is_empty()); 239 | let start_addr = start_addr.to_be_bytes(); 240 | 241 | let mut ops = [ 242 | Operation::Write(&[0x1D]), 243 | Operation::Write(&start_addr), 244 | Operation::Read(result), 245 | ]; 246 | 247 | self.spi.transaction(&mut ops).map_err(SpiError::Transfer)?; 248 | Ok(()) 249 | } 250 | 251 | /// Write data into the buffer at the defined offset 252 | pub fn write_buffer( 253 | &mut self, 254 | 255 | offset: u8, 256 | data: &[u8], 257 | ) -> Result<(), SxError> { 258 | let header = [0x0E, offset]; 259 | let mut ops = [Operation::Write(&header), Operation::Write(data)]; 260 | self.spi 261 | .transaction(&mut ops) 262 | .map_err(SpiError::Write) 263 | .map_err(Into::into) 264 | } 265 | 266 | /// Read data from the data from the defined offset 267 | pub fn read_buffer( 268 | &mut self, 269 | 270 | offset: u8, 271 | result: &mut [u8], 272 | ) -> Result<(), SxError> { 273 | let header = [0x1E, offset, NOP]; 274 | let mut ops = [Operation::Write(&header), Operation::Read(result)]; 275 | self.spi 276 | .transaction(&mut ops) 277 | .map_err(SpiError::Transfer) 278 | .map_err(Into::into) 279 | } 280 | 281 | /// Configure the dio2 pin as RF control switch 282 | pub fn set_dio2_as_rf_switch_ctrl( 283 | &mut self, 284 | 285 | enable: bool, 286 | ) -> Result<(), SxError> { 287 | self.spi 288 | .write(&[0x9D, enable as u8]) 289 | .map_err(SpiError::Write) 290 | .map_err(Into::into) 291 | } 292 | 293 | pub fn get_packet_status(&mut self) -> Result> { 294 | let header = [0x14, NOP]; 295 | let mut result = [NOP; 3]; 296 | let mut ops = [Operation::Write(&header), Operation::Read(&mut result)]; 297 | self.spi.transaction(&mut ops).map_err(SpiError::Transfer)?; 298 | 299 | Ok(result.into()) 300 | } 301 | 302 | /// Configure the dio3 pin as TCXO control switch 303 | pub fn set_dio3_as_tcxo_ctrl( 304 | &mut self, 305 | 306 | tcxo_voltage: TcxoVoltage, 307 | tcxo_delay: TcxoDelay, 308 | ) -> Result<(), SxError> { 309 | let header = [0x97, tcxo_voltage as u8]; 310 | let tcxo_delay: [u8; 3] = tcxo_delay.into(); 311 | let mut ops = [Operation::Write(&header), Operation::Write(&tcxo_delay)]; 312 | self.spi 313 | .transaction(&mut ops) 314 | .map_err(SpiError::Write) 315 | .map_err(Into::into) 316 | } 317 | 318 | /// Clear device error register 319 | pub fn clear_device_errors(&mut self) -> Result<(), SxError> { 320 | self.spi 321 | .write(&[0x07, NOP, NOP]) 322 | .map_err(SpiError::Write) 323 | .map_err(Into::into) 324 | } 325 | 326 | /// Get current device errors 327 | pub fn get_device_errors(&mut self) -> Result> { 328 | let mut result = [0x17, NOP, NOP, NOP]; 329 | self.spi 330 | .transfer_in_place(&mut result) 331 | .map_err(SpiError::Transfer)?; 332 | Ok(DeviceErrors::from(u16::from_le_bytes( 333 | result[2..].try_into().unwrap(), 334 | ))) 335 | } 336 | 337 | /// Reset the device py pulling nrst low for a while 338 | pub fn reset(&mut self) -> Result<(), SxError> { 339 | critical_section::with(|_| { 340 | self.nrst_pin.set_low().map_err(PinError::Output)?; 341 | // 8.1: The pin should be held low for typically 100 μs for the Reset to happen 342 | self.spi 343 | .transaction(&mut [Operation::DelayNs(200_000)]) 344 | .map_err(SpiError::Write)?; 345 | self.nrst_pin 346 | .set_high() 347 | .map_err(PinError::Output) 348 | .map_err(Into::into) 349 | }) 350 | } 351 | 352 | /// Enable antenna 353 | pub fn set_ant_enabled(&mut self, enabled: bool) -> Result<(), TPINERR> { 354 | if enabled { 355 | self.ant_pin.set_high() 356 | } else { 357 | self.ant_pin.set_low() 358 | } 359 | } 360 | 361 | /// Configure IRQ 362 | pub fn set_dio_irq_params( 363 | &mut self, 364 | 365 | irq_mask: IrqMask, 366 | dio1_mask: IrqMask, 367 | dio2_mask: IrqMask, 368 | dio3_mask: IrqMask, 369 | ) -> Result<(), SxError> { 370 | let irq = (Into::::into(irq_mask)).to_be_bytes(); 371 | let dio1 = (Into::::into(dio1_mask)).to_be_bytes(); 372 | let dio2 = (Into::::into(dio2_mask)).to_be_bytes(); 373 | let dio3 = (Into::::into(dio3_mask)).to_be_bytes(); 374 | let mut ops = [ 375 | Operation::Write(&[0x08]), 376 | Operation::Write(&irq), 377 | Operation::Write(&dio1), 378 | Operation::Write(&dio2), 379 | Operation::Write(&dio3), 380 | ]; 381 | self.spi 382 | .transaction(&mut ops) 383 | .map_err(SpiError::Transfer) 384 | .map_err(Into::into) 385 | } 386 | 387 | /// Get the current IRQ status 388 | pub fn get_irq_status(&mut self) -> Result> { 389 | let mut status = [NOP, NOP, NOP]; 390 | let mut ops = [Operation::Write(&[0x12]), Operation::Read(&mut status)]; 391 | self.spi.transaction(&mut ops).map_err(SpiError::Transfer)?; 392 | let irq_status: [u8; 2] = [status[1], status[2]]; 393 | Ok(u16::from_be_bytes(irq_status).into()) 394 | } 395 | 396 | /// Clear the IRQ status 397 | pub fn clear_irq_status(&mut self, mask: IrqMask) -> Result<(), SxError> { 398 | let mask = Into::::into(mask).to_be_bytes(); 399 | let mut ops = [Operation::Write(&[0x02]), Operation::Write(&mask)]; 400 | self.spi 401 | .transaction(&mut ops) 402 | .map_err(SpiError::Write) 403 | .map_err(Into::into) 404 | } 405 | 406 | /// Put the device in TX mode. It will start sending the data written in the buffer, 407 | /// starting at the configured offset 408 | pub fn set_tx(&mut self, timeout: RxTxTimeout) -> Result> { 409 | let mut buf = [0x83u8; 4]; 410 | let timeout: [u8; 3] = timeout.into(); 411 | buf[1..].copy_from_slice(&timeout); 412 | 413 | self.spi 414 | .transfer_in_place(&mut buf) 415 | .map_err(SpiError::Transfer)?; 416 | Ok(timeout[1].into()) 417 | } 418 | 419 | pub fn set_rx(&mut self, timeout: RxTxTimeout) -> Result> { 420 | let mut buf = [0x82u8; 4]; 421 | let timeout: [u8; 3] = timeout.into(); 422 | buf[1..].copy_from_slice(&timeout); 423 | 424 | self.spi.write(&buf).map_err(SpiError::Transfer)?; 425 | Ok(timeout[0].into()) 426 | } 427 | 428 | /// Set packet parameters 429 | pub fn set_packet_params( 430 | &mut self, 431 | 432 | params: PacketParams, 433 | ) -> Result<(), SxError> { 434 | let params: [u8; 9] = params.into(); 435 | let mut ops = [Operation::Write(&[0x8C]), Operation::Write(¶ms)]; 436 | self.spi 437 | .transaction(&mut ops) 438 | .map_err(SpiError::Write) 439 | .map_err(Into::into) 440 | } 441 | 442 | /// Set modulation parameters 443 | pub fn set_mod_params(&mut self, params: ModParams) -> Result<(), SxError> { 444 | let params: [u8; 8] = params.into(); 445 | let mut ops = [Operation::Write(&[0x8B]), Operation::Write(¶ms)]; 446 | self.spi 447 | .transaction(&mut ops) 448 | .map_err(SpiError::Write) 449 | .map_err(Into::into) 450 | } 451 | 452 | /// Set TX parameters 453 | pub fn set_tx_params(&mut self, params: TxParams) -> Result<(), SxError> { 454 | let params: [u8; 2] = params.into(); 455 | let mut ops = [Operation::Write(&[0x8E]), Operation::Write(¶ms)]; 456 | self.spi 457 | .transaction(&mut ops) 458 | .map_err(SpiError::Write) 459 | .map_err(Into::into) 460 | } 461 | 462 | /// Set RF frequency. This writes the passed rf_freq directly to the modem. 463 | /// Use sx1262::calc_rf_freq to calulate the correct value based 464 | /// On the XTAL frequency and the desired RF frequency 465 | pub fn set_rf_frequency(&mut self, rf_freq: u32) -> Result<(), SxError> { 466 | let rf_freq = rf_freq.to_be_bytes(); 467 | let mut ops = [Operation::Write(&[0x86]), Operation::Write(&rf_freq)]; 468 | self.spi 469 | .transaction(&mut ops) 470 | .map_err(SpiError::Write) 471 | .map_err(Into::into) 472 | } 473 | 474 | /// Set Power Amplifier configuration 475 | pub fn set_pa_config(&mut self, pa_config: PaConfig) -> Result<(), SxError> { 476 | let pa_config: [u8; 4] = pa_config.into(); 477 | let mut ops = [Operation::Write(&[0x95]), Operation::Write(&pa_config)]; 478 | self.spi 479 | .transaction(&mut ops) 480 | .map_err(SpiError::Write) 481 | .map_err(Into::into) 482 | } 483 | 484 | /// Configure the base addresses in the buffer 485 | pub fn set_buffer_base_address( 486 | &mut self, 487 | 488 | tx_base_addr: u8, 489 | rx_base_addr: u8, 490 | ) -> Result<(), SxError> { 491 | self.spi 492 | .write(&[0x8F, tx_base_addr, rx_base_addr]) 493 | .map_err(SpiError::Write) 494 | .map_err(Into::into) 495 | } 496 | 497 | /// High level method to send a message. This methods writes the data in the buffer, 498 | /// puts the device in TX mode, and waits until the devices 499 | /// is done sending the data or a timeout occurs. 500 | /// Please note that this method updates the packet params 501 | pub fn write_bytes( 502 | &mut self, 503 | data: &[u8], 504 | timeout: RxTxTimeout, 505 | preamble_len: u16, 506 | crc_type: packet::LoRaCrcType, 507 | ) -> Result> { 508 | use packet::LoRaPacketParams; 509 | // Write data to buffer 510 | self.write_buffer(0x00, data)?; 511 | 512 | // Set packet params 513 | let params = LoRaPacketParams::default() 514 | .set_preamble_len(preamble_len) 515 | .set_payload_len(data.len() as u8) 516 | .set_crc_type(crc_type) 517 | .into(); 518 | 519 | self.set_packet_params(params)?; 520 | 521 | // Set tx mode 522 | let status = self.set_tx(timeout)?; 523 | // Wait for busy line to go low 524 | self.wait_on_busy().map_err(SxError::Spi)?; 525 | // Wait on dio1 going high 526 | self.wait_on_dio1()?; 527 | // Clear IRQ 528 | self.clear_irq_status(IrqMask::all())?; 529 | // Write completed! 530 | Ok(status) 531 | } 532 | 533 | /// Get Rx buffer status, containing the length of the last received packet 534 | /// and the address of the first byte received. 535 | pub fn get_rx_buffer_status(&mut self) -> Result> { 536 | let mut result = [0x13, NOP, NOP, NOP]; 537 | self.spi 538 | .transfer_in_place(&mut result) 539 | .map_err(SpiError::Transfer)?; 540 | Ok(TryInto::<[u8; 2]>::try_into(&result[2..]).unwrap().into()) 541 | } 542 | 543 | /// Busily wait for the busy pin to go low 544 | pub fn wait_on_busy(&mut self) -> Result<(), SpiError> { 545 | self.spi 546 | .transaction(&mut [Operation::DelayNs(1000)]) 547 | .map_err(SpiError::Transfer)?; 548 | while let Ok(true) = self.busy_pin.is_high() { 549 | // NOP 550 | } 551 | Ok(()) 552 | } 553 | 554 | /// Busily wait for the dio1 pin to go high 555 | fn wait_on_dio1(&mut self) -> Result<(), PinError> { 556 | while let Ok(true) = self.dio1_pin.is_low() { 557 | // NOP 558 | } 559 | Ok(()) 560 | } 561 | } 562 | --------------------------------------------------------------------------------