diff --git a/Makefile b/Makefile index 6af4abb..e5bf87c 100644 --- a/Makefile +++ b/Makefile @@ -223,13 +223,15 @@ endif @md5sum ./build/test.elf tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/touchpaint/ @md5sum ./build/test.elf + tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/ + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \ hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \ pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft hts221 lps22hb apds9960 axp192 xpt2046 \ - ft6336 + ft6336 sx126x TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS)) unit-test: diff --git a/README.md b/README.md index a2b699a..e66a9de 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ The following 78 devices are supported. | [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI | | [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO | | [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO | +| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-transceiv-ers/sx1261) | SPI | ## Contributing diff --git a/examples/sx126x/lora_continuous/lora_continuous.go b/examples/sx126x/lora_continuous/lora_continuous.go new file mode 100644 index 0000000..97db402 --- /dev/null +++ b/examples/sx126x/lora_continuous/lora_continuous.go @@ -0,0 +1,80 @@ +package main + +// This example will periodically enable Continuous "Preamble" and "Wave" modes on 868.1 Mhz +import ( + "machine" + "time" + + rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch" + + "tinygo.org/x/drivers/sx126x" +) + +const FREQ = 868100000 + +var ( + loraRadio *sx126x.Device +) + +func main() { + println("\n# TinyGo Lora continuous Wave/Preamble test") + println("# -----------------------------------------") + + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + // Create the driver + loraRadio = sx126x.New(machine.SPI3) + loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262) + + // Create RF Switch + var radioSwitch rfswitch.CustomSwitch + loraRadio.SetRfSwitch(radioSwitch) + + state := loraRadio.DetectDevice() + if !state { + panic("sx126x not detected. ") + } + + // Prepare for Lora operation + loraConf := sx126x.LoraConfig{ + Freq: FREQ, + Bw: sx126x.SX126X_LORA_BW_500_0, + Sf: sx126x.SX126X_LORA_SF9, + Cr: sx126x.SX126X_LORA_CR_4_7, + HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT, + Preamble: 12, + Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF, + Iq: sx126x.SX126X_LORA_IQ_STANDARD, + Crc: sx126x.SX126X_LORA_CRC_ON, + SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD, + LoraTxPowerDBm: 14, + } + loraRadio.LoraConfig(loraConf) + + // Although LoraConfig has already configured most of Lora settings, + // the following lines are still required to enable Continuous Preamble/Wave + loraRadio.SetPacketType(sx126x.SX126X_PACKET_TYPE_LORA) + loraRadio.SetRfFrequency(loraConf.Freq) + loraRadio.SetModulationParams(loraConf.Sf, loraConf.Bw, loraConf.Cr, loraConf.Ldr) + loraRadio.SetTxParams(loraConf.LoraTxPowerDBm, sx126x.SX126X_PA_RAMP_200U) + + for { + println("2 seconds in Continuous Preamble") + loraRadio.SetStandby() + loraRadio.SetTxContinuousPreamble() + time.Sleep(2 * time.Second) + println("Continuous Preamble Stopped") + + loraRadio.SetStandby() + time.Sleep(10 * time.Second) + + println("2 seconds in Continuous Wave") + loraRadio.SetTxContinuousWave() + time.Sleep(2 * time.Second) + println(" Continuous Wave Stopped") + + loraRadio.SetStandby() + time.Sleep(60 * time.Second) + + } +} diff --git a/examples/sx126x/lora_rxtx/lora_rxtx.go b/examples/sx126x/lora_rxtx/lora_rxtx.go new file mode 100644 index 0000000..7c13b93 --- /dev/null +++ b/examples/sx126x/lora_rxtx/lora_rxtx.go @@ -0,0 +1,98 @@ +package main + +// In this example, a Lora packet will be sent every 10s +// module will be in RX mode between two transmissions + +import ( + "device/stm32" + "machine" + "runtime/interrupt" + "time" + + rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch" + + "tinygo.org/x/drivers/sx126x" +) + +const FREQ = 868100000 + +const ( + LORA_DEFAULT_RXTIMEOUT_MS = 1000 + LORA_DEFAULT_TXTIMEOUT_MS = 5000 +) + +var ( + loraRadio *sx126x.Device + txmsg = []byte("Hello TinyGO") +) + +// radioIntHandler will take care of radio interrupts +func radioIntHandler(intr interrupt.Interrupt) { + loraRadio.HandleInterrupt() +} + +func main() { + println("\n# TinyGo Lora RX/TX test") + println("# ----------------------") + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + // Create the driver + loraRadio = sx126x.New(machine.SPI3) + loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262) + + // Create RF Switch + var radioSwitch rfswitch.CustomSwitch + loraRadio.SetRfSwitch(radioSwitch) + + // Detect the device + state := loraRadio.DetectDevice() + if !state { + panic("sx126x not detected.") + } + + // Add interrupt handler for Radio IRQs + intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler) + intr.Enable() + + loraConf := sx126x.LoraConfig{ + Freq: FREQ, + Bw: sx126x.SX126X_LORA_BW_500_0, + Sf: sx126x.SX126X_LORA_SF9, + Cr: sx126x.SX126X_LORA_CR_4_7, + HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT, + Preamble: 12, + Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF, + Iq: sx126x.SX126X_LORA_IQ_STANDARD, + Crc: sx126x.SX126X_LORA_CRC_ON, + SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD, + LoraTxPowerDBm: 20, + } + + loraRadio.LoraConfig(loraConf) + + var count uint + for { + tStart := time.Now() + + // Blocking RX for LORA_DEFAULT_RXTIMEOUT_MS + println("Start Lora RX for 10 sec") + for int(time.Now().Sub(tStart).Seconds()) < 10 { + buf, err := loraRadio.LoraRx(LORA_DEFAULT_RXTIMEOUT_MS) + + if err != nil { + println("RX Error: ", err) + } else if buf != nil { + println("Packet Received: len=", len(buf), string(buf)) + } + } + println("END Lora RX") + + println("LORA TX size=", len(txmsg)) + err := loraRadio.LoraTx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS) + if err != nil { + println("TX Error:", err) + } + count++ + } + +} diff --git a/examples/sx126x/rfswitch/gnse.go b/examples/sx126x/rfswitch/gnse.go new file mode 100644 index 0000000..3530a67 --- /dev/null +++ b/examples/sx126x/rfswitch/gnse.go @@ -0,0 +1,61 @@ +//go:build gnse +// +build gnse + +/* + Generic Node Sensor Edition + RFSwitch + + Disable Switch : PB8=OFF PA0=OFF PA1=OFF + Enable RX : PB8=ON PA0=ON PA1=OFF + Enable TX RFO LP : PB8=ON PA0=ON PA1=ON + Enable TX RFO HP : PB8=ON PA0=OFF PA1=ON +*/ +package rfswitch + +import ( + "machine" + + "tinygo.org/x/drivers/sx126x" +) + +type CustomSwitch struct { +} + +var ( + rfstate int +) + +func (s CustomSwitch) InitRFSwitch() { + machine.RF_FE_CTRL1.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.RF_FE_CTRL2.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.RF_FE_CTRL3.Configure(machine.PinConfig{Mode: machine.PinOutput}) + rfstate = -1 //Unknown +} + +func (s CustomSwitch) SetRfSwitchMode(mode int) error { + if mode == rfstate { + return nil + } + + switch mode { + + case sx126x.RFSWITCH_TX_HP: + machine.RF_FE_CTRL1.Set(false) + machine.RF_FE_CTRL2.Set(true) + machine.RF_FE_CTRL3.Set(true) + + case sx126x.RFSWITCH_TX_LP: + machine.RF_FE_CTRL1.Set(true) + machine.RF_FE_CTRL2.Set(true) + machine.RF_FE_CTRL3.Set(true) + + case sx126x.RFSWITCH_RX: + machine.RF_FE_CTRL1.Set(true) + machine.RF_FE_CTRL2.Set(false) + machine.RF_FE_CTRL3.Set(true) + } + + rfstate = mode + + return nil +} diff --git a/examples/sx126x/rfswitch/lorae5.go b/examples/sx126x/rfswitch/lorae5.go new file mode 100644 index 0000000..0422e26 --- /dev/null +++ b/examples/sx126x/rfswitch/lorae5.go @@ -0,0 +1,43 @@ +//go:build lorae5 +// +build lorae5 + +package radio + +/* +/!\ LoRa-E5 module ONLY transmits through RFO_HP: + + Receive: PA4=1, PA5=0 + Transmit(high output power, SMPS mode): PA4=0, PA5=1 + +*/ + +import ( + "errors" + "machine" + + "tinygo.org/x/drivers/sx126x" +) + +type CustomSwitch struct { +} + +func (s CustomSwitch) InitRFSwitch() { + machine.PA4.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.PB5.Configure(machine.PinConfig{Mode: machine.PinOutput}) +} + +func (s CustomSwitch) SetRfSwitchMode(mode int) error { + switch mode { + + case sx126x.RFSWITCH_RX: + machine.PA4.Set(true) + machine.PB5.Set(false) + case sx126x.RFSWITCH_TX_LP: + errors.New("RFSWITCH_TX_LP not supported ") + case sx126x.RFSWITCH_TX_HP: + machine.PA4.Set(false) + machine.PB5.Set(true) + + } + return nil +} diff --git a/examples/sx126x/rfswitch/wl55jc.go b/examples/sx126x/rfswitch/wl55jc.go new file mode 100644 index 0000000..93f2f35 --- /dev/null +++ b/examples/sx126x/rfswitch/wl55jc.go @@ -0,0 +1,55 @@ +//go:build nucleowl55jc +// +build nucleowl55jc + +/* + Nucleo WL55JC1 + RFSwitch + ++-----------+---------+------------+------------+ +| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 | +| | (PC4) | (PC5) | (PC3) | ++-----------+----------+-----------+------------+ +| TX_HP | LOW | HIGH | HIGH | +| TX_LP | HIGH | HIGH | HIGH | +| RX | HIGH | LOW | HIGH | ++-----------+----------+-----------+------------+ +*/ +package rfswitch + +import ( + "machine" + + "tinygo.org/x/drivers/sx126x" +) + +type CustomSwitch struct { +} + +func (s CustomSwitch) InitRFSwitch() { + machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput}) +} + +func (s CustomSwitch) SetRfSwitchMode(mode int) error { + switch mode { + + case sx126x.RFSWITCH_TX_HP: + machine.PC4.Set(false) + machine.PC5.Set(true) + machine.PC3.Set(true) + + case sx126x.RFSWITCH_TX_LP: + machine.PC4.Set(true) + machine.PC5.Set(true) + machine.PC3.Set(true) + + case sx126x.RFSWITCH_RX: + machine.PC4.Set(true) + machine.PC5.Set(false) + machine.PC3.Set(true) + + } + return nil + +} diff --git a/sx126x/registers.go b/sx126x/registers.go new file mode 100644 index 0000000..2aa450a --- /dev/null +++ b/sx126x/registers.go @@ -0,0 +1,335 @@ +package sx126x + +const ( + // SX126X physical layer properties + SX126X_FREQUENCY_STEP_SIZE = 0.9536743164 + SX126X_MAX_PACKET_LENGTH = 255 + SX126X_CRYSTAL_FREQ = 32.0 + SX126X_DIV_EXPONENT = 25 + + // SX126X SPI commands + // operational modes commands + SX126X_CMD_NOP = 0x00 + SX126X_CMD_SET_SLEEP = 0x84 + SX126X_CMD_SET_STANDBY = 0x80 + SX126X_CMD_SET_FS = 0xC1 + SX126X_CMD_SET_TX = 0x83 + SX126X_CMD_SET_RX = 0x82 + SX126X_CMD_STOP_TIMER_ON_PREAMBLE = 0x9F + SX126X_CMD_SET_RX_DUTY_CYCLE = 0x94 + SX126X_CMD_SET_CAD = 0xC5 + SX126X_CMD_SET_TX_CONTINUOUS_WAVE = 0xD1 + SX126X_CMD_SET_TX_INFINITE_PREAMBLE = 0xD2 + SX126X_CMD_SET_REGULATOR_MODE = 0x96 + SX126X_CMD_CALIBRATE = 0x89 + SX126X_CMD_CALIBRATE_IMAGE = 0x98 + SX126X_CMD_SET_PA_CONFIG = 0x95 + SX126X_CMD_SET_RX_TX_FALLBACK_MODE = 0x93 + + // register and buffer access commands + SX126X_CMD_WRITE_REGISTER = 0x0D + SX126X_CMD_READ_REGISTER = 0x1D + SX126X_CMD_WRITE_BUFFER = 0x0E + SX126X_CMD_READ_BUFFER = 0x1E + + // DIO and IRQ control + SX126X_CMD_SET_DIO_IRQ_PARAMS = 0x08 + SX126X_CMD_GET_IRQ_STATUS = 0x12 + SX126X_CMD_CLEAR_IRQ_STATUS = 0x02 + SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL = 0x9D + SX126X_CMD_SET_DIO3_AS_TCXO_CTRL = 0x97 + + // RF, modulation and packet commands + SX126X_CMD_SET_RF_FREQUENCY = 0x86 + SX126X_CMD_SET_PACKET_TYPE = 0x8A + SX126X_CMD_GET_PACKET_TYPE = 0x11 + SX126X_CMD_SET_TX_PARAMS = 0x8E + SX126X_CMD_SET_MODULATION_PARAMS = 0x8B + SX126X_CMD_SET_PACKET_PARAMS = 0x8C + SX126X_CMD_SET_CAD_PARAMS = 0x88 + SX126X_CMD_SET_BUFFER_BASE_ADDRESS = 0x8F + SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT = 0x0A + + // status commands + SX126X_CMD_GET_STATUS = 0xC0 + SX126X_CMD_GET_RSSI_INST = 0x15 + SX126X_CMD_GET_RX_BUFFER_STATUS = 0x13 + SX126X_CMD_GET_PACKET_STATUS = 0x14 + SX126X_CMD_GET_DEVICE_ERRORS = 0x17 + SX126X_CMD_CLEAR_DEVICE_ERRORS = 0x07 + SX126X_CMD_GET_STATS = 0x10 + SX126X_CMD_RESET_STATS = 0x00 + + // SX126X register map + SX126X_REG_WHITENING_INITIAL_MSB = 0x06B8 + SX126X_REG_WHITENING_INITIAL_LSB = 0x06B9 + SX126X_REG_CRC_INITIAL_MSB = 0x06BC + SX126X_REG_CRC_INITIAL_LSB = 0x06BD + SX126X_REG_CRC_POLYNOMIAL_MSB = 0x06BE + SX126X_REG_CRC_POLYNOMIAL_LSB = 0x06BF + SX126X_REG_SYNC_WORD_0 = 0x06C0 + SX126X_REG_SYNC_WORD_1 = 0x06C1 + SX126X_REG_SYNC_WORD_2 = 0x06C2 + SX126X_REG_SYNC_WORD_3 = 0x06C3 + SX126X_REG_SYNC_WORD_4 = 0x06C4 + SX126X_REG_SYNC_WORD_5 = 0x06C5 + SX126X_REG_SYNC_WORD_6 = 0x06C6 + SX126X_REG_SYNC_WORD_7 = 0x06C7 + SX126X_REG_NODE_ADDRESS = 0x06CD + SX126X_REG_BROADCAST_ADDRESS = 0x06CE + SX126X_REG_LORA_SYNC_WORD_MSB = 0x0740 + SX126X_REG_LORA_SYNC_WORD_LSB = 0x0741 + SX126X_REG_RANDOM_NUMBER_0 = 0x0819 + SX126X_REG_RANDOM_NUMBER_1 = 0x081A + SX126X_REG_RANDOM_NUMBER_2 = 0x081B + SX126X_REG_RANDOM_NUMBER_3 = 0x081C + SX126X_REG_RX_GAIN = 0x08AC + SX126X_REG_OCP_CONFIGURATION = 0x08E7 + SX126X_REG_XTA_TRIM = 0x0911 + SX126X_REG_XTB_TRIM = 0x0912 + + // undocumented registers + SX126X_REG_SENSITIVITY_CONFIG = 0x0889 // SX1268 datasheet v1.1, section 15.1 + SX126X_REG_TX_CLAMP_CONFIG = 0x08D8 // SX1268 datasheet v1.1, section 15.2 + SX126X_REG_RTC_STOP = 0x0920 // SX1268 datasheet v1.1, section 15.3 + SX126X_REG_RTC_EVENT = 0x0944 // SX1268 datasheet v1.1, section 15.3 + SX126X_REG_IQ_CONFIG = 0x0736 // SX1268 datasheet v1.1, section 15.4 + SX126X_REG_RX_GAIN_RETENTION_0 = 0x029F // SX1268 datasheet v1.1, section 9.6 + SX126X_REG_RX_GAIN_RETENTION_1 = 0x02A0 // SX1268 datasheet v1.1, section 9.6 + SX126X_REG_RX_GAIN_RETENTION_2 = 0x02A1 // SX1268 datasheet v1.1, section 9.6 + + // SX126X SPI command variables + //SX126X_CMD_SET_SLEEP MSB LSB DESCRIPTION + SX126X_SLEEP_START_COLD = 0b00000000 // 2 2 sleep mode: cold start, configuration is lost (default) + SX126X_SLEEP_START_WARM = 0b00000100 // 2 2 warm start, configuration is retained + SX126X_SLEEP_RTC_OFF = 0b00000000 // 0 0 wake on RTC timeout: disabled + SX126X_SLEEP_RTC_ON = 0b00000001 // 0 0 enabled + + //SX126X_CMD_SET_STANDBY + SX126X_STANDBY_RC = 0x00 // 7 0 standby mode: 13 MHz RC oscillator + SX126X_STANDBY_XOSC = 0x01 // 7 0 32 MHz crystal oscillator + + //SX126X_CMD_SET_RX + SX126X_RX_TIMEOUT_NONE = 0x000000 // 23 0 Rx timeout duration: no timeout (Rx single mode) + SX126X_RX_TIMEOUT_INF = 0xFFFFFF // 23 0 infinite (Rx continuous mode) + + //SX126X_CMD_SET_TX + SX126X_TX_TIMEOUT_NONE = 0x000000 // 23 0 Tx timeout duration: no timeout (Tx single mode) + + //SX126X_CMD_STOP_TIMER_ON_PREAMBLE + SX126X_STOP_ON_PREAMBLE_OFF = 0x00 // 7 0 stop timer on: sync word or header (default) + SX126X_STOP_ON_PREAMBLE_ON = 0x01 // 7 0 preamble detection + + //SX126X_CMD_SET_REGULATOR_MODE + SX126X_REGULATOR_LDO = 0x00 // 7 0 set regulator mode: LDO (default) + SX126X_REGULATOR_DC_DC = 0x01 // 7 0 DC-DC + + //SX126X_CMD_CALIBRATE + SX126X_CALIBRATE_IMAGE_OFF = 0b00000000 // 6 6 image calibration: disabled + SX126X_CALIBRATE_IMAGE_ON = 0b01000000 // 6 6 enabled + SX126X_CALIBRATE_ADC_BULK_P_OFF = 0b00000000 // 5 5 ADC bulk P calibration: disabled + SX126X_CALIBRATE_ADC_BULK_P_ON = 0b00100000 // 5 5 enabled + SX126X_CALIBRATE_ADC_BULK_N_OFF = 0b00000000 // 4 4 ADC bulk N calibration: disabled + SX126X_CALIBRATE_ADC_BULK_N_ON = 0b00010000 // 4 4 enabled + SX126X_CALIBRATE_ADC_PULSE_OFF = 0b00000000 // 3 3 ADC pulse calibration: disabled + SX126X_CALIBRATE_ADC_PULSE_ON = 0b00001000 // 3 3 enabled + SX126X_CALIBRATE_PLL_OFF = 0b00000000 // 2 2 PLL calibration: disabled + SX126X_CALIBRATE_PLL_ON = 0b00000100 // 2 2 enabled + SX126X_CALIBRATE_RC13M_OFF = 0b00000000 // 1 1 13 MHz RC osc. calibration: disabled + SX126X_CALIBRATE_RC13M_ON = 0b00000010 // 1 1 enabled + SX126X_CALIBRATE_RC64K_OFF = 0b00000000 // 0 0 64 kHz RC osc. calibration: disabled + SX126X_CALIBRATE_RC64K_ON = 0b00000001 // 0 0 enabled + SX126X_CALIBRATE_ALL = 0b01111111 // 6 0 calibrate all blocks + + //SX126X_CMD_CALIBRATE_IMAGE + SX126X_CAL_IMG_430_MHZ_1 = 0x6B + SX126X_CAL_IMG_430_MHZ_2 = 0x6F + SX126X_CAL_IMG_470_MHZ_1 = 0x75 + SX126X_CAL_IMG_470_MHZ_2 = 0x81 + SX126X_CAL_IMG_779_MHZ_1 = 0xC1 + SX126X_CAL_IMG_779_MHZ_2 = 0xC5 + SX126X_CAL_IMG_863_MHZ_1 = 0xD7 + SX126X_CAL_IMG_863_MHZ_2 = 0xDB + SX126X_CAL_IMG_902_MHZ_1 = 0xE1 + SX126X_CAL_IMG_902_MHZ_2 = 0xE9 + + //SX126X_CMD_SET_PA_CONFIG + SX126X_PA_CONFIG_HP_MAX = 0x07 + SX126X_PA_CONFIG_PA_LUT = 0x01 + SX126X_PA_CONFIG_SX1262_8 = 0x00 + + //SX126X_CMD_SET_RX_TX_FALLBACK_MODE + SX126X_RX_TX_FALLBACK_MODE_FS = 0x40 // 7 0 after Rx/Tx go to: FS mode + SX126X_RX_TX_FALLBACK_MODE_STDBY_XOSC = 0x30 // 7 0 standby with crystal oscillator + SX126X_RX_TX_FALLBACK_MODE_STDBY_RC = 0x20 // 7 0 standby with RC oscillator (default) + + //SX126X_CMD_SET_DIO_IRQ_PARAMS + SX126X_IRQ_TIMEOUT = 0b1000000000 // 9 9 Rx or Tx timeout + SX126X_IRQ_CAD_DETECTED = 0b0100000000 // 8 8 channel activity detected + SX126X_IRQ_CAD_DONE = 0b0010000000 // 7 7 channel activity detection finished + SX126X_IRQ_CRC_ERR = 0b0001000000 // 6 6 wrong CRC received + SX126X_IRQ_HEADER_ERR = 0b0000100000 // 5 5 LoRa header CRC error + SX126X_IRQ_HEADER_VALID = 0b0000010000 // 4 4 valid LoRa header received + SX126X_IRQ_SYNC_WORD_VALID = 0b0000001000 // 3 3 valid sync word detected + SX126X_IRQ_PREAMBLE_DETECTED = 0b0000000100 // 2 2 preamble detected + SX126X_IRQ_RX_DONE = 0b0000000010 // 1 1 packet received + SX126X_IRQ_TX_DONE = 0b0000000001 // 0 0 packet transmission completed + SX126X_IRQ_ALL = 0b1111111111 // 9 0 all interrupts + SX126X_IRQ_NONE = 0b0000000000 // 9 0 no interrupts + + //SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL + SX126X_DIO2_AS_IRQ = 0x00 // 7 0 DIO2 configuration: IRQ + SX126X_DIO2_AS_RF_SWITCH = 0x01 // 7 0 RF switch control + + //SX126X_CMD_SET_DIO3_AS_TCXO_CTRL + SX126X_DIO3_OUTPUT_1_6 = 0x00 // 7 0 DIO3 voltage output for TCXO: 1.6 V + SX126X_DIO3_OUTPUT_1_7 = 0x01 // 7 0 1.7 V + SX126X_DIO3_OUTPUT_1_8 = 0x02 // 7 0 1.8 V + SX126X_DIO3_OUTPUT_2_2 = 0x03 // 7 0 2.2 V + SX126X_DIO3_OUTPUT_2_4 = 0x04 // 7 0 2.4 V + SX126X_DIO3_OUTPUT_2_7 = 0x05 // 7 0 2.7 V + SX126X_DIO3_OUTPUT_3_0 = 0x06 // 7 0 3.0 V + SX126X_DIO3_OUTPUT_3_3 = 0x07 // 7 0 3.3 V + + //SX126X_CMD_SET_PACKET_TYPE + SX126X_PACKET_TYPE_GFSK = 0x00 // 7 0 packet type: GFSK + SX126X_PACKET_TYPE_LORA = 0x01 // 7 0 LoRa + + //SX126X_CMD_SET_TX_PARAMS + SX126X_PA_RAMP_10U = 0x00 // 7 0 ramp time: 10 us + SX126X_PA_RAMP_20U = 0x01 // 7 0 20 us + SX126X_PA_RAMP_40U = 0x02 // 7 0 40 us + SX126X_PA_RAMP_80U = 0x03 // 7 0 80 us + SX126X_PA_RAMP_200U = 0x04 // 7 0 200 us + SX126X_PA_RAMP_800U = 0x05 // 7 0 800 us + SX126X_PA_RAMP_1700U = 0x06 // 7 0 1700 us + SX126X_PA_RAMP_3400U = 0x07 // 7 0 3400 us + + //SX126X_CMD_SET_MODULATION_PARAMS + SX126X_GFSK_FILTER_NONE = 0x00 // 7 0 GFSK filter: none + SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08 // 7 0 Gaussian, BT = 0.3 + SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09 // 7 0 Gaussian, BT = 0.5 + SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A // 7 0 Gaussian, BT = 0.7 + SX126X_GFSK_FILTER_GAUSS_1 = 0x0B // 7 0 Gaussian, BT = 1 + SX126X_GFSK_RX_BW_4_8 = 0x1F // 7 0 GFSK Rx bandwidth: 4.8 kHz + SX126X_GFSK_RX_BW_5_8 = 0x17 // 7 0 5.8 kHz + SX126X_GFSK_RX_BW_7_3 = 0x0F // 7 0 7.3 kHz + SX126X_GFSK_RX_BW_9_7 = 0x1E // 7 0 9.7 kHz + SX126X_GFSK_RX_BW_11_7 = 0x16 // 7 0 11.7 kHz + SX126X_GFSK_RX_BW_14_6 = 0x0E // 7 0 14.6 kHz + SX126X_GFSK_RX_BW_19_5 = 0x1D // 7 0 19.5 kHz + SX126X_GFSK_RX_BW_23_4 = 0x15 // 7 0 23.4 kHz + SX126X_GFSK_RX_BW_29_3 = 0x0D // 7 0 29.3 kHz + SX126X_GFSK_RX_BW_39_0 = 0x1C // 7 0 39.0 kHz + SX126X_GFSK_RX_BW_46_9 = 0x14 // 7 0 46.9 kHz + SX126X_GFSK_RX_BW_58_6 = 0x0C // 7 0 58.6 kHz + SX126X_GFSK_RX_BW_78_2 = 0x1B // 7 0 78.2 kHz + SX126X_GFSK_RX_BW_93_8 = 0x13 // 7 0 93.8 kHz + SX126X_GFSK_RX_BW_117_3 = 0x0B // 7 0 117.3 kHz + SX126X_GFSK_RX_BW_156_2 = 0x1A // 7 0 156.2 kHz + SX126X_GFSK_RX_BW_187_2 = 0x12 // 7 0 187.2 kHz + SX126X_GFSK_RX_BW_234_3 = 0x0A // 7 0 234.3 kHz + SX126X_GFSK_RX_BW_312_0 = 0x19 // 7 0 312.0 kHz + SX126X_GFSK_RX_BW_373_6 = 0x11 // 7 0 373.6 kHz + SX126X_GFSK_RX_BW_467_0 = 0x09 // 7 0 467.0 kHz + SX126X_LORA_BW_7_8 = 0x00 // 7 0 LoRa bandwidth: 7.8 kHz + SX126X_LORA_BW_10_4 = 0x08 // 7 0 10.4 kHz + SX126X_LORA_BW_15_6 = 0x01 // 7 0 15.6 kHz + SX126X_LORA_BW_20_8 = 0x09 // 7 0 20.8 kHz + SX126X_LORA_BW_31_25 = 0x02 // 7 0 31.25 kHz + SX126X_LORA_BW_41_7 = 0x0A // 7 0 41.7 kHz + SX126X_LORA_BW_62_5 = 0x03 // 7 0 62.5 kHz + SX126X_LORA_BW_125_0 = 0x04 // 7 0 125.0 kHz + SX126X_LORA_BW_250_0 = 0x05 // 7 0 250.0 kHz + SX126X_LORA_BW_500_0 = 0x06 // 7 0 500.0 kHz + SX126X_LORA_CR_4_5 = 0x01 // 7 0 LoRa coding rate: 4/5 + SX126X_LORA_CR_4_6 = 0x02 // 7 0 4/6 + SX126X_LORA_CR_4_7 = 0x03 // 7 0 4/7 + SX126X_LORA_CR_4_8 = 0x04 // 7 0 4/8 + SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF = 0x00 // 7 0 LoRa low data rate optimization: disabled + SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON = 0x01 // 7 0 enabled + + //SX126X_CMD_SET_PACKET_PARAMS + SX126X_GFSK_PREAMBLE_DETECT_OFF = 0x00 // 7 0 GFSK minimum preamble length before reception starts: detector disabled + SX126X_GFSK_PREAMBLE_DETECT_8 = 0x04 // 7 0 8 bits + SX126X_GFSK_PREAMBLE_DETECT_16 = 0x05 // 7 0 16 bits + SX126X_GFSK_PREAMBLE_DETECT_24 = 0x06 // 7 0 24 bits + SX126X_GFSK_PREAMBLE_DETECT_32 = 0x07 // 7 0 32 bits + SX126X_GFSK_ADDRESS_FILT_OFF = 0x00 // 7 0 GFSK address filtering: disabled + SX126X_GFSK_ADDRESS_FILT_NODE = 0x01 // 7 0 node only + SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST = 0x02 // 7 0 node and broadcast + SX126X_GFSK_PACKET_FIXED = 0x00 // 7 0 GFSK packet type: fixed (payload length known in advance to both sides) + SX126X_GFSK_PACKET_VARIABLE = 0x01 // 7 0 variable (payload length added to packet) + SX126X_GFSK_CRC_OFF = 0x01 // 7 0 GFSK packet CRC: disabled + SX126X_GFSK_CRC_1_BYTE = 0x00 // 7 0 1 byte + SX126X_GFSK_CRC_2_BYTE = 0x02 // 7 0 2 byte + SX126X_GFSK_CRC_1_BYTE_INV = 0x04 // 7 0 1 byte, inverted + SX126X_GFSK_CRC_2_BYTE_INV = 0x06 // 7 0 2 byte, inverted + SX126X_GFSK_WHITENING_OFF = 0x00 // 7 0 GFSK data whitening: disabled + SX126X_GFSK_WHITENING_ON = 0x01 // 7 0 enabled + SX126X_LORA_HEADER_EXPLICIT = 0x00 // 7 0 LoRa header mode: explicit + SX126X_LORA_HEADER_IMPLICIT = 0x01 // 7 0 implicit + SX126X_LORA_CRC_OFF = 0x00 // 7 0 LoRa CRC mode: disabled + SX126X_LORA_CRC_ON = 0x01 // 7 0 enabled + SX126X_LORA_IQ_STANDARD = 0x00 // 7 0 LoRa IQ setup: standard + SX126X_LORA_IQ_INVERTED = 0x01 // 7 0 inverted + + //SX126X_CMD_SET_CAD_PARAMS + SX126X_CAD_ON_1_SYMB = 0x00 // 7 0 number of symbols used for CAD: 1 + SX126X_CAD_ON_2_SYMB = 0x01 // 7 0 2 + SX126X_CAD_ON_4_SYMB = 0x02 // 7 0 4 + SX126X_CAD_ON_8_SYMB = 0x03 // 7 0 8 + SX126X_CAD_ON_16_SYMB = 0x04 // 7 0 16 + SX126X_CAD_GOTO_STDBY = 0x00 // 7 0 after CAD is done, always go to STDBY_RC mode + SX126X_CAD_GOTO_RX = 0x01 // 7 0 after CAD is done, go to Rx mode if activity is detected + + //SX126X_CMD_GET_STATUS + SX126X_STATUS_MODE_STDBY_RC = 0b00100000 // 6 4 current chip mode: STDBY_RC + SX126X_STATUS_MODE_STDBY_XOSC = 0b00110000 // 6 4 STDBY_XOSC + SX126X_STATUS_MODE_FS = 0b01000000 // 6 4 FS + SX126X_STATUS_MODE_RX = 0b01010000 // 6 4 RX + SX126X_STATUS_MODE_TX = 0b01100000 // 6 4 TX + SX126X_STATUS_DATA_AVAILABLE = 0b00000100 // 3 1 command status: packet received and data can be retrieved + SX126X_STATUS_CMD_TIMEOUT = 0b00000110 // 3 1 SPI command timed out + SX126X_STATUS_CMD_INVALID = 0b00001000 // 3 1 invalid SPI command + SX126X_STATUS_CMD_FAILED = 0b00001010 // 3 1 SPI command failed to execute + SX126X_STATUS_TX_DONE = 0b00001100 // 3 1 packet transmission done + SX126X_STATUS_SPI_FAILED = 0b11111111 // 7 0 SPI transaction failed + + //SX126X_CMD_GET_PACKET_STATUS + SX126X_GFSK_RX_STATUS_PREAMBLE_ERR = 0b10000000 // 7 7 GFSK Rx status: preamble error + SX126X_GFSK_RX_STATUS_SYNC_ERR = 0b01000000 // 6 6 sync word error + SX126X_GFSK_RX_STATUS_ADRS_ERR = 0b00100000 // 5 5 address error + SX126X_GFSK_RX_STATUS_CRC_ERR = 0b00010000 // 4 4 CRC error + SX126X_GFSK_RX_STATUS_LENGTH_ERR = 0b00001000 // 3 3 length error + SX126X_GFSK_RX_STATUS_ABORT_ERR = 0b00000100 // 2 2 abort error + SX126X_GFSK_RX_STATUS_PACKET_RECEIVED = 0b00000010 // 2 2 packet received + SX126X_GFSK_RX_STATUS_PACKET_SENT = 0b00000001 // 2 2 packet sent + + //SX126X_CMD_GET_DEVICE_ERRORS + SX126X_PA_RAMP_ERR = 0b100000000 // 8 8 device errors: PA ramping failed + SX126X_PLL_LOCK_ERR = 0b001000000 // 6 6 PLL failed to lock + SX126X_XOSC_START_ERR = 0b000100000 // 5 5 crystal oscillator failed to start + SX126X_IMG_CALIB_ERR = 0b000010000 // 4 4 image calibration failed + SX126X_ADC_CALIB_ERR = 0b000001000 // 3 3 ADC calibration failed + SX126X_PLL_CALIB_ERR = 0b000000100 // 2 2 PLL calibration failed + SX126X_RC13M_CALIB_ERR = 0b000000010 // 1 1 RC13M calibration failed + SX126X_RC64K_CALIB_ERR = 0b000000001 // 0 0 RC64K calibration failed + + // SX126X SPI register variables + //SX126X_REG_LORA_SYNC_WORD_MSB + LSB + SX126X_SYNC_WORD_PUBLIC = 0x34 // actually 0x3444 NOTE: The low nibbles in each byte (0x_4_4) are masked out since apparently, they're reserved. + SX126X_SYNC_WORD_PRIVATE = 0x12 // actually 0x1424 You couldn't make this up if you tried. + + SX126X_LORA_MAC_PUBLIC_SYNCWORD = 0x3444 + SX126X_LORA_MAC_PRIVATE_SYNCWORD = 0x1424 + + SX126X_LORA_SF5 = 0x05 + SX126X_LORA_SF6 = 0x06 + SX126X_LORA_SF7 = 0x07 + SX126X_LORA_SF8 = 0x08 + SX126X_LORA_SF9 = 0x09 + SX126X_LORA_SF10 = 0x0A + SX126X_LORA_SF11 = 0x0B + SX126X_LORA_SF12 = 0x0C +) diff --git a/sx126x/spi_stm32wl.go b/sx126x/spi_stm32wl.go new file mode 100644 index 0000000..540dd5b --- /dev/null +++ b/sx126x/spi_stm32wl.go @@ -0,0 +1,85 @@ +//go:build stm32wlx +// +build stm32wlx + +package sx126x + +import ( + "device/stm32" + "errors" + "machine" + "tinygo.org/x/drivers" +) + +// New creates a new SX126x connection. +func New(spi drivers.SPI) *Device { + c := make(chan RadioEvent, 10) + d := Device{ + spi: spi, + radioEventChan: c, + } + if d.spi == machine.SPI3 { + d.SubGhzInit() + d.SetDeviceType(DEVICE_TYPE_SX1262) + } else { + panic("Driver only support SUBGHZSPI (SPI3) on stm32wlx targets") + } + return &d +} + +//SpiSetNss Sets the NSS line +func (d *Device) SpiSetNss(state bool) { + if state { + stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS) + } else { + stm32.PWR.SUBGHZSPICR.ClearBits(stm32.PWR_SUBGHZSPICR_NSS) + + } +} + +// WaitBusy sleep until all busy flags clears +func (d *Device) WaitBusy() error { + count := 100 + var rfbusyms, rfbusys bool + for count > 0 { + rfbusyms = stm32.PWR.SR2.HasBits(stm32.PWR_SR2_RFBUSYMS) + rfbusys = stm32.PWR.SR2.HasBits(stm32.PWR_SR2_RFBUSYS) + + if !(rfbusyms && rfbusys) { + return nil + } + count-- + } + return errors.New("WaitBusy Timeout") +} + +// SubGhzInit() configures internal SX1262's SPI bus. +func (d *Device) SubGhzInit() { + + // Enable APB3 Periph clock and delay + stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SUBGHZSPIEN) + _ = stm32.RCC.APB3ENR.Get() + + // Disable radio reset and wait it's ready + stm32.RCC.CSR.ClearBits(stm32.RCC_CSR_RFRST) + for stm32.RCC.CSR.HasBits(stm32.RCC_CSR_RFRSTF) { + } + + // Set NSS line low + stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS) + + // Enable radio busy wakeup from Standby for CPU + stm32.PWR.CR3.SetBits(stm32.PWR_CR3_EWRFBUSY) + + // Clear busy flag + stm32.PWR.SCR.Set(stm32.PWR_SCR_CWRFBUSYF) + + // Enable SUBGHZ Spi + // - /8 Prescaler + // - Software Slave Management (NSS) + // - FIFO Threshold and 8bit size + stm32.SPI3.CR1.ClearBits(stm32.SPI_CR1_SPE) + stm32.SPI3.CR1.Set(stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI | (0b010 << 3) | stm32.SPI_CR1_SSM) + stm32.SPI3.CR2.Set(stm32.SPI_CR2_FRXTH | (0b111 << 8)) + stm32.SPI3.CR1.SetBits(stm32.SPI_CR1_SPE) + +} diff --git a/sx126x/sx126x.go b/sx126x/sx126x.go new file mode 100644 index 0000000..5f9a173 --- /dev/null +++ b/sx126x/sx126x.go @@ -0,0 +1,699 @@ +// Package sx126x provides a driver for SX126x LoRa transceivers. +// Inspired from https://github.com/Lora-net/sx126x_driver/ + +package sx126x + +import ( + "errors" + "time" + + "tinygo.org/x/drivers" +) + +// SX126X radio transceiver RF_IN and RF_OUT may be connected +// to RF Switch. This interface allows the creation of struct +// that can drive the RF Switch (Used in Lora RX and Lora Tx) +type RFSwitch interface { + InitRFSwitch() + SetRfSwitchMode(mode int) error +} + +const ( + DEVICE_TYPE_SX1261 = iota + DEVICE_TYPE_SX1262 = iota + DEVICE_TYPE_SX1268 = iota +) + +const ( + RFSWITCH_RX = iota + RFSWITCH_TX_LP = iota + RFSWITCH_TX_HP = iota +) + +const ( + RadioEventRxDone = iota + RadioEventTxDone = iota + RadioEventTimeout = iota + RadioEventWatchdog = iota + RadioEventCrcError = iota + RadioEventUnhandled = iota +) + +// RadioEvent are used for communicating in the radio Event Channel +type RadioEvent struct { + EventType int + IRQStatus uint16 + EventData []byte +} + +const ( + PERIOD_PER_SEC = (uint32)(1000000 / 15.625) // SX1261 DS 13.1.4 + SPI_BUFFER_SIZE = 256 +) + +// Device wraps an SPI connection to a SX127x device. +type Device struct { + spi drivers.SPI // SPI bus for module communication + radioEventChan chan RadioEvent // Channel for Receiving events + loraConf LoraConfig // Current Lora configuration + rfswitch RFSwitch // RF Switch, if any + deepSleep bool // Internal Sleep state + deviceType int // sx1261,sx1262,sx1268 (defaults sx1261) + spiBuffer [SPI_BUFFER_SIZE]uint8 +} + +// Config holds the LoRa configuration parameters +type LoraConfig struct { + Freq uint32 // Frequency + Cr uint8 // Coding Rate + Sf uint8 // Spread Factor + Bw uint8 // Bandwidth + Ldr uint8 // Low Data Rate + Preamble uint16 // PreambleLength + SyncWord uint16 // Sync Word + HeaderType uint8 // Header : Implicit/explicit + Crc uint8 // CRC : Yes/No + Iq uint8 // iq : Standard/inverted + LoraTxPowerDBm int8 // Tx power in Dbm +} + +const ( + SX126X_RTC_FREQ_IN_HZ uint32 = 64000 +) + +var ( + errUndefinedLoraConf = errors.New("Undefined Lora configuration") +) + +// -------------------------------------------------- +// Helper functions +// -------------------------------------------------- + +// timeoutMsToRtcSteps converts Timeout (in ms) to RTC Steps +func timeoutMsToRtcSteps(timeoutMs uint32) uint32 { + r := uint32(timeoutMs * (SX126X_RTC_FREQ_IN_HZ / 1000)) + return r +} + +// -------------------------------------------------- +// Channel and events +// -------------------------------------------------- +//NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel +func NewRadioEvent(eType int, irqStatus uint16, eData []byte) RadioEvent { + r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData} + return r +} + +// Get the RadioEvent channel of the device +func (d *Device) GetRadioEventChan() chan RadioEvent { + return d.radioEventChan +} + +// Specify device type (SX1261/2/8) +func (d *Device) SetDeviceType(devType int) { + d.deviceType = devType +} + +// SetRfSwitch let you define a custom RF Switch driver if needed +func (d *Device) SetRfSwitch(rfswitch RFSwitch) { + d.rfswitch = rfswitch + d.rfswitch.InitRFSwitch() +} + +// -------------------------------------------------- +// Operational modes functions +// -------------------------------------------------- + +// DetectDevice() tries to detect the radio module by changing SyncWord value +func (d *Device) DetectDevice() bool { + bak := d.GetSyncWord() + d.SetSyncWord(0xBEEF) + tmp := d.GetSyncWord() + if tmp != 0xBEEF { + return false + } else { + d.SetSyncWord(bak) + return true + } +} + +// SetSleep sets the device in SLEEP mode with the lowest current consumption possible. +func (d *Device) SetSleep() { + d.ExecSetCommand(SX126X_CMD_SET_SLEEP, []uint8{SX126X_SLEEP_START_WARM | SX126X_SLEEP_RTC_OFF}) +} + +// SetStandby sets the device in a configuration mode which is at an intermediate level of consumption +func (d *Device) SetStandby() { + d.ExecSetCommand(SX126X_CMD_SET_STANDBY, []uint8{SX126X_STANDBY_RC}) +} + +// SetFs sets the device in frequency synthesis mode where the PLL is locked to the carrier frequency. +func (d *Device) SetFs() { + d.ExecSetCommand(SX126X_CMD_SET_FS, []uint8{}) +} + +// SetTxContinuousWave set device in test mode to generate a continuous wave (RF tone) +func (d *Device) SetTxContinuousWave() { + if d.rfswitch != nil { + d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP) + } + d.ExecSetCommand(SX126X_CMD_SET_TX_CONTINUOUS_WAVE, []uint8{}) +} + +// SetTxContinuousPreamble set device in test mode to constantly modulate LoRa preamble symbols. +// Take care to initialize all Lora settings like it's done in LoraTx before calling this function +// If you don't init properly all the settings, it'll fail +func (d *Device) SetTxContinuousPreamble() { + if d.rfswitch != nil { + d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP) + } + d.ExecSetCommand(SX126X_CMD_SET_TX_INFINITE_PREAMBLE, []uint8{}) +} + +// SetTx() sets the device in TX mode +// timeout is expressed in RTC Step unit (15uS) +// The device will stay in Tx until countdown or packet transmitted +// Value of 0x000000 will disable timer and device will stay TX +func (d *Device) SetTx(timeoutRtcStep uint32) { + var p [3]uint8 + p[0] = uint8((timeoutRtcStep >> 16) & 0xFF) + p[1] = uint8((timeoutRtcStep >> 8) & 0xFF) + p[2] = uint8((timeoutRtcStep >> 0) & 0xFF) + d.ExecSetCommand(SX126X_CMD_SET_TX, p[:]) +} + +// SetRx() sets the device in RX mode +// timeout is expressed in RTC Step unit (15uS) +// Value of 0x000000 => No timeout. Rx Single mode. +// Value of 0xffffff => Rx Continuous mode +// Other values => Timeout active. The device remains in RX until countdown or packet received +func (d *Device) SetRx(timeoutRtcStep uint32) { + var p [3]uint8 + p[0] = uint8(((timeoutRtcStep >> 16) & 0xFF)) + p[1] = uint8(((timeoutRtcStep >> 8) & 0xFF)) + p[2] = uint8(((timeoutRtcStep >> 0) & 0xFF)) + d.ExecSetCommand(SX126X_CMD_SET_RX, p[:]) +} + +// StopTimerOnPreamble allows the user to select if the timer is stopped upon preamble detection of SyncWord / header detection. +func (d *Device) StopTimerOnPreamble(enable bool) { + var p [1]uint8 + if enable { + p[0] = 1 + } else { + p[0] = 0 + } + d.ExecSetCommand(SX126X_CMD_STOP_TIMER_ON_PREAMBLE, p[:]) +} + +// SetRegulatorMode sets the regulator more (depends on hardware implementation) +func (d *Device) SetRegulatorMode(mode uint8) { + p := []uint8{mode} + d.ExecSetCommand(SX126X_CMD_SET_REGULATOR_MODE, p[:]) +} + +// Calibrate starts the calibration of a block defined by calibParam +func (d *Device) Calibrate(calibParam uint8) { + p := []uint8{calibParam} + d.ExecSetCommand(SX126X_CMD_CALIBRATE, p[:]) +} + +// CalibrateImage calibrates the image rejection of the device for the device operating +func (d *Device) CalibrateImage(freq uint32) { + var calFreq [2]uint8 + if freq > 900000000 { + calFreq[0] = 0xE1 + calFreq[1] = 0xE9 + } else if freq > 850000000 { + calFreq[0] = 0xD7 + calFreq[1] = 0xD8 + } else if freq > 770000000 { + calFreq[0] = 0xC1 + calFreq[1] = 0xC5 + } else if freq > 460000000 { + calFreq[0] = 0x75 + calFreq[1] = 0x81 + } else if freq > 425000000 { + calFreq[0] = 0x6B + calFreq[1] = 0x6F + } + d.ExecSetCommand(SX126X_CMD_CALIBRATE_IMAGE, calFreq[:]) +} + +// SetPaConfig sets the Power Amplifier configuration +// deviceSel: 0 for SX1262, 1 for SX1261 +func (d *Device) SetPaConfig(paDutyCycle, hpMax, deviceSel, paLut uint8) { + var p [4]uint8 + p[0] = paDutyCycle + p[1] = hpMax + p[2] = deviceSel + p[3] = paLut + d.ExecSetCommand(SX126X_CMD_SET_PA_CONFIG, p[:]) +} + +// SetRxTxFallbackMode defines into which mode the chip goes after a successful transmission or after a packet reception. +func (d *Device) SetRxTxFallbackMode(fallbackMode uint8) { + d.ExecSetCommand(SX126X_CMD_SET_RX_TX_FALLBACK_MODE, []uint8{fallbackMode}) +} + +// -------------------------------------------------- +// Registers and Buffers +// -------------------------------------------------- + +// ReadRegister reads register value +func (d *Device) ReadRegister(addr, size uint16) ([]uint8, error) { + d.CheckDeviceReady() + d.SpiSetNss(false) + // Send command + cmd := []uint8{SX126X_CMD_READ_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF), 0x00} + d.spi.Tx(cmd, nil) + ret := d.spiBuffer[0:size] + d.spi.Tx(nil, ret) + d.SpiSetNss(true) + d.WaitBusy() + return ret, nil +} + +// WriteRegister writes value to register +func (d *Device) WriteRegister(addr uint16, data []uint8) { + d.CheckDeviceReady() + d.SpiSetNss(false) + cmd := []uint8{SX126X_CMD_WRITE_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF)} + d.spi.Tx(append(cmd, data...), nil) + d.SpiSetNss(true) + d.WaitBusy() +} + +// WriteBuffer write data from current buffer position +func (d *Device) WriteBuffer(data []uint8) { + p := []uint8{0} + p = append(p, data...) + d.ExecSetCommand(SX126X_CMD_WRITE_BUFFER, p) +} + +// ReadBuffer Reads size bytes from current buffer position +func (d *Device) ReadBuffer(size uint8) []uint8 { + ret := d.ExecGetCommand(SX126X_CMD_READ_BUFFER, size) + return ret +} + +// -------------------------------------------------- +// DIO and IRQ +// -------------------------------------------------- + +// SetDioIrqParams configures DIO Irq +func (d *Device) SetDioIrqParams(irqMask, dio1Mask, dio2Mask, dio3Mask uint16) { + var p [8]uint8 + p[0] = uint8((irqMask >> 8) & 0xFF) + p[1] = uint8(irqMask & 0xFF) + p[2] = uint8((dio1Mask >> 8) & 0xFF) + p[3] = uint8(dio1Mask & 0xFF) + p[4] = uint8((dio2Mask >> 8) & 0xFF) + p[5] = uint8(dio2Mask & 0xFF) + p[6] = uint8((dio3Mask >> 8) & 0xFF) + p[7] = uint8(dio3Mask & 0xFF) + d.ExecSetCommand(SX126X_CMD_SET_DIO_IRQ_PARAMS, p[:]) +} + +// GetIrqStatus returns IRQ status +func (d *Device) GetIrqStatus() (irqStatus uint16) { + r := d.ExecGetCommand(SX126X_CMD_GET_IRQ_STATUS, 2) + ret := (uint16(r[0]) << 8) | uint16(r[1]) + return ret +} + +// ClearIrqStatus clears IRQ flags +func (d *Device) ClearIrqStatus(clearIrqParams uint16) { + var p [2]uint8 + p[0] = uint8((clearIrqParams >> 8) & 0xFF) + p[1] = uint8(clearIrqParams & 0xFF) + d.ExecSetCommand(SX126X_CMD_CLEAR_IRQ_STATUS, p[:]) +} + +// -------------------------------------------------- +// Communication Status Information +// -------------------------------------------------- + +// GetStatus returns radio status(13.5.1) +func (d *Device) GetStatus() (radioStatus uint8) { + r := d.ExecGetCommand(SX126X_CMD_GET_STATUS, 1) + return r[0] +} + +// GetRxBufferStatus returns the length of the last received packet (PayloadLengthRx) +// and the address of the first byte received (RxStartBufferPointer). (13.5.2) +func (d *Device) GetRxBufferStatus() (payloadLengthRx uint8, rxStartBufferPointer uint8) { + r := d.ExecGetCommand(SX126X_CMD_GET_RX_BUFFER_STATUS, 2) + return r[0], r[1] +} + +// GetPackeType returns current Packet Type (13.4.3) +func (d *Device) GetPacketType() (packetType uint8) { + r := d.ExecGetCommand(SX126X_CMD_GET_PACKET_TYPE, 1) + return r[0] +} + +// GetDeviceErrors returns current Device Errors +func (d *Device) GetDeviceErrors() uint16 { + r := d.ExecGetCommand(SX126X_CMD_GET_DEVICE_ERRORS, 2) + ret := uint16(r[0]<<8 + r[1]) + return ret +} + +// ClearDeviceErrors clears device Errors +func (d *Device) ClearDeviceErrors() { + p := [2]uint8{0x00, 0x00} + d.ExecSetCommand(SX126X_CMD_CLEAR_DEVICE_ERRORS, p[:]) +} + +// GetStats returns the number of informations received on a few last packets +// Lora: NbPktReceived, NbPktCrcError, NbPktHeaderErr +func (d *Device) GetLoraStats() (nbPktReceived, nbPktCrcError, nbPktHeaderErr uint16) { + r := d.ExecGetCommand(SX126X_CMD_GET_STATS, 6) + return uint16(r[0]<<8 | r[1]), uint16(r[2]<<8 | r[3]), uint16(r[4]<<8 | r[5]) +} + +// --------------------------------------- +// PACKET / RADIO / PROTOCOL CONFIGURATION +// --------------------------------------- + +// SetPacketType sets the packet type +func (d *Device) SetPacketType(packetType uint8) { + var p [1]uint8 + p[0] = packetType + d.ExecSetCommand(SX126X_CMD_SET_PACKET_TYPE, p[:]) +} + +// SetSyncWord defines the Sync Word to yse +func (d *Device) SetSyncWord(syncword uint16) { + var p [2]uint8 + d.loraConf.SyncWord = syncword + p[0] = uint8((syncword >> 8) & 0xFF) + p[1] = uint8((syncword >> 0) & 0xFF) + d.WriteRegister(SX126X_REG_LORA_SYNC_WORD_MSB, p[:]) +} + +// GetSyncWord gets the Sync Word to use +func (d *Device) GetSyncWord() uint16 { + p, _ := d.ReadRegister(SX126X_REG_LORA_SYNC_WORD_MSB, 2) + r := uint16(p[0])<<8 + uint16(p[1]) + return r +} + +// SetLoraPublicNetwork sets Sync Word to 0x3444 (Public) or 0x1424 (Private) +func (d *Device) SetLoraPublicNetwork(enable bool) { + if enable { + d.SetSyncWord(SX126X_LORA_MAC_PUBLIC_SYNCWORD) + } else { + d.SetSyncWord(SX126X_LORA_MAC_PRIVATE_SYNCWORD) + } +} + +// SetPacketParam sets various packet-related params +func (d *Device) SetPacketParam(preambleLength uint16, headerType, crcType, payloadLength, invertIQ uint8) { + var p [6]uint8 + p[0] = uint8((preambleLength >> 8) & 0xFF) + p[1] = uint8(preambleLength & 0xFF) + p[2] = headerType + p[3] = payloadLength + p[4] = crcType + p[5] = invertIQ + d.ExecSetCommand(SX126X_CMD_SET_PACKET_PARAMS, p[:]) +} + +// SetBufferBaseAddress sets base address for buffer +func (d *Device) SetBufferBaseAddress(txBaseAddress, rxBaseAddress uint8) { + var p [2]uint8 + p[0] = txBaseAddress + p[1] = rxBaseAddress + d.ExecSetCommand(SX126X_CMD_SET_BUFFER_BASE_ADDRESS, p[:]) +} + +// SetRfFrequency sets the radio frequency +func (d *Device) SetRfFrequency(frequency uint32) { + var p [4]uint8 + freq := uint32((uint64(frequency) << 25) / 32000000) + p[0] = uint8((freq >> 24) & 0xFF) + p[1] = uint8((freq >> 16) & 0xFF) + p[2] = uint8((freq >> 8) & 0xFF) + p[3] = uint8((freq >> 0) & 0xFF) + d.ExecSetCommand(SX126X_CMD_SET_RF_FREQUENCY, p[:]) +} + +// SetCurrentLimit sets max current in the module +func (d *Device) SetCurrentLimit(limit uint8) { + if limit > 140 { + limit = 140 + } + rawLimit := uint8(float32(limit) / 2.5) + p := []uint8{rawLimit} + d.WriteRegister(SX126X_REG_OCP_CONFIGURATION, p[:]) +} + +// SetTxConfig sets power and rampup time +func (d *Device) SetTxParams(power int8, rampTime uint8) { + var p [2]uint8 + + if d.deviceType == DEVICE_TYPE_SX1261 { + if power == 15 { + d.SetPaConfig(0x06, 0x00, 0x01, 0x01) + } else { + d.SetPaConfig(0x04, 0x00, 0x01, 0x01) + } + if power > 14 { + power = 14 + } else if power < -3 { + power = -3 + } + d.SetCurrentLimit(80) // Set max current limit to 80mA + } else { // sx1262 and sx1268 + d.SetPaConfig(0x04, 0x07, 0x00, 0x01) + if power > 22 { + power = 22 + } else if power < -3 { + power = -3 + } + d.SetCurrentLimit(140) // Set max current limit to 140 mA + } + + p[0] = uint8(power) + p[1] = rampTime + d.ExecSetCommand(SX126X_CMD_SET_TX_PARAMS, p[:]) +} + +// SetModulationParams sets the Lora modulation frequency +func (d *Device) SetModulationParams(spreadingFactor, bandwidth, codingRate, lowDataRateOptimize uint8) { + var p [4]uint8 + p[0] = spreadingFactor + p[1] = bandwidth + p[2] = codingRate + p[3] = lowDataRateOptimize + d.ExecSetCommand(SX126X_CMD_SET_MODULATION_PARAMS, p[:]) +} + +// CheckDeviceReady sleep until all busy flags clears +func (d *Device) CheckDeviceReady() error { + if d.deepSleep == true { + d.SpiSetNss(false) + time.Sleep(time.Millisecond) + d.SpiSetNss(true) + d.deepSleep = false + } + return d.WaitBusy() +} + +// ExecSetCommand send a command to configure the peripheral +func (d *Device) ExecSetCommand(cmd uint8, buf []uint8) { + d.CheckDeviceReady() + if cmd == SX126X_CMD_SET_SLEEP { + d.deepSleep = true + } else { + d.deepSleep = false + } + d.SpiSetNss(false) + // Send command and params + d.spi.Tx(append([]uint8{cmd}, buf...), nil) + d.SpiSetNss(true) + if cmd != SX126X_CMD_SET_SLEEP { + d.WaitBusy() + } +} + +// ExecGetCommand queries the peripheral the peripheral +func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 { + d.CheckDeviceReady() + d.SpiSetNss(false) + // Send the command and flush first status byte (as not used) + d.spi.Tx([]uint8{cmd, 0x00}, nil) + d.spi.Tx(nil, d.spiBuffer[:size]) + d.SpiSetNss(true) + d.WaitBusy() + return d.spiBuffer[:size] +} + +// +// Configuration +// + +// SetLoraFrequency() Sets current Lora Frequency +// NB: Change will be applied at next RX / TX +func (d *Device) SetLoraFrequency(freq uint32) { + d.loraConf.Freq = d.loraConf.Freq +} + +// SetLoraIqMode() defines the current IQ Mode (Standard/Inverted) +// NB: Change will be applied at next RX / TX +func (d *Device) SetLoraIqMode(mode uint8) { + if mode == 0 { + d.loraConf.Iq = SX126X_LORA_IQ_STANDARD + } else { + d.loraConf.Iq = SX126X_LORA_IQ_INVERTED + } +} + +// SetLoraCodingRate() sets current Lora Coding Rate +// NB: Change will be applied at next RX / TX +func (d *Device) SetLoraCodingRate(cr uint8) { + d.loraConf.Cr = cr +} + +// SetLoraBandwidth() sets current Lora Bandwidth +// NB: Change will be applied at next RX / TX +func (d *Device) SetLoraBandwidth(bw uint8) { + d.loraConf.Cr = bw +} + +// SetLoraCrc() sets current CRC mode (ON/OFF) +// NB: Change will be applied at next RX / TX +func (d *Device) SetLoraCrc(enable bool) { + if enable { + d.loraConf.Crc = SX126X_LORA_CRC_ON + } else { + d.loraConf.Crc = SX126X_LORA_CRC_OFF + } +} + +//SetLoraSpreadingFactor setc surrent Lora Spreading Factor +// NB: Change will be applied at next RX / TX +func (d *Device) SetLoraSpreadingFactor(sf uint8) { + d.loraConf.Sf = sf +} + +// +// Lora functions +// +// + +// LoraConfig() defines Lora configuration for next Lora operations +func (d *Device) LoraConfig(cnf LoraConfig) { + // Save given configuration + d.loraConf = cnf + // Switch to standby prior to configuration changes + d.SetStandby() + // Clear errors, disable radio interrupts for the moment + d.ClearDeviceErrors() + d.ClearIrqStatus(SX126X_IRQ_ALL) + d.SetDioIrqParams(0x00, 0x00, 0x00, 0x00) + // Define radio operation mode + d.SetPacketType(SX126X_PACKET_TYPE_LORA) + d.SetRfFrequency(d.loraConf.Freq) + d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr) + d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U) + d.SetSyncWord(d.loraConf.SyncWord) + d.SetBufferBaseAddress(0, 0) +} + +// LoraTx sends a lora packet, (with timeout) +func (d *Device) LoraTx(pkt []uint8, timeoutMs uint32) error { + + if d.loraConf.Freq == 0 { + return errUndefinedLoraConf + } + if d.rfswitch != nil { + err := d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP) + if err != nil { + return err + } + } + d.ClearIrqStatus(SX126X_IRQ_ALL) + irqVal := uint16(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR) + d.SetStandby() + d.SetPacketType(SX126X_PACKET_TYPE_LORA) + d.SetRfFrequency(d.loraConf.Freq) + d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U) + d.SetBufferBaseAddress(0, 0) + d.WriteBuffer(pkt) + d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr) + d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, uint8(len(pkt)), d.loraConf.Iq) + d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE) + d.SetSyncWord(d.loraConf.SyncWord) + d.SetTx(timeoutMsToRtcSteps(timeoutMs)) + + msg := <-d.GetRadioEventChan() + if msg.EventType != RadioEventTxDone { + return errors.New("Unexpected Radio Event while TX") + } + return nil +} + +// LoraRx tries to receive a Lora packet (with timeout in milliseconds) +func (d *Device) LoraRx(timeoutMs uint32) ([]uint8, error) { + + if d.loraConf.Freq == 0 { + return nil, errUndefinedLoraConf + } + if d.rfswitch != nil { + err := d.rfswitch.SetRfSwitchMode(RFSWITCH_RX) + if err != nil { + return nil, err + } + } + d.ClearIrqStatus(SX126X_IRQ_ALL) + irqVal := uint16(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR) + d.SetStandby() + d.SetBufferBaseAddress(0, 0) + d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr) + d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, 0xFF, d.loraConf.Iq) + d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE) + d.SetRx(timeoutMsToRtcSteps(timeoutMs)) + + msg := <-d.GetRadioEventChan() + + if msg.EventType == RadioEventTimeout { + return nil, nil + } else if msg.EventType != RadioEventRxDone { + return nil, errors.New("Unexpected Radio Event while RX") + } + + pLen, pStart := d.GetRxBufferStatus() + d.SetBufferBaseAddress(0, pStart+1) + pkt := d.ReadBuffer(pLen + 1) + pkt = pkt[1:] + + return pkt, nil +} + +// HandleInterrupt must be called by main code on DIO state change. +func (d *Device) HandleInterrupt() { + st := d.GetIrqStatus() + d.ClearIrqStatus(SX126X_IRQ_ALL) + + rChan := d.GetRadioEventChan() + + if (st & SX126X_IRQ_RX_DONE) > 0 { + rChan <- NewRadioEvent(RadioEventRxDone, st, nil) + } + + if (st & SX126X_IRQ_TX_DONE) > 0 { + rChan <- NewRadioEvent(RadioEventTxDone, st, nil) + } + + if (st & SX126X_IRQ_TIMEOUT) > 0 { + rChan <- NewRadioEvent(RadioEventTimeout, st, nil) + } + + if (st & SX126X_IRQ_CRC_ERR) > 0 { + rChan <- NewRadioEvent(RadioEventCrcError, st, nil) + } + +}